diff --git a/components/ILIAS/Mail/classes/Folder/MailFolderData.php b/components/ILIAS/Mail/classes/Folder/MailFolderData.php index 5d6526f04ea5..b74aa2da0858 100644 --- a/components/ILIAS/Mail/classes/Folder/MailFolderData.php +++ b/components/ILIAS/Mail/classes/Folder/MailFolderData.php @@ -60,6 +60,11 @@ public function isDrafts(): bool return $this->type === MailFolderType::DRAFTS; } + public function isOutbox(): bool + { + return $this->type === MailFolderType::OUTBOX; + } + public function isSent(): bool { return $this->type === MailFolderType::SENT; diff --git a/components/ILIAS/Mail/classes/Folder/MailFolderTableUI.php b/components/ILIAS/Mail/classes/Folder/MailFolderTableUI.php index 885a7c75bae6..88c1bfe9088d 100644 --- a/components/ILIAS/Mail/classes/Folder/MailFolderTableUI.php +++ b/components/ILIAS/Mail/classes/Folder/MailFolderTableUI.php @@ -241,7 +241,7 @@ private function getActions(): array } } - if ($this->current_folder->isDrafts()) { + if ($this->current_folder->isDrafts() || $this->current_folder->isOutbox()) { unset($actions[self::ACTION_SHOW], $actions[self::ACTION_REPLY], $actions[self::ACTION_FORWARD]); } else { unset($actions[self::ACTION_EDIT]); @@ -421,7 +421,7 @@ private function getSubject(MailRecordData $record): Link (string) $this->url_builder ->withParameter( $this->action_token, - $this->current_folder->isDrafts() ? self::ACTION_EDIT : self::ACTION_SHOW + $this->current_folder->isDrafts() || $this->current_folder->isOutbox() ? self::ACTION_EDIT : self::ACTION_SHOW ) ->withParameter($this->row_id_token, (string) $record->getMailId()) ->buildURI() diff --git a/components/ILIAS/Mail/classes/Folder/MailFolderType.php b/components/ILIAS/Mail/classes/Folder/MailFolderType.php index dfbbcae20ca4..f6d2650f586e 100644 --- a/components/ILIAS/Mail/classes/Folder/MailFolderType.php +++ b/components/ILIAS/Mail/classes/Folder/MailFolderType.php @@ -29,4 +29,5 @@ enum MailFolderType: string case SENT = 'sent'; case LOCAL = 'local'; case USER = 'user_folder'; + case OUTBOX = 'outbox'; } diff --git a/components/ILIAS/Mail/classes/class.ilMail.php b/components/ILIAS/Mail/classes/class.ilMail.php index d3b8e550ffac..3e50f33b7625 100755 --- a/components/ILIAS/Mail/classes/class.ilMail.php +++ b/components/ILIAS/Mail/classes/class.ilMail.php @@ -461,16 +461,18 @@ public function updateDraft( string $a_m_subject, string $a_m_message, int $a_draft_id = 0, + ?ilDateTime $a_send_time = null, bool $a_use_placeholders = false, ?string $a_tpl_context_id = null, array $a_tpl_context_params = [] ): int { + $a_send_time?->switchTimeZone('UTC'); $this->db->update( $this->table_mail, [ 'folder_id' => ['integer', $a_folder_id], 'attachments' => ['clob', serialize($a_attachments)], - 'send_time' => ['timestamp', date('Y-m-d H:i:s')], + 'send_time' => ['timestamp', $a_send_time ? $a_send_time->get(IL_CAL_DATETIME) : date('Y-m-d H:i:s')], 'rcp_to' => ['clob', $a_rcp_to], 'rcp_cc' => ['clob', $a_rcp_cc], 'rcp_bcc' => ['clob', $a_rcp_bcc], @@ -489,6 +491,55 @@ public function updateDraft( return $a_draft_id; } + public function sendTerminatedMail( + int $outbox_id, + int $folder_id, + int $sender_usr_id, + array $attachments, + string $to, + string $cc, + string $bcc, + string $subject, + string $message, + ?ilDateTime $a_send_time = null, + bool $use_placeholders = false, + ?string $template_context_id = null, + array $template_context_parameters = [] + ): int { + if ($use_placeholders) { + $message = $this->replacePlaceholders($message, $sender_usr_id); + } + $a_send_time?->switchTimeZone('UTC'); + $message = str_ireplace(['
', '
', '
'], "\n", $message); + $mail_values = [ + 'user_id' => ['integer', $sender_usr_id], + 'folder_id' => ['integer', $folder_id], + 'sender_id' => ['integer', $sender_usr_id], + 'attachments' => ['clob', serialize($attachments)], + 'send_time' => ['timestamp', $a_send_time ? $a_send_time->get(IL_CAL_DATETIME) : date('Y-m-d H:i:s')], + 'rcp_to' => ['clob', $to], + 'rcp_cc' => ['clob', $cc], + 'rcp_bcc' => ['clob', $bcc], + 'm_status' => ['text', 'read'], + 'm_subject' => ['text', $subject], + 'm_message' => ['clob', $message], + 'tpl_ctx_id' => ['text', $template_context_id], + 'tpl_ctx_params' => ['blob', json_encode($template_context_parameters, JSON_THROW_ON_ERROR)], + ]; + + if (!$outbox_id) { + $outbox_id = $this->db->nextId($this->table_mail); + $mail_values['mail_id'] = ['integer', $outbox_id]; + $this->db->insert($this->table_mail, $mail_values); + } else { + $this->db->update($this->table_mail, $mail_values, [ + 'mail_id' => ['integer', $outbox_id], + ]); + } + + return $outbox_id; + } + private function sendInternalMail( int $folder_id, int $sender_usr_id, diff --git a/components/ILIAS/Mail/classes/class.ilMailCronTerminatedMails.php b/components/ILIAS/Mail/classes/class.ilMailCronTerminatedMails.php new file mode 100644 index 000000000000..be02101260fe --- /dev/null +++ b/components/ILIAS/Mail/classes/class.ilMailCronTerminatedMails.php @@ -0,0 +1,177 @@ +init_done) { + $this->settings = $DIC->settings(); + $this->lng = $DIC->language(); + $this->db = $DIC->database(); + $this->user = $DIC->user(); + $this->http = $DIC->http(); + $this->cron_manager = $DIC->cron()->manager(); + $this->mail = new ilMail($this->user->getId()); + $this->umail = new ilFormatMail($this->user->getId()); + + $this->lng->loadLanguageModule('mail'); + $this->init_done = true; + } + } + + public function getId(): string + { + return 'mail_terminated_mails'; + } + + public function getTitle(): string + { + $this->init(); + + return $this->lng->txt('mail_cron_terminated_mails'); + } + + public function getDescription(): string + { + $this->init(); + + return $this->lng->txt('mail_cron_terminated_mails_desc'); + } + + public function hasAutoActivation(): bool + { + return true; + } + + public function hasFlexibleSchedule(): bool + { + return true; + } + + public function getDefaultScheduleType(): JobScheduleType + { + return JobScheduleType::DAILY; + } + + public function getDefaultScheduleValue(): ?int + { + return 1; + } + + public function run(): JobResult + { + $this->init(); + + $job_result = new JobResult(); + $job_result->setStatus(JobResult::STATUS_OK); + + ilLoggerFactory::getLogger('mail')->info('Start sending terminated mails from all users.'); + + $mails = $this->getOutboxMails(); + $sent_mail_ids = []; + foreach ($mails as $mail) { + try { + $mailer = $this->umail + ->withContextId(ilContext::CONTEXT_CRON); + + $mailer->setSaveInSentbox(true); + + $mailer->autoresponder()->enableAutoresponder(); + $errors = $mailer->enqueue( + $mail['rcp_to'], + $mail['rcp_cc'], + $mail['rcp_bcc'], + $mail['m_subject'], + $mail['m_message'], + unserialize($mail['attachments'], ['allowed_classes' => false]), + (bool) ($mail['use_placeholders'] ?? false) + ); + + if (empty($errors)) { + $sent_mail_ids[] = (int) ($mail['mail_id'] ?? 0); + } + } catch (\Exception $e) { + $job_result->setStatus(JobResult::STATUS_FAIL); + ilLoggerFactory::getLogger('mail')->error( + 'Error sending terminated mail with id ' . ($mail['mail_id'] ?? 'unknown') . ': ' . $e->getMessage() + ); + $job_result->setMessage($e->getMessage()); + return $job_result; + } + } + $this->mail->deleteMails($sent_mail_ids); + ilLoggerFactory::getLogger('mail')->info( + 'Sent ' . count($sent_mail_ids) . ' terminated mails and removed them from outbox.' + ); + $job_result->setMessage('Processed ' . count($sent_mail_ids) . ' mails.'); + return $job_result; + } + + public function getOutboxMails(): Generator + { + $res = $this->db->queryF( + <<<'SQL' + SELECT * FROM mail + JOIN mail_obj_data ON mail.folder_id = mail_obj_data.obj_id + WHERE mail_obj_data.m_type = %s + AND send_time IS NOT NULL + AND send_time <= %s + SQL, + ['text', 'timestamp'], + ['outbox', (new DateTimeImmutable('NOW', new DateTimeZone('UTC')))->format('Y-m-d H:i:s')] + ); + + while ($row = $this->db->fetchAssoc($res)) { + yield $row; + } + } +} diff --git a/components/ILIAS/Mail/classes/class.ilMailFolderGUI.php b/components/ILIAS/Mail/classes/class.ilMailFolderGUI.php index 88273eca5ad3..f3e2a256e76a 100755 --- a/components/ILIAS/Mail/classes/class.ilMailFolderGUI.php +++ b/components/ILIAS/Mail/classes/class.ilMailFolderGUI.php @@ -239,7 +239,15 @@ protected function executeTableAction(): void (string) $this->folder->getFolderId() ); $this->ctrl->setParameterByClass(ilMailFormGUI::class, self::PARAM_MAIL_ID, (string) $mail_ids[0]); - $this->ctrl->setParameterByClass(ilMailFormGUI::class, 'type', ilMailFormGUI::MAIL_FORM_TYPE_DRAFT); + if ($this->folder->isOutbox()) { + $this->ctrl->setParameterByClass( + ilMailFormGUI::class, + 'type', + ilMailFormGUI::MAIL_FORM_TYPE_OUTBOX + ); + } else { + $this->ctrl->setParameterByClass(ilMailFormGUI::class, 'type', ilMailFormGUI::MAIL_FORM_TYPE_DRAFT); + } $this->ctrl->redirectByClass(ilMailFormGUI::class); // no break diff --git a/components/ILIAS/Mail/classes/class.ilMailFormAttachmentPropertyGUI.php b/components/ILIAS/Mail/classes/class.ilMailFormAttachmentPropertyGUI.php index fc3c85f49096..3fa4d46b23ab 100755 --- a/components/ILIAS/Mail/classes/class.ilMailFormAttachmentPropertyGUI.php +++ b/components/ILIAS/Mail/classes/class.ilMailFormAttachmentPropertyGUI.php @@ -49,4 +49,13 @@ public function insert(ilTemplate $a_tpl): void $a_tpl->setVariable('PROP_GENERIC', $tpl->get()); $a_tpl->parseCurrentBlock(); } + + public function checkInput(): bool + { + return true; + } + + public function setValueByArray(): void + { + } } diff --git a/components/ILIAS/Mail/classes/class.ilMailFormGUI.php b/components/ILIAS/Mail/classes/class.ilMailFormGUI.php index 8840fcfc1229..23babca97142 100755 --- a/components/ILIAS/Mail/classes/class.ilMailFormGUI.php +++ b/components/ILIAS/Mail/classes/class.ilMailFormGUI.php @@ -38,6 +38,7 @@ class ilMailFormGUI final public const string MAIL_FORM_TYPE_ADDRESS = 'address'; final public const string MAIL_FORM_TYPE_FORWARD = 'forward'; final public const string MAIL_FORM_TYPE_DRAFT = 'draft'; + final public const string MAIL_FORM_TYPE_OUTBOX = 'outbox'; private readonly ilGlobalTemplateInterface $tpl; private readonly ilCtrlInterface $ctrl; @@ -175,20 +176,126 @@ protected function decodeAttachmentFiles(array $files): array return $decoded_files; } - public function sendMessage(): void + public function saveMessageToOutbox(): void { - $message = $this->getBodyParam('m_message', $this->refinery->kindlyTo()->string(), ''); + $termination = new DateTimeImmutable( + $this->getBodyParam('m_termination', $this->refinery->kindlyTo()->string(), null), + new DateTimeZone($this->user->getTimezone()) + ); + $termination = new ilDateTime($termination->getTimestamp(), IL_CAL_UNIX, $this->user->getTimezone()); + + $files = $this->decodeAttachmentFiles($this->getBodyParam( + 'attachments', + $this->refinery->kindlyTo()->listOf( + $this->refinery->custom()->transformation($this->refinery->kindlyTo()->string()) + ), + [] + )); - $mail_body = new ilMailBody($message, $this->purifier); + $rcp_to = ilUtil::securePlainString($this->getBodyParam('rcp_to', $this->refinery->kindlyTo()->string(), '')); + $rcp_cc = ilUtil::securePlainString($this->getBodyParam('rcp_cc', $this->refinery->kindlyTo()->string(), '')); + $rcp_bcc = ilUtil::securePlainString($this->getBodyParam('rcp_bcc', $this->refinery->kindlyTo()->string(), '')); + + $message = ilUtil::securePlainString($this->getBodyParam('m_message', $this->refinery->kindlyTo()->string(), '')); + $mail_body = new ilMailBody($message, $this->purifier); $sanitized_message = $mail_body->getContent(); + $outbox_folder_id = $this->mbox->getOutboxFolder(); + if (ilSession::get('outbox')) { + $outbox_id = (int) ilSession::get('outbox'); + ilSession::clear('outbox'); + } + + $this->umail->sendTerminatedMail( + $outbox_id ?? 0, + $outbox_folder_id, + $this->user->getId(), + $files, + $rcp_to, + $rcp_cc, + $rcp_bcc, + ilUtil::securePlainString( + $this->getBodyParam('m_subject', $this->refinery->kindlyTo()->string(), '') + ) ?: 'No Subject', + $sanitized_message, + $termination, + $this->getBodyParam('use_placeholders', $this->refinery->kindlyTo()->bool(), false), + ilMailFormCall::getContextId(), + ilMailFormCall::getContextParameters() + ); + + if (ilSession::get('draft')) { + $draft_id = (int) ilSession::get('draft'); + ilSession::clear('draft'); + $this->umail->deleteMails([$draft_id]); + } + + $this->ctrl->setParameterByClass(ilMailFolderGUI::class, 'mobj_id', $outbox_folder_id); + $this->tpl->setOnScreenMessage('info', $this->lng->txt('mail_saved'), true); + + if (ilMailFormCall::isRefererStored()) { + ilUtil::redirect(ilMailFormCall::getRefererRedirectUrl()); + } else { + $this->ctrl->redirectByClass([ilMailGUI::class, ilMailFolderGUI::class]); + } + + $this->showForm(); + } + + public function sendMessage(): void + { $attachments = $this->getBodyParam( 'attachments', $this->refinery->kindlyTo()->listOf($this->refinery->kindlyTo()->string()), [] ); $files = $this->decodeAttachmentFiles($attachments); + $mail_data = $this->http->request()->getParsedBody(); + $mail_data['attachments'] = $files; + $form = $this->getShowForm($mail_data); + ; + $errors = $this->umail->validateRecipients( + ilUtil::securePlainString($this->getBodyParam('rcp_to', $this->refinery->kindlyTo()->string(), '')), + ilUtil::securePlainString($this->getBodyParam('rcp_cc', $this->refinery->kindlyTo()->string(), '')), + ilUtil::securePlainString($this->getBodyParam('rcp_bcc', $this->refinery->kindlyTo()->string(), '')) + ); + $form_valid = $form->checkInput(); + if ($errors || !$form_valid) { + if ($errors) { + $this->showSubmissionErrors($errors); + } + $this->tpl->addJavaScript('assets/js/ilMailComposeFunctions.js'); + $this->tpl->setContent($form->getHTML()); + $this->tpl->printToStdout(); + $this->http->close(); + } + + $termination = new DateTimeImmutable( + $this->getBodyParam('m_termination', $this->refinery->kindlyTo()->string(), null), + new DateTimeZone($this->user->getTimezone()) + ); + if ( + $termination->getTimestamp() > time() && + $this->getBodyParam('use_termination', $this->refinery->kindlyTo()->bool(), false) + ) { + $this->saveMessageToOutbox(); + } + + $mail_id = 0; + if (ilSession::get('outbox')) { + $mail_id = (int) ilSession::get('outbox'); + ilSession::clear('outbox'); + } elseif (ilSession::get('draft')) { + $mail_id = (int) ilSession::get('draft'); + ilSession::clear('draft'); + } + + $message = $this->getBodyParam('m_message', $this->refinery->kindlyTo()->string(), ''); + + $mail_body = new ilMailBody($message, $this->purifier); + + $sanitized_message = $mail_body->getContent(); $mailer = $this->umail ->withContextId(ilMailFormCall::getContextId() ?: '') @@ -211,6 +318,9 @@ public function sendMessage(): void $this->showSubmissionErrors($errors); } else { $mailer->autoresponder()->disableAutoresponder(); + if ($mail_id) { + $mailer->deleteMails([$mail_id]); + } $mailer->persistToStage( $this->user->getId(), @@ -238,7 +348,15 @@ public function sendMessage(): void public function saveDraft(): void { - $draft_folder_id = $this->mbox->getDraftsFolder(); + $rcp_to = ilUtil::securePlainString($this->getBodyParam('rcp_to', $this->refinery->kindlyTo()->string(), '')); + $rcp_cc = ilUtil::securePlainString($this->getBodyParam('rcp_cc', $this->refinery->kindlyTo()->string(), '')); + $rcp_bcc = ilUtil::securePlainString($this->getBodyParam('rcp_bcc', $this->refinery->kindlyTo()->string(), '')); + + $errors = $this->umail->validateRecipients( + $rcp_to, + $rcp_cc, + $rcp_bcc, + ); $files = $this->decodeAttachmentFiles($this->getBodyParam( 'attachments', @@ -248,19 +366,26 @@ public function saveDraft(): void [] )); - $rcp_to = ilUtil::securePlainString($this->getBodyParam('rcp_to', $this->refinery->kindlyTo()->string(), '')); - $rcp_cc = ilUtil::securePlainString($this->getBodyParam('rcp_cc', $this->refinery->kindlyTo()->string(), '')); - $rcp_bcc = ilUtil::securePlainString($this->getBodyParam('rcp_bcc', $this->refinery->kindlyTo()->string(), '')); + $mail_data = $this->http->request()->getParsedBody(); + $mail_data['attachments'] = $files; + $form = $this->getShowForm($mail_data); + $form_valid = $form->checkInput(); + if ($errors || !$form_valid) { + if ($errors) { + $this->showSubmissionErrors($errors); + } + $this->tpl->addJavaScript('assets/js/ilMailComposeFunctions.js'); + $this->tpl->setContent($form->getHTML()); + $this->tpl->printToStdout(); + $this->http->close(); + } - if ($errors = $this->umail->validateRecipients( - $rcp_to, - $rcp_cc, - $rcp_bcc, - )) { - $this->request_attachments = $files; - $this->showSubmissionErrors($errors); - $this->showForm(); - return; + $draft_folder_id = $this->mbox->getDraftsFolder(); + + if (ilSession::get('outbox')) { + $outbox_id = (int) ilSession::get('outbox'); + ilSession::clear('outbox'); + $this->umail->deleteMails([$outbox_id]); } if (ilSession::get('draft')) { @@ -270,6 +395,12 @@ public function saveDraft(): void $draft_id = $this->umail->getNewDraftId($draft_folder_id); } + $termination = new DateTimeImmutable( + $this->getBodyParam('m_termination', $this->refinery->kindlyTo()->string(), null), + new DateTimeZone($this->user->getTimezone()) + ); + $termination = new ilDateTime($termination->getTimestamp(), IL_CAL_UNIX, $this->user->getTimezone()); + $this->umail->updateDraft( $draft_folder_id, $files, @@ -281,11 +412,13 @@ public function saveDraft(): void ) ?: 'No Subject', ilUtil::securePlainString($this->getBodyParam('m_message', $this->refinery->kindlyTo()->string(), '')), $draft_id, + $termination, $this->getBodyParam('use_placeholders', $this->refinery->kindlyTo()->bool(), false), ilMailFormCall::getContextId(), ilMailFormCall::getContextParameters() ); + $this->ctrl->setParameterByClass(ilMailFolderGUI::class, 'mobj_id', $draft_folder_id); $this->tpl->setOnScreenMessage('info', $this->lng->txt('mail_saved'), true); if (ilMailFormCall::isRefererStored()) { @@ -584,12 +717,21 @@ public function showForm(): void ilMailFormCall::setContextParameters($mail_data['tpl_ctx_params']); break; + case self::MAIL_FORM_TYPE_OUTBOX: + ilSession::set('outbox', $mail_id); + $mail_data = $this->umail->getMail($mail_id); + ilMailFormCall::setContextId($mail_data['tpl_ctx_id']); + ilMailFormCall::setContextParameters($mail_data['tpl_ctx_params']); + break; + case self::MAIL_FORM_TYPE_FORWARD: $mail_data = $this->umail->getMail($mail_id); $mail_data['rcp_to'] = $mail_data['rcp_cc'] = $mail_data['rcp_bcc'] = ''; $mail_data['m_subject'] = $this->umail->formatForwardSubject($mail_data['m_subject'] ?? ''); $mail_data['m_message'] = $this->umail->prependSignature($mail_data['m_message'] ?? ''); - if (is_array($mail_data['attachments']) && count($mail_data['attachments']) && $error = $this->mfile->adoptAttachments( + if (is_array($mail_data['attachments']) && count( + $mail_data['attachments'] + ) && $error = $this->mfile->adoptAttachments( $mail_data['attachments'], $mail_id )) { @@ -599,19 +741,25 @@ public function showForm(): void case self::MAIL_FORM_TYPE_NEW: // Note: For security reasons, ILIAS only allows Plain text strings in E-Mails. - $to = ilUtil::securePlainString($this->getQueryParam('rcp_to', $this->refinery->kindlyTo()->string(), '')); + $to = ilUtil::securePlainString( + $this->getQueryParam('rcp_to', $this->refinery->kindlyTo()->string(), '') + ); if ($to === '' && ilSession::get('rcp_to')) { $to = ilSession::get('rcp_to'); } $mail_data['rcp_to'] = $to; - $cc = ilUtil::securePlainString($this->getQueryParam('rcp_cc', $this->refinery->kindlyTo()->string(), '')); + $cc = ilUtil::securePlainString( + $this->getQueryParam('rcp_cc', $this->refinery->kindlyTo()->string(), '') + ); if ($cc === '' && ilSession::get('rcp_cc')) { $cc = ilSession::get('rcp_cc'); } $mail_data['rcp_cc'] = $cc; - $bcc = ilUtil::securePlainString($this->getQueryParam('rcp_bcc', $this->refinery->kindlyTo()->string(), '')); + $bcc = ilUtil::securePlainString( + $this->getQueryParam('rcp_bcc', $this->refinery->kindlyTo()->string(), '') + ); if ($bcc === '' && ilSession::get('rcp_bcc')) { $bcc = ilSession::get('rcp_bcc'); } @@ -661,10 +809,12 @@ public function showForm(): void $additional_msg_text = ''; if ($this->http->wrapper()->post()->has('additional_message_text')) { - $additional_msg_text = ilUtil::securePlainString($this->http->wrapper()->post()->retrieve( - 'additional_message_text', - $this->refinery->kindlyTo()->string() - )); + $additional_msg_text = ilUtil::securePlainString( + $this->http->wrapper()->post()->retrieve( + 'additional_message_text', + $this->refinery->kindlyTo()->string() + ) + ); } $mail_data['m_message'] .= $additional_msg_text @@ -691,151 +841,80 @@ public function showForm(): void } } - if ($this->request_attachments) { - $mail_data['attachments'] = $this->request_attachments; - } break; } + $this->tpl->parseCurrentBlock(); + + $this->tpl->setVariable('FORM', $this->getShowForm($mail_data)->getHTML()); + + $this->tpl->addJavaScript('assets/js/ilMailComposeFunctions.js'); + $this->tpl->printToStdout(); + } + + public function getShowForm(array $mail_data = []): ilPropertyFormGUI + { $form_gui = new ilPropertyFormGUI(); $form_gui->setTitle($this->lng->txt('compose')); $form_gui->setId('mail_compose_form'); $form_gui->setName('mail_compose_form'); $form_gui->setFormAction($this->ctrl->getFormAction($this, 'sendMessage')); - $this->tpl->setVariable('FORM_ID', $form_gui->getId()); - - $mail_form = 'form_' . $form_gui->getName(); - - $btn = $this->ui_factory->button() - ->standard($this->lng->txt('search_recipients'), '#') - ->withOnLoadCode(static fn($id): string => " - document.getElementById('$id').addEventListener('click', function() { - const frm = document.getElementById('$mail_form'), - action = new URL(frm.action), - action_params = new URLSearchParams(action.search); - - action_params.delete('cmd'); - action_params.append('cmd', 'searchUsers'); - - action.search = action_params.toString(); - - frm.action = action.href; - frm.submit(); - return false; - }); - "); - $this->toolbar->addStickyItem($btn); - - $btn = $this->ui_factory->button() - ->standard($this->lng->txt('mail_my_courses'), '#') - ->withOnLoadCode(static fn($id): string => " - document.getElementById('$id').addEventListener('click', function() { - const frm = document.getElementById('$mail_form'), - action = new URL(frm.action), - action_params = new URLSearchParams(action.search); - - action_params.delete('cmd'); - action_params.append('cmd', 'searchCoursesTo'); - - action.search = action_params.toString(); - - frm.action = action.href; - frm.submit(); - return false; - }); - "); - $this->toolbar->addComponent($btn); - - $btn = $this->ui_factory->button() - ->standard($this->lng->txt('mail_my_groups'), '#') - ->withOnLoadCode(static fn($id): string => " - document.getElementById('$id').addEventListener('click', function() { - const frm = document.getElementById('$mail_form'), - action = new URL(frm.action), - action_params = new URLSearchParams(action.search); - - action_params.delete('cmd'); - action_params.append('cmd', 'searchGroupsTo'); - - action.search = action_params.toString(); - - frm.action = action.href; - frm.submit(); - return false; - }); - "); - $this->toolbar->addComponent($btn); - - if (count(ilBuddyList::getInstanceByGlobalUser()->getLinkedRelations()) > 0) { - $btn = $this->ui_factory->button() - ->standard($this->lng->txt('mail_my_mailing_lists'), '#') - ->withOnLoadCode(static fn($id): string => " - document.getElementById('$id').addEventListener('click', function() { - const frm = document.getElementById('$mail_form'), - action = new URL(frm.action), - action_params = new URLSearchParams(action.search); - - action_params.delete('cmd'); - action_params.append('cmd', 'searchMailingListsTo'); - - action.search = action_params.toString(); - - frm.action = action.href; - frm.submit(); - return false; - }); - "); - $this->toolbar->addComponent($btn); - } - $data_source_url = $this->ctrl->getLinkTarget($this, 'lookupRecipientAsync', '', true); $inp = new ilTextInputGUI($this->lng->txt('mail_to'), 'rcp_to'); $inp->setMaxLength(null); $inp->setRequired(true); $inp->setSize(50); - $inp->setValue((string) ($mail_data['rcp_to'] ?? '')); + if (isset($mail_data['rcp_to'])) { + $inp->setValue((string) $mail_data['rcp_to']); + } $inp->setDataSource($data_source_url, ','); $form_gui->addItem($inp); $inp = new ilTextInputGUI($this->lng->txt('mail_cc'), 'rcp_cc'); $inp->setMaxLength(null); $inp->setSize(50); - $inp->setValue((string) ($mail_data['rcp_cc'] ?? '')); + if (isset($mail_data['rcp_cc'])) { + $inp->setValue((string) $mail_data['rcp_cc']); + } $inp->setDataSource($data_source_url, ','); $form_gui->addItem($inp); $inp = new ilTextInputGUI($this->lng->txt('mail_bcc'), 'rcp_bcc'); $inp->setMaxLength(null); $inp->setSize(50); - $inp->setValue($mail_data['rcp_bcc'] ?? ''); + if (isset($mail_data['rcp_to'])) { + $inp->setValue((string) $mail_data['rcp_bcc']); + } $inp->setDataSource($data_source_url, ','); $form_gui->addItem($inp); $inp = new ilTextInputGUI($this->lng->txt('subject'), 'm_subject'); $inp->setSize(50); $inp->setRequired(true); - $inp->setValue((string) ($mail_data['m_subject'] ?? '')); + if (isset($mail_data['m_subject'])) { + $inp->setValue((string) $mail_data['m_subject']); + } $form_gui->addItem($inp); $att = new ilMailFormAttachmentPropertyGUI( $this->lng->txt( - isset($mail_data['attachments']) && is_array($mail_data['attachments']) ? - 'edit' : - 'add' + isset($mail_data['attachments']) && is_array($mail_data['attachments']) && $mail_data['attachments'] !== [] ? + 'edit' : + 'add' ), 'm_attachment' ); - if (isset($mail_data['attachments']) && is_array($mail_data['attachments'])) { + if (isset($mail_data['attachments']) && is_array($mail_data['attachments']) && $mail_data['attachments'] !== []) { foreach ($mail_data['attachments'] as $data) { if (is_file($this->mfile->getMailPath() . '/' . $this->user->getId() . '_' . $data)) { $hidden = new ilHiddenInputGUI('attachments[]'); + $hidden->setValue(urlencode((string) $data)); $form_gui->addItem($hidden); $size = filesize($this->mfile->getMailPath() . '/' . $this->user->getId() . '_' . $data); $label = $data . ' [' . ilUtil::formatSize($size) . ']'; $att->addItem($label); - $hidden->setValue(urlencode((string) $data)); } } } @@ -866,10 +945,12 @@ public function showForm(): void if (!isset($mail_data['template_id']) && $template->isDefault()) { $template_chb->setValue((string) $template->getTplId()); - $form_gui->getItemByPostVar('m_subject')->setValue($template->getSubject()); - $mail_data['m_message'] = $template->getMessage() . $this->umail->appendSignature( - $mail_data['m_message'] - ); + if (isset($mail_data['m_message'])) { + $form_gui->getItemByPostVar('m_subject')->setValue($template->getSubject()); + $mail_data['m_message'] = $template->getMessage() . $this->umail->appendSignature( + $mail_data['m_message'] + ); + } } } if (isset($mail_data['template_id'])) { @@ -882,21 +963,48 @@ public function showForm(): void $form_gui->addItem($template_chb); } } catch (Exception) { - ilLoggerFactory::getLogger('mail')->error(sprintf( - '%s has been called with invalid context id: %s.', - __METHOD__, - $context_id - )); + ilLoggerFactory::getLogger('mail')->error( + sprintf( + '%s has been called with invalid context id: %s.', + __METHOD__, + $context_id + ) + ); } } $inp = new ilTextAreaInputGUI($this->lng->txt('message_content'), 'm_message'); - $inp->setValue((string) ($mail_data['m_message'] ?? '')); + if (isset($mail_data['m_message'])) { + $inp->setValue((string) ($mail_data['m_message'])); + } $inp->setRequired(false); $inp->setCols(60); $inp->setRows(10); $form_gui->addItem($inp); + if (isset($mail_data['send_time'])) { + $current_time = new ilDateTime(time(), IL_CAL_UNIX, 'UTC'); + $current_time->switchTimeZone($this->user->getTimezone()); + $termination_time = (new ilDateTime((string) ($mail_data['send_time']), IL_CAL_DATETIME, 'UTC')); + $termination_time->switchTimeZone($this->user->getTimeZone()); + } + + $termination = new ilCheckboxInputGUI($this->lng->txt('mail_message_termination'), 'use_termination'); + $termination->setValue('1'); + $termination->setChecked(isset($current_time, $termination_time) && $termination_time->getUnixTime() >= $current_time->getUnixTime()); + + $termination_date = new ilDateTimeInputGUI('', 'm_termination'); + $termination_date->setInfo($this->lng->txt('mail_message_termination_info')); + $termination_date->setShowTime(true); + if (isset($termination_time, $current_time)) { + $termination_date->setDate( + $termination_time->getUnixTime() >= $current_time->getUnixTime() ? $termination_time : $current_time + ); + } + + $termination->addSubItem($termination_date); + $form_gui->addItem($termination); + $chb = new ilCheckboxInputGUI( $this->lng->txt('mail_serial_letter_placeholders'), 'use_placeholders' @@ -927,12 +1035,7 @@ public function showForm(): void $form_gui->addCommandButton('cancelMail', $this->lng->txt('cancel')); } - $this->tpl->parseCurrentBlock(); - - $this->tpl->setVariable('FORM', $form_gui->getHTML()); - - $this->tpl->addJavaScript('assets/js/ilMailComposeFunctions.js'); - $this->tpl->printToStdout(); + return $form_gui; } public function lookupRecipientAsync(): void diff --git a/components/ILIAS/Mail/classes/class.ilMailbox.php b/components/ILIAS/Mail/classes/class.ilMailbox.php index 5d0c88d60235..aad9ad4cdb50 100755 --- a/components/ILIAS/Mail/classes/class.ilMailbox.php +++ b/components/ILIAS/Mail/classes/class.ilMailbox.php @@ -27,13 +27,22 @@ class ilMailbox private readonly ilDBInterface $db; private readonly ilTree $mtree; - /** @var array{b_inbox: string, c_trash: string, d_drafts: string, e_sent: string, z_local : string} */ + /** @var array{ + * b_inbox: string, + * c_trash: string, + * d_drafts: string, + * e_sent: string, + * z_local : string, + * o_outbox: string + * } + */ private array $default_folders = [ 'b_inbox' => 'inbox', 'c_trash' => 'trash', 'd_drafts' => 'drafts', 'e_sent' => 'sent', 'z_local' => 'local', + 'o_outbox' => 'outbox', ]; private readonly string $table_mail_obj_data; private readonly string $table_tree; @@ -96,6 +105,19 @@ public function getDraftsFolder(): int return (int) $row['obj_id']; } + public function getOutboxFolder(): int + { + $res = $this->db->queryF( + 'SELECT obj_id FROM ' . $this->table_mail_obj_data . ' WHERE user_id = %s AND m_type = %s', + ['integer', 'text'], + [$this->usr_id, 'outbox'] + ); + + $row = $this->db->fetchAssoc($res); + + return (int) $row['obj_id']; + } + public function getTrashFolder(): int { $res = $this->db->queryF( diff --git a/components/ILIAS/Mail/service.xml b/components/ILIAS/Mail/service.xml index c1919cdeb647..c7b8b4dfb355 100755 --- a/components/ILIAS/Mail/service.xml +++ b/components/ILIAS/Mail/service.xml @@ -13,6 +13,7 @@ + diff --git a/components/ILIAS/Mail/tests/ilMailTest.php b/components/ILIAS/Mail/tests/ilMailTest.php index d7af35d53e83..a57ed3774043 100755 --- a/components/ILIAS/Mail/tests/ilMailTest.php +++ b/components/ILIAS/Mail/tests/ilMailTest.php @@ -434,6 +434,10 @@ public function testGetNewDraftId(): void public function testUpdateDraft(): void { + $send_time = '2022-01-01 00:00:00'; + $date_time = $this->getMockBuilder(ilDateTime::class)->disableOriginalConstructor()->getMock(); + $date_time->expects($this->once())->method('get')->with(IL_CAL_DATETIME)->willReturn($send_time); + $folder_id = 7890; $instance = $this->create(); $to = 'abc'; @@ -449,7 +453,7 @@ public function testUpdateDraft(): void $this->mock_database->expects($this->once())->method('update')->with('mail', [ 'folder_id' => ['integer', $folder_id], 'attachments' => ['clob', serialize([])], - 'send_time' => ['timestamp', date('Y-m-d H:i:s')], + 'send_time' => ['timestamp', $send_time], 'rcp_to' => ['clob', $to], 'rcp_cc' => ['clob', $cc], 'rcp_bcc' => ['clob', $bcc], @@ -463,7 +467,23 @@ public function testUpdateDraft(): void 'mail_id' => ['integer', $draft_id], ]); - $this->assertSame($draft_id, $instance->updateDraft($folder_id, [], $to, $cc, $bcc, $subject, $message, $draft_id, $use_placeholders, $context_id, $params)); + $this->assertSame( + $draft_id, + $instance->updateDraft( + $folder_id, + [], + $to, + $cc, + $bcc, + $subject, + $message, + $draft_id, + $date_time, + $use_placeholders, + $context_id, + $params + ) + ); } public function testPersistingToStage(): void diff --git a/components/ILIAS/UI/resources/images/standard/icon_outbox.svg b/components/ILIAS/UI/resources/images/standard/icon_outbox.svg new file mode 100644 index 000000000000..17dcc1bfeb85 --- /dev/null +++ b/components/ILIAS/UI/resources/images/standard/icon_outbox.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 5ed4fb61c35c..8fa10245e8b8 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -4555,6 +4555,8 @@ common#:#mail_at_the_ilias_installation#:#Auf der ILIAS-Installation %2$s haben common#:#mail_attachment#:#Mailanhang common#:#mail_b_inbox#:#Posteingang common#:#mail_c_trash#:#Papierkorb +common#:#mail_cron_terminated_mails#:#Versende terminierte Mails +common#:#mail_cron_terminated_mails_desc#:#... common#:#mail_d_drafts#:#Entwürfe common#:#mail_delete_error#:#Fehler beim Löschen common#:#mail_e_sent#:#Gesendete @@ -4565,7 +4567,10 @@ common#:#mail_mails_of#:#Mail common#:#mail_maxsize_attach#:#Maximale Größe des Mail-Anhangs common#:#mail_member#:#Mail an Mitglied common#:#mail_members#:#Mail an Mitglieder +common#:#mail_message_termination#:#Mail terminieren +common#:#mail_message_termination_info#:#terminierte Mails werden bis zum gewählten Zeitpunkt im Postausgang verbleiben. common#:#mail_not_sent#:#Ihre Mail konnte nicht verschickt werden +common#:#mail_o_outbox#:#Postausgang common#:#mail_search_no#:#Es wurden keine passenden Ergebnisse gefunden common#:#mail_select_one#:#Sie müssen mindestens eine Mail auswählen common#:#mail_send_error#:#Fehler beim Verschicken der Mail diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 0e35906b5e7b..be4a31d0952a 100755 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -4555,6 +4555,8 @@ common#:#mail_at_the_ilias_installation#:#You received %1$s new mail at the ILIA common#:#mail_attachment#:#Mail attachment common#:#mail_b_inbox#:#Inbox common#:#mail_c_trash#:#Trash +common#:#mail_cron_terminated_mails#:#Send scheduled mails +common#:#mail_cron_terminated_mails_desc#:#... common#:#mail_d_drafts#:#Drafts common#:#mail_delete_error#:#Error while deleting common#:#mail_e_sent#:#Sent @@ -4565,7 +4567,10 @@ common#:#mail_mails_of#:#Mail common#:#mail_maxsize_attach#:#Max. attachment size common#:#mail_member#:#Mail to Member common#:#mail_members#:#Mail to Members +common#:#mail_message_termination#:#Schedule mail +common#:#mail_message_termination_info#:#Scheduled mails will remain in the outbox until the selected time. common#:#mail_not_sent#:#Mail not sent! +common#:#mail_o_outbox#:#Outbox common#:#mail_search_no#:#No entries found. common#:#mail_select_one#:#You must select one mail common#:#mail_send_error#:#Error sending mail