From 895dc21b306be626437da29e1f0d78b4981b52c7 Mon Sep 17 00:00:00 2001 From: aynsix Date: Thu, 20 Feb 2020 16:51:29 +0300 Subject: [PATCH 1/8] command user:create --- bin/console | 3 + .../Command/User/UserCreateCommand.php | 223 ++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php diff --git a/bin/console b/bin/console index 552cb672d0..7c2d146ea2 100755 --- a/bin/console +++ b/bin/console @@ -46,6 +46,7 @@ use Alchemy\Phrasea\Command\Task\TaskRun; use Alchemy\Phrasea\Command\Task\TaskStart; use Alchemy\Phrasea\Command\Task\TaskState; use Alchemy\Phrasea\Command\Task\TaskStop; +use Alchemy\Phrasea\Command\User\UserCreateCommand; use Alchemy\Phrasea\Command\UpgradeDBDatas; require_once __DIR__ . '/../lib/autoload.php'; @@ -110,6 +111,8 @@ $cli->command(new \module_console_fieldsMerge('fields:merge')); $cli->command(new CreateCollection('collection:create')); $cli->command(new CreateDataboxCommand('databox:create')); +$cli->command(new UserCreateCommand('user:create')); + $cli->command(new RecordAdd('records:add')); $cli->command(new RescanTechnicalDatas('records:rescan-technical-datas')); $cli->command(new BuildMissingSubdefs('records:build-missing-subdefs')); diff --git a/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php b/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php new file mode 100644 index 0000000000..510e9f458f --- /dev/null +++ b/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php @@ -0,0 +1,223 @@ +setDescription('Create user in Phraseanet') + ->addOption('user_login', null, InputOption::VALUE_REQUIRED, 'The desired login for created user.') + ->addOption('user_mail', null, InputOption::VALUE_OPTIONAL, 'The desired mail for created user.') + ->addOption('user_password', null, InputOption::VALUE_OPTIONAL, 'The desired password') + ->addOption('send_mail_confirm', null, InputOption::VALUE_NONE, 'Send an email to user, for validate email.') + ->addOption('send_mail_password', null, InputOption::VALUE_NONE, 'Send an email to user, for define password') + ->addOption('model_number', null, InputOption::VALUE_OPTIONAL, 'Id of model') + ->addOption('user_gender', null, InputOption::VALUE_OPTIONAL, 'The gender for created user.') + ->addOption('user_firstname', null, InputOption::VALUE_OPTIONAL, 'The first name for created user.') + ->addOption('user_lastname', null, InputOption::VALUE_OPTIONAL, 'The last name for created user.') + ->addOption('user_adress', null, InputOption::VALUE_OPTIONAL, 'The adress name for created user.') + ->addOption('user_zipcode', null, InputOption::VALUE_OPTIONAL, 'The zip code for created user.') + ->addOption('user_city', null, InputOption::VALUE_OPTIONAL, 'The city for created user.') + ->addOption('user_compagny', null, InputOption::VALUE_OPTIONAL, 'The compagny for created user.') + ->addOption('user_job', null, InputOption::VALUE_OPTIONAL, 'The job for created user.') + ->addOption('user_activitie', null, InputOption::VALUE_OPTIONAL, 'The activitie for created user.') + ->addOption('user_phone', null, InputOption::VALUE_OPTIONAL, 'The phone number for created user.') + ->addOption('user_fax', null, InputOption::VALUE_OPTIONAL, 'The fax number for created user.') + ->setHelp(''); + + return $this; + } + + protected function doExecute(InputInterface $input, OutputInterface $output) + { + + $userLogin = $input->getOption('user_login'); + $userMail = $input->getOption('user_mail'); + $userPassword = $input->getOption('user_password'); + $sendMailConfirm = $input->getOption('send_mail_confirm'); + $sendMailPassword = $input->getOption('send_mail_password'); + $modelNumber = $input->getOption('model_number'); + $userGender = $input->getOption('user_gender'); + $userFirstName = $input->getOption('user_firstname'); + $userLastName = $input->getOption('user_lastname'); + $userAdress = $input->getOption('user_adress'); + $userZipCode = $input->getOption('user_zipcode'); + $userCity = $input->getOption('user_city'); + $userCompagny = $input->getOption('user_compagny'); + $userJob = $input->getOption('user_job'); + $userActivity = $input->getOption('user_activitie'); + $userPhone = $input->getOption('user_phone'); + $userFax = $input->getOption('user_fax'); + + $userRepository = $this->container['repo.users']; + + if ($userMail) { + if (!\Swift_Validate::email($userMail)) { + $output->writeln('Invalid mail address'); + return 0; + } + + if (null !== $userRepository->findByEmail($userMail)) { + $output->writeln('An user exist with this email.'); + return 0; + } + + } + + $password = (!is_null($userPassword)) ? $userPassword : $this->container['random.medium']->generateString(128); + $userManipulator = $this->container['manipulator.user']; + $user = $userManipulator->createUser($userLogin, $password, $userMail); + + if ($userGender) { + if (null === $gender = $this->verifyGender($userGender)) { + $output->writeln('Gender '.$userGender.' not exists.'); + } + $user->setGender($gender); + } + + if($userFirstName) $user->setFirstName($userFirstName); + if($userLastName) $user->setLastName($userLastName); + if($userAdress) $user->setAddress($userAdress); + if($userZipCode) $user->setZipCode($userZipCode); + if($userCity) $user->setCity($userCity); + if($userCompagny) $user->setCompany($userCompagny); + if($userJob) $user->setJob($userJob); + if($userActivity) $user->setActivity($userActivity); + if($userPhone) $user->setPhone($userPhone); + if($userFax) $user->setFax($userFax); + + if ($sendMailPassword and $userMail and is_null($userPassword)) { + $this->sendPasswordSetupMail($user); + } + + if ($sendMailConfirm and $userMail) { + $user->setMailLocked(true); + $this->sendAccountUnlockEmail($user); + } + + if ($modelNumber) { + $template = $userRepository->find($modelNumber); + if (!$template) { + $output->writeln('Model '.$modelNumber.' not found.'); + } else { + $base_ids = []; + foreach ($this->container->getApplicationBox()->get_databoxes() as $databox) { + foreach ($databox->get_collections() as $collection) { + $base_ids[] = $collection->get_base_id(); + } + } + $this->container->getAclForUser($user)->apply_model($template, $base_ids); + } + } + + $this->container['orm.em']->flush(); + + $output->writeln("Create new user successful !"); + + return 0; + } + + /** + * Get gender for user + * @param $type + * @return int|null + */ + private function verifyGender($type) + { + switch (strtolower($type)) { + case "mlle.": + case "mlle": + case "miss": + case "mademoiselle": + case "0": + $gender = User::GENDER_MISS; + break; + case "mme": + case "madame": + case "ms": + case "ms.": + case "1": + $gender = User::GENDER_MRS; + break; + case "m": + case "m.": + case "mr": + case "mr.": + case "monsieur": + case "mister": + case "2": + $gender = User::GENDER_MR; + break; + default: + $gender = null; + } + return $gender; + } + + /** + * Send mail for renew password + * @param User $user + */ + public function sendPasswordSetupMail(User $user) + { + $this->setDelivererLocator(new LazyLocator($this->container, 'notification.deliverer')); + $receiver = Receiver::fromUser($user); + + $token = $this->container['manipulator.token']->createResetPasswordToken($user); + + $mail = MailRequestPasswordSetup::create($this->container, $receiver); + $servername = $this->container['conf']->get('servername'); + $mail->setButtonUrl('http://'.$servername.'/login/renew-password/?token='.$token->getValue()); + $mail->setLogin($user->getLogin()); + + $this->deliver($mail); + } + + /** + * @param User $user + */ + public function sendAccountUnlockEmail(User $user) + { + $this->setDelivererLocator(new LazyLocator($this->container, 'notification.deliverer')); + $receiver = Receiver::fromUser($user); + + $token = $this->container['manipulator.token']->createAccountUnlockToken($user); + + $mail = MailRequestEmailConfirmation::create($this->container, $receiver); + $servername = $this->container['conf']->get('servername'); + $mail->setButtonUrl('http://'.$servername.'/login/register-confirm/?code='.$token->getValue()); + $mail->setExpiration($token->getExpiration()); + + $this->deliver($mail); + } + +} From d91c4be04d610bc371ea00a107453497822d30b7 Mon Sep 17 00:00:00 2001 From: Nicolas Maillat Date: Thu, 20 Feb 2020 18:43:53 +0100 Subject: [PATCH 2/8] add warn on send_mail_password --- lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php b/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php index 510e9f458f..9eb160de2f 100644 --- a/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php +++ b/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php @@ -39,7 +39,7 @@ class UserCreateCommand extends Command ->addOption('user_mail', null, InputOption::VALUE_OPTIONAL, 'The desired mail for created user.') ->addOption('user_password', null, InputOption::VALUE_OPTIONAL, 'The desired password') ->addOption('send_mail_confirm', null, InputOption::VALUE_NONE, 'Send an email to user, for validate email.') - ->addOption('send_mail_password', null, InputOption::VALUE_NONE, 'Send an email to user, for define password') + ->addOption('send_mail_password', null, InputOption::VALUE_NONE, 'Send an email to user, for password definition, work only if user_password is not define' ') ->addOption('model_number', null, InputOption::VALUE_OPTIONAL, 'Id of model') ->addOption('user_gender', null, InputOption::VALUE_OPTIONAL, 'The gender for created user.') ->addOption('user_firstname', null, InputOption::VALUE_OPTIONAL, 'The first name for created user.') From 0b393a340a2d22ffb1baee0bc8951849c8ba335d Mon Sep 17 00:00:00 2001 From: Nicolas Maillat Date: Thu, 20 Feb 2020 18:55:36 +0100 Subject: [PATCH 3/8] Update UserCreateCommand.php --- lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php b/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php index 9eb160de2f..987238c38e 100644 --- a/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php +++ b/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php @@ -39,7 +39,7 @@ class UserCreateCommand extends Command ->addOption('user_mail', null, InputOption::VALUE_OPTIONAL, 'The desired mail for created user.') ->addOption('user_password', null, InputOption::VALUE_OPTIONAL, 'The desired password') ->addOption('send_mail_confirm', null, InputOption::VALUE_NONE, 'Send an email to user, for validate email.') - ->addOption('send_mail_password', null, InputOption::VALUE_NONE, 'Send an email to user, for password definition, work only if user_password is not define' ') + ->addOption('send_mail_password', null, InputOption::VALUE_NONE, 'Send an email to user, for password definition, work only if user_password is not define') ->addOption('model_number', null, InputOption::VALUE_OPTIONAL, 'Id of model') ->addOption('user_gender', null, InputOption::VALUE_OPTIONAL, 'The gender for created user.') ->addOption('user_firstname', null, InputOption::VALUE_OPTIONAL, 'The first name for created user.') From f79ae4f05eec9d0c4bd5125a5d8ed26866759684 Mon Sep 17 00:00:00 2001 From: aynsix Date: Fri, 21 Feb 2020 14:17:20 +0300 Subject: [PATCH 4/8] remove city and fax option to the command user:create --- lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php b/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php index 987238c38e..0685ab329c 100644 --- a/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php +++ b/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php @@ -46,12 +46,10 @@ class UserCreateCommand extends Command ->addOption('user_lastname', null, InputOption::VALUE_OPTIONAL, 'The last name for created user.') ->addOption('user_adress', null, InputOption::VALUE_OPTIONAL, 'The adress name for created user.') ->addOption('user_zipcode', null, InputOption::VALUE_OPTIONAL, 'The zip code for created user.') - ->addOption('user_city', null, InputOption::VALUE_OPTIONAL, 'The city for created user.') ->addOption('user_compagny', null, InputOption::VALUE_OPTIONAL, 'The compagny for created user.') ->addOption('user_job', null, InputOption::VALUE_OPTIONAL, 'The job for created user.') ->addOption('user_activitie', null, InputOption::VALUE_OPTIONAL, 'The activitie for created user.') ->addOption('user_phone', null, InputOption::VALUE_OPTIONAL, 'The phone number for created user.') - ->addOption('user_fax', null, InputOption::VALUE_OPTIONAL, 'The fax number for created user.') ->setHelp(''); return $this; @@ -71,13 +69,11 @@ class UserCreateCommand extends Command $userLastName = $input->getOption('user_lastname'); $userAdress = $input->getOption('user_adress'); $userZipCode = $input->getOption('user_zipcode'); - $userCity = $input->getOption('user_city'); $userCompagny = $input->getOption('user_compagny'); $userJob = $input->getOption('user_job'); $userActivity = $input->getOption('user_activitie'); $userPhone = $input->getOption('user_phone'); - $userFax = $input->getOption('user_fax'); - + $userRepository = $this->container['repo.users']; if ($userMail) { @@ -108,13 +104,11 @@ class UserCreateCommand extends Command if($userLastName) $user->setLastName($userLastName); if($userAdress) $user->setAddress($userAdress); if($userZipCode) $user->setZipCode($userZipCode); - if($userCity) $user->setCity($userCity); if($userCompagny) $user->setCompany($userCompagny); if($userJob) $user->setJob($userJob); if($userActivity) $user->setActivity($userActivity); if($userPhone) $user->setPhone($userPhone); - if($userFax) $user->setFax($userFax); - + if ($sendMailPassword and $userMail and is_null($userPassword)) { $this->sendPasswordSetupMail($user); } From 015ce2506b8c735dd1c22ede8b87523681d90ff3 Mon Sep 17 00:00:00 2001 From: aynsix Date: Fri, 21 Feb 2020 16:02:33 +0300 Subject: [PATCH 5/8] remove adress and zipcode option to the user:create command --- lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php b/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php index 0685ab329c..66a33f1916 100644 --- a/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php +++ b/lib/Alchemy/Phrasea/Command/User/UserCreateCommand.php @@ -44,8 +44,6 @@ class UserCreateCommand extends Command ->addOption('user_gender', null, InputOption::VALUE_OPTIONAL, 'The gender for created user.') ->addOption('user_firstname', null, InputOption::VALUE_OPTIONAL, 'The first name for created user.') ->addOption('user_lastname', null, InputOption::VALUE_OPTIONAL, 'The last name for created user.') - ->addOption('user_adress', null, InputOption::VALUE_OPTIONAL, 'The adress name for created user.') - ->addOption('user_zipcode', null, InputOption::VALUE_OPTIONAL, 'The zip code for created user.') ->addOption('user_compagny', null, InputOption::VALUE_OPTIONAL, 'The compagny for created user.') ->addOption('user_job', null, InputOption::VALUE_OPTIONAL, 'The job for created user.') ->addOption('user_activitie', null, InputOption::VALUE_OPTIONAL, 'The activitie for created user.') @@ -67,8 +65,6 @@ class UserCreateCommand extends Command $userGender = $input->getOption('user_gender'); $userFirstName = $input->getOption('user_firstname'); $userLastName = $input->getOption('user_lastname'); - $userAdress = $input->getOption('user_adress'); - $userZipCode = $input->getOption('user_zipcode'); $userCompagny = $input->getOption('user_compagny'); $userJob = $input->getOption('user_job'); $userActivity = $input->getOption('user_activitie'); @@ -102,8 +98,6 @@ class UserCreateCommand extends Command if($userFirstName) $user->setFirstName($userFirstName); if($userLastName) $user->setLastName($userLastName); - if($userAdress) $user->setAddress($userAdress); - if($userZipCode) $user->setZipCode($userZipCode); if($userCompagny) $user->setCompany($userCompagny); if($userJob) $user->setJob($userJob); if($userActivity) $user->setActivity($userActivity); From e2e6879cd2c67b607a823cfe2d121e57899b94fc Mon Sep 17 00:00:00 2001 From: aynsix Date: Fri, 21 Feb 2020 16:57:45 +0300 Subject: [PATCH 6/8] add jsonformat to user:list --- .../Phrasea/Command/User/UserListCommand.php | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/Alchemy/Phrasea/Command/User/UserListCommand.php b/lib/Alchemy/Phrasea/Command/User/UserListCommand.php index 1777c017cb..6fe659223c 100644 --- a/lib/Alchemy/Phrasea/Command/User/UserListCommand.php +++ b/lib/Alchemy/Phrasea/Command/User/UserListCommand.php @@ -42,6 +42,7 @@ class UserListCommand extends Command ->addOption('application', null, InputOption::VALUE_NONE, 'List application of user work only if --user_id is set') ->addOption('right', null, InputOption::VALUE_NONE, 'Show right information') ->addOption('adress', null, InputOption::VALUE_NONE, 'Show adress information') + ->addOption('jsonformat', null, InputOption::VALUE_NONE, 'Output in json format') ->setHelp(''); return $this; @@ -61,6 +62,7 @@ class UserListCommand extends Command $created = $input->getOption('created'); $updated = $input->getOption('updated'); $withRight = $input->getOption('right'); + $jsonformat = $input->getOption('jsonformat'); $query = $this->container['phraseanet.user-query']; @@ -87,24 +89,33 @@ class UserListCommand extends Command if ($userId and $application) { $showApplication = true; } - $userList[] = $this->listUser($user,$withAdress,$withRight); + $userList[] = $this->listUser($user, $withAdress, $withRight); + + $userListRaw[] = array_combine($this->headerTable($withAdress, $withRight), $this->listUser($user, $withAdress, $withRight)); } - $table = $this->getHelperSet()->get('table'); - $table - ->setHeaders($this->headerTable($withAdress,$withRight)) - ->setRows($userList) - ->render($output); - ; + if ($jsonformat) { + echo json_encode($userListRaw); + } else { + $table = $this->getHelperSet()->get('table'); + $table + ->setHeaders($this->headerTable($withAdress, $withRight)) + ->setRows($userList) + ->render($output); + ; + + + if ($showApplication) { + $applicationTable = $this->getHelperSet()->get('table'); + $applicationTable->setHeaders(array( + array(new TableCell('Applications', array('colspan' => 5))), + ['name','callback','client_secret','client_id','token'], + ))->setRows($this->getApplicationOfUser($users[0]))->render($output); + } - if ($showApplication) { - $applicationTable = $this->getHelperSet()->get('table'); - $applicationTable->setHeaders(array( - array(new TableCell('Applications', array('colspan' => 5))), - ['name','callback','client_secret','client_id','token'], - ))->setRows($this->getApplicationOfUser($users[0]))->render($output); } + return 0; } From f8f9d9f9c9e5d8f8e9835321dec3f26312fa1632 Mon Sep 17 00:00:00 2001 From: aynsix Date: Fri, 21 Feb 2020 17:16:14 +0300 Subject: [PATCH 7/8] add jsonformat option to databox:list --- .../Command/Databox/ListDataboxCommand.php | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/Alchemy/Phrasea/Command/Databox/ListDataboxCommand.php b/lib/Alchemy/Phrasea/Command/Databox/ListDataboxCommand.php index 49775437d5..92a30435fe 100644 --- a/lib/Alchemy/Phrasea/Command/Databox/ListDataboxCommand.php +++ b/lib/Alchemy/Phrasea/Command/Databox/ListDataboxCommand.php @@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\Command\Databox; use Alchemy\Phrasea\Command\Command; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class ListDataboxCommand extends Command @@ -25,6 +26,7 @@ class ListDataboxCommand extends Command parent::__construct('databox:list'); $this->setDescription('List all databox in Phraseanet') + ->addOption('jsonformat', null, InputOption::VALUE_NONE, 'Output in json format') ->setHelp(''); return $this; @@ -33,15 +35,23 @@ class ListDataboxCommand extends Command protected function doExecute(InputInterface $input, OutputInterface $output) { try { + $jsonformat = $input->getOption('jsonformat'); $databoxes = array_map(function (\databox $databox) { return $this->listDatabox($databox); }, $this->container->getApplicationBox()->get_databoxes()); - $table = $this->getHelperSet()->get('table'); - $table - ->setHeaders(['id', 'name', 'alias']) - ->setRows($databoxes) - ->render($output); + if ($jsonformat) { + foreach ($databoxes as $databox) { + $databoxList[] = array_combine(['id', 'name', 'alias'], $databox); + } + echo json_encode($databoxList); + } else { + $table = $this->getHelperSet()->get('table'); + $table + ->setHeaders(['id', 'name', 'alias']) + ->setRows($databoxes) + ->render($output); + } } catch (\Exception $e) { $output->writeln('Listing databox failed : '.$e->getMessage().''); From 497307c0dc750c6e8bf70cf3e38b49684fe865fc Mon Sep 17 00:00:00 2001 From: aynsix Date: Fri, 21 Feb 2020 17:37:04 +0300 Subject: [PATCH 8/8] add jsonformat to collection:list --- .../Collection/ListCollectionCommand.php | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/Alchemy/Phrasea/Command/Collection/ListCollectionCommand.php b/lib/Alchemy/Phrasea/Command/Collection/ListCollectionCommand.php index e76796e8b7..7cf2d91ced 100644 --- a/lib/Alchemy/Phrasea/Command/Collection/ListCollectionCommand.php +++ b/lib/Alchemy/Phrasea/Command/Collection/ListCollectionCommand.php @@ -24,20 +24,30 @@ class ListCollectionCommand extends Command parent::__construct('collection:list'); $this->setDescription('List all collection in Phraseanet') ->addOption('databox_id', 'd', InputOption::VALUE_REQUIRED, 'The id of the databox to list collection') + ->addOption('jsonformat', null, InputOption::VALUE_NONE, 'Output in json format') ->setHelp(''); return $this; } protected function doExecute(InputInterface $input, OutputInterface $output) { try { - $databox = $this->container->findDataboxById($input->getOption('databox_id')); - $collections = $this->listDataboxCollections($databox); + $jsonformat = $input->getOption('jsonformat'); + $databox = $this->container->findDataboxById($input->getOption('databox_id')); + $collections = $this->listDataboxCollections($databox); + + if ($jsonformat) { + foreach ($collections as $collection) { + $collectionList[] = array_combine(['id local for API', 'id distant', 'name','label','status','total records'], $collection); + } + echo json_encode($collectionList); + } else { + $table = $this->getHelperSet()->get('table'); + $table + ->setHeaders(['id local for API', 'id distant', 'name','label','status','total records']) + ->setRows($collections) + ->render($output); + } - $table = $this->getHelperSet()->get('table'); - $table - ->setHeaders(['id local for API', 'id distant', 'name','label','status','total records']) - ->setRows($collections) - ->render($output); } catch (\Exception $e) { $output->writeln("{$e->getMessage()}"); }