From 40c844dd0890e770fdce02fc5b95a043f2998c4a Mon Sep 17 00:00:00 2001 From: Fabian Helfer Date: Thu, 18 Jun 2026 11:21:07 +0200 Subject: [PATCH 1/4] [Feature] Mail: IRSS --- .../Attachments/FileDataRCHandling.php | 102 ++--- .../classes/Attachments/MailAttachments.php | 157 +++++++ .../class.ilMailDeliveryJob.php | 3 +- .../class.ilMassMailDeliveryJob.php | 3 +- .../MailDeletionHandler.php | 53 ++- .../Folder/OutboxDatabaseRepository.php | 3 +- .../ILIAS/Mail/classes/MailDeliveryData.php | 6 +- .../Mail/classes/Message/MailBoxQuery.php | 14 +- .../Mail/classes/Message/MailRecordData.php | 13 +- .../classes/Setup/MailDBUpdateSteps11.php | 17 + .../MigrateMailAttachmentsToIRSS.php | 179 ++++++++ .../classes/Setup/class.ilMailSetupAgent.php | 4 +- .../Mail/classes/class.ilFileDataMail.php | 423 +++++++++++++++++- .../ILIAS/Mail/classes/class.ilMail.php | 155 ++++--- .../classes/class.ilMailAttachmentGUI.php | 95 ++-- .../class.ilMailAttachmentStakeholder.php | 37 +- .../Mail/classes/class.ilMailFolderGUI.php | 65 ++- .../Mail/classes/class.ilMailFormGUI.php | 90 ++-- .../Mail/classes/class.ilMailNotification.php | 4 +- .../ILIAS/Mail/classes/class.ilObjMailGUI.php | 3 +- .../ILIAS/Mail/tests/MailAttachmentsTest.php | 59 +++ components/ILIAS/Mail/tests/ilMailTest.php | 35 +- 22 files changed, 1248 insertions(+), 272 deletions(-) create mode 100644 components/ILIAS/Mail/classes/Attachments/MailAttachments.php create mode 100644 components/ILIAS/Mail/classes/Setup/Migration/MigrateMailAttachmentsToIRSS.php create mode 100644 components/ILIAS/Mail/tests/MailAttachmentsTest.php diff --git a/components/ILIAS/Mail/classes/Attachments/FileDataRCHandling.php b/components/ILIAS/Mail/classes/Attachments/FileDataRCHandling.php index 8e5d242d0b73..581e6440291a 100644 --- a/components/ILIAS/Mail/classes/Attachments/FileDataRCHandling.php +++ b/components/ILIAS/Mail/classes/Attachments/FileDataRCHandling.php @@ -18,9 +18,8 @@ declare(strict_types=1); -use ILIAS\Filesystem\Stream\Streams; +use ILIAS\Mail\Attachments\MailAttachments; use ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification; -use ILIAS\ResourceStorage\Collection\ResourceCollection; trait FileDataRCHandling { @@ -28,36 +27,14 @@ trait FileDataRCHandling * @param list $path_to_files */ protected function getCurrentCollection( - array $path_to_files, - ilMailAttachmentStakeholder $stakeholder + array $path_to_files ): \ILIAS\ResourceStorage\Collection\ResourceCollection { - set_error_handler(static function ($severity, $message, $file, $line): void { - throw new ErrorException($message, $severity, 0, $file, $line); - }); - - try { - global $DIC; - $file_system = $DIC->filesystem()->storage(); - $rcid = $this->storage->collection()->id(); - $collection = $this->storage->collection()->get($rcid); - foreach ($path_to_files as $path_to_file) { - $base_dir = (new \ILIAS\FileDelivery\Setup\BaseDirObjective())::get(); - $path_to_file = str_replace($base_dir, '/', $path_to_file); - $rid = $this->storage->manage()->stream( - $file_system->readStream($path_to_file), - $stakeholder, - md5(basename($path_to_file)) - ); - $collection->add($rid); - } - $this->storage->collection()->store($collection); - } catch (Exception $e) { - throw new Exception("Storing file into collection failed: " . $e->getMessage()); - } finally { - restore_error_handler(); + $rcid = $this->fdm->createCollectionFromPaths($path_to_files); + if ($rcid === null) { + throw new Exception('Storing file into collection failed: no files found'); } - return $collection; + return $this->fdm->getCollection($rcid); } /** @@ -66,33 +43,26 @@ protected function getCurrentCollection( */ public function filesFromLegacyToIRSS(array $mail_data): array { - $files = []; + $attachments = $mail_data['attachments'] ?? null; + if (!$attachments instanceof MailAttachments || !$attachments->isLegacy()) { + throw new InvalidArgumentException('Legacy mail attachments expected.'); + } + $path_to_files = []; - foreach ($mail_data['attachments'] as $file) { + foreach ($attachments->legacyFilenames() as $file) { $path_to_files[] = $this->fdm->getAbsoluteAttachmentPoolPathByFilename($file); } - $collection = $this->getCurrentCollection($path_to_files, new ilMailAttachmentStakeholder()); - foreach ($collection->getResourceIdentifications() as $rcid) { - $files[] = $rcid->serialize(); - } + $collection = $this->getCurrentCollection($path_to_files); - return $files; + return $this->fdm->getRidsFromCollection($collection->getIdentification()); } /** - * @param array $mail_data + * @param list $filenames */ - public function getIdforCollection(array $mail_data): ?ResourceCollectionIdentification + public function getIdforCollection(array $filenames): ?ResourceCollectionIdentification { - $files = []; - $path_to_files = []; - foreach ($mail_data as $attachment) { - $path_to_files[] = $this->fdm->getAbsoluteAttachmentPoolPathByFilename($attachment); - } - $collection = $this->getCurrentCollection($path_to_files, new ilMailAttachmentStakeholder()); - $rcid = $collection->getIdentification(); - - return $rcid; + return $this->fdm->createCollectionFromPoolFilenames($filenames); } /** @@ -100,39 +70,31 @@ public function getIdforCollection(array $mail_data): ?ResourceCollectionIdentif */ public function FilesFromIRSSToLegacy(ResourceCollectionIdentification $identification): array { - $files = []; - $collection = $this->storage->collection()->get($identification); - $all_ids = $collection->getResourceIdentifications(); - foreach ($all_ids as $id) { - $files[] = $id->serialize(); - } - - return $files; + return $this->fdm->getRidsFromCollection($identification); } /** * @param array $attachments - * @return list */ - protected function handleAttachments(array $attachments): array + protected function handleAttachments(array $attachments): ResourceCollectionIdentification { - $files = []; + $resource_identifications = []; foreach ($attachments as $attachment) { $info = $this->upload_handler->getInfoResult($attachment); - if ($info->getFileIdentifier() !== 'unknown') { - $src = $this->upload_handler->getStreamConsumer($attachment); - $stored = $this->fdm->storeAsAttachment( - $info->getName(), - (string) $src->getStream() - ); - if ($stored === false) { - throw new Exception("File '" . $info->getName() . "' could not be stored"); - } - $files[] = ilFileUtils::_sanitizeFilemame($info->getName()); - $this->upload_handler->removeFileForIdentifier($attachment); + if ($info->getFileIdentifier() === 'unknown') { + continue; } + $found = $this->storage->manage()->find($attachment); + if ($found === null) { + throw new Exception("File '" . $info->getName() . "' could not be found in IRSS"); + } + $resource_identifications[] = $found; + } + + if ($resource_identifications === []) { + throw new Exception('No attachments could be stored'); } - return $files; + return $this->fdm->createCollectionFromResourceIdentifications($resource_identifications); } } diff --git a/components/ILIAS/Mail/classes/Attachments/MailAttachments.php b/components/ILIAS/Mail/classes/Attachments/MailAttachments.php new file mode 100644 index 000000000000..8499fd933354 --- /dev/null +++ b/components/ILIAS/Mail/classes/Attachments/MailAttachments.php @@ -0,0 +1,157 @@ + */ + private readonly array $legacy_filenames, + ) { + } + + public static function empty(): self + { + return new self(null, []); + } + + public static function fromIrss(ResourceCollectionIdentification $rcid): self + { + return new self($rcid, []); + } + + /** + * @param list $filenames + */ + public static function fromLegacyFilenames(array $filenames): self + { + if ($filenames === []) { + return self::empty(); + } + + return new self(null, array_values($filenames)); + } + + public static function fromDb(?string $raw): ?self + { + if ($raw === null || $raw === '') { + return null; + } + + if (str_contains($raw, 'a:')) { + $unserialized = unserialize($raw, ['allowed_classes' => false]); + if (!is_array($unserialized)) { + return null; + } + + if (isset($unserialized[self::SERIALIZED_RCID_KEY])) { + return self::fromIrss( + new ResourceCollectionIdentification((string) $unserialized[self::SERIALIZED_RCID_KEY]) + ); + } + + return self::fromLegacyFilenames($unserialized); + } + + return self::fromIrss(new ResourceCollectionIdentification($raw)); + } + + public static function fromBackgroundTask(string $serialized): self + { + $parsed = unserialize($serialized, ['allowed_classes' => false]); + if (!is_array($parsed)) { + return self::empty(); + } + + if (isset($parsed[self::SERIALIZED_RCID_KEY])) { + return self::fromIrss( + new ResourceCollectionIdentification((string) $parsed[self::SERIALIZED_RCID_KEY]) + ); + } + + return self::fromLegacyFilenames($parsed); + } + + public function isEmpty(): bool + { + return $this->rcid === null && $this->legacy_filenames === []; + } + + public function isIrss(): bool + { + return $this->rcid !== null; + } + + public function isLegacy(): bool + { + return $this->rcid === null && $this->legacy_filenames !== []; + } + + public function rcid(): ResourceCollectionIdentification + { + if ($this->rcid === null) { + throw new InvalidArgumentException('Mail attachments are not stored in IRSS.'); + } + + return $this->rcid; + } + + /** + * @return list + */ + public function legacyFilenames(): array + { + if ($this->isIrss()) { + throw new InvalidArgumentException('Mail attachments are not legacy filenames.'); + } + + return $this->legacy_filenames; + } + + public function toDb(): string + { + if ($this->isIrss()) { + return $this->rcid->serialize(); + } + + return serialize($this->legacy_filenames); + } + + public function toBackgroundTask(): string + { + if ($this->isIrss()) { + return serialize([self::SERIALIZED_RCID_KEY => $this->rcid->serialize()]); + } + + return serialize($this->legacy_filenames); + } + + public function stageRcidOrNull(): ?ResourceCollectionIdentification + { + return $this->rcid; + } +} diff --git a/components/ILIAS/Mail/classes/BackgroundTask/class.ilMailDeliveryJob.php b/components/ILIAS/Mail/classes/BackgroundTask/class.ilMailDeliveryJob.php index 24f51d6f433d..a6c885983418 100755 --- a/components/ILIAS/Mail/classes/BackgroundTask/class.ilMailDeliveryJob.php +++ b/components/ILIAS/Mail/classes/BackgroundTask/class.ilMailDeliveryJob.php @@ -26,6 +26,7 @@ use ILIAS\BackgroundTasks\Types\SingleType; use ILIAS\BackgroundTasks\Types\Type; use ILIAS\BackgroundTasks\Value; +use ILIAS\Mail\Attachments\MailAttachments; class ilMailDeliveryJob extends AbstractJob { @@ -69,7 +70,7 @@ public function run(array $input, Observer $observer): Value (string) $input[3]->getValue(), // Bcc (string) $input[4]->getValue(), // Subject (string) $input[5]->getValue(), // Message - (array) unserialize($input[6]->getValue(), ['allowed_classes' => false]), // Attachments + MailAttachments::fromBackgroundTask($input[6]->getValue()), // Attachments (bool) $input[7]->getValue() // Use Placeholders ); $mail->sendMail($mail_data); diff --git a/components/ILIAS/Mail/classes/BackgroundTask/class.ilMassMailDeliveryJob.php b/components/ILIAS/Mail/classes/BackgroundTask/class.ilMassMailDeliveryJob.php index d97d5ddbe707..777b298ac8ad 100755 --- a/components/ILIAS/Mail/classes/BackgroundTask/class.ilMassMailDeliveryJob.php +++ b/components/ILIAS/Mail/classes/BackgroundTask/class.ilMassMailDeliveryJob.php @@ -26,6 +26,7 @@ use ILIAS\BackgroundTasks\Types\SingleType; use ILIAS\BackgroundTasks\Types\Type; use ILIAS\BackgroundTasks\Value; +use ILIAS\Mail\Attachments\MailAttachments; class ilMassMailDeliveryJob extends AbstractJob { @@ -73,7 +74,7 @@ public function run(array $input, Observer $observer): Value $recipients_bcc, $value_object->getSubject(), $value_object->getBody(), - $value_object->getAttachments(), + MailAttachments::fromLegacyFilenames($value_object->getAttachments()), $value_object->isUsingPlaceholders() ); $mail->sendMail($mail_data); diff --git a/components/ILIAS/Mail/classes/Cron/ExpiredOrOrphanedMails/MailDeletionHandler.php b/components/ILIAS/Mail/classes/Cron/ExpiredOrOrphanedMails/MailDeletionHandler.php index 8faac45a6718..ee6ecb035326 100755 --- a/components/ILIAS/Mail/classes/Cron/ExpiredOrOrphanedMails/MailDeletionHandler.php +++ b/components/ILIAS/Mail/classes/Cron/ExpiredOrOrphanedMails/MailDeletionHandler.php @@ -31,6 +31,7 @@ use Throwable; use RecursiveIteratorIterator; use RecursiveDirectoryIterator; +use ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification; use SplFileInfo; class MailDeletionHandler @@ -41,6 +42,7 @@ class MailDeletionHandler private readonly ilSetting $settings; private readonly ilLogger $logger; private readonly ilDBStatement $mail_ids_for_path_stmt; + private readonly ilDBStatement $mail_ids_for_rcid_stmt; /** @var callable(string): void|null */ private $delete_directory_callback; @@ -53,7 +55,7 @@ public function __construct( ?ilDBInterface $db = null, ?ilSetting $setting = null, ?ilLogger $logger = null, - ?callable $delete_directory_callback = null + ?callable $delete_directory_callback = null, ) { global $DIC; @@ -66,6 +68,46 @@ public function __construct( 'SELECT COUNT(*) cnt FROM mail_attachment WHERE path = ?', [ilDBConstants::T_TEXT] ); + $this->mail_ids_for_rcid_stmt = $this->db->prepare( + 'SELECT COUNT(*) cnt FROM mail_attachment WHERE rcid = ?', + [ilDBConstants::T_TEXT] + ); + } + + /** + * @return list + */ + private function determineDeletableAttachmentRcids(): array + { + $rcids = []; + $res = $this->db->query( + ' + SELECT rcid, COUNT(mail_id) cnt_mail_ids + FROM mail_attachment + WHERE rcid IS NOT NULL AND rcid != "" AND rcid != "-" + AND ' . $this->db->in( + 'mail_id', + $this->collector->mailIdsToDelete(), + false, + ilDBConstants::T_INTEGER + ) . ' GROUP BY rcid' + ); + + while ($row = $this->db->fetchAssoc($res)) { + $num_usages_total = (int) $this->db->fetchAssoc( + $this->db->execute( + $this->mail_ids_for_rcid_stmt, + [$row['rcid']] + ) + )['cnt']; + $num_usages_within_deleted_mails = (int) $row['cnt_mail_ids']; + + if ($num_usages_within_deleted_mails >= $num_usages_total) { + $rcids[] = $row['rcid']; + } + } + + return $rcids; } /** @@ -122,6 +164,15 @@ private function deleteDirectory(string $directory): void private function deleteAttachments(): void { + $mail_file_data = new ilFileDataMail(); + foreach ($this->determineDeletableAttachmentRcids() as $rcid) { + try { + $mail_file_data->removeCollection(new ResourceCollectionIdentification($rcid)); + } catch (Throwable $e) { + $this->logger->warning($e->getMessage()); + } + } + $attachment_paths = $this->determineDeletableAttachmentPaths(); $i = 0; diff --git a/components/ILIAS/Mail/classes/Folder/OutboxDatabaseRepository.php b/components/ILIAS/Mail/classes/Folder/OutboxDatabaseRepository.php index 0fed31891775..64f5aca1cadf 100644 --- a/components/ILIAS/Mail/classes/Folder/OutboxDatabaseRepository.php +++ b/components/ILIAS/Mail/classes/Folder/OutboxDatabaseRepository.php @@ -20,6 +20,7 @@ namespace ILIAS\Mail\Folder; +use ILIAS\Mail\Attachments\MailAttachments; use Generator; use DateTimeZone; use ilDBConstants; @@ -78,7 +79,7 @@ public function getOutboxMails(): Generator $row['rcp_bcc'], $row['m_subject'], $row['m_message'], - $row['attachments'], + $row['attachments'] ?? MailAttachments::empty(), (bool) ($row['use_placeholders'] ?? false), isset($row['mail_id']) ? (int) $row['mail_id'] : null ); diff --git a/components/ILIAS/Mail/classes/MailDeliveryData.php b/components/ILIAS/Mail/classes/MailDeliveryData.php index 26c42c8eb2a1..5c410bb90d0c 100755 --- a/components/ILIAS/Mail/classes/MailDeliveryData.php +++ b/components/ILIAS/Mail/classes/MailDeliveryData.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Mail\Attachments\MailAttachments; + final class MailDeliveryData { public function __construct( @@ -26,7 +28,7 @@ public function __construct( private readonly string $bcc, private readonly string $subject, private readonly string $message, - private readonly array $attachments, + private readonly MailAttachments $attachments, private readonly bool $use_placeholder, private ?int $internal_mail_id = null ) { @@ -57,7 +59,7 @@ public function getMessage(): string return $this->message; } - public function getAttachments(): array + public function getAttachments(): MailAttachments { return $this->attachments; } diff --git a/components/ILIAS/Mail/classes/Message/MailBoxQuery.php b/components/ILIAS/Mail/classes/Message/MailBoxQuery.php index b8a8ba9feeb5..6f4244bb6f50 100755 --- a/components/ILIAS/Mail/classes/Message/MailBoxQuery.php +++ b/components/ILIAS/Mail/classes/Message/MailBoxQuery.php @@ -26,7 +26,7 @@ use DateTimeZone; use ILIAS\Data\Order; use ilUserSearchOptions; -use ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification; +use ILIAS\Mail\Attachments\MailAttachments; class MailBoxQuery { @@ -51,9 +51,8 @@ class MailBoxQuery private MailBoxOrderColumn $order_column = self::DEFAULT_ORDER_COLUMN; private string $order_direction = self::DEFAULT_ORDER_DIRECTION; - public function __construct( - private readonly int $user_id, - ) { + public function __construct(private readonly int $user_id) + { global $DIC; $this->db = $DIC->database(); } @@ -278,11 +277,8 @@ public function query($short): array $set = []; while ($row = $this->db->fetchAssoc($res)) { - if (isset($row['attachments']) && \is_string($row['attachments']) && str_contains($row['attachments'], '{')) { - $unserialized_attachments = unserialize($row['attachments'], ['allowed_classes' => false]); - $row['attachments'] = \is_array($unserialized_attachments) ? $unserialized_attachments : null; - } elseif (isset($row['attachments']) && \is_string($row['attachments']) && $row['attachments'] !== '') { - $row['attachments'] = new ResourceCollectionIdentification($row['attachments']); + if (isset($row['attachments']) && is_string($row['attachments'])) { + $row['attachments'] = MailAttachments::fromDb($row['attachments']); } else { $row['attachments'] = null; } diff --git a/components/ILIAS/Mail/classes/Message/MailRecordData.php b/components/ILIAS/Mail/classes/Message/MailRecordData.php index 53bc3feb3e64..3e27d05a7806 100644 --- a/components/ILIAS/Mail/classes/Message/MailRecordData.php +++ b/components/ILIAS/Mail/classes/Message/MailRecordData.php @@ -21,7 +21,7 @@ namespace ILIAS\Mail\Message; use DateTimeImmutable; -use ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification; +use ILIAS\Mail\Attachments\MailAttachments; class MailRecordData { @@ -29,7 +29,7 @@ class MailRecordData public const string STATUS_UNREAD = 'unread'; /** - * @param null|non-empty-list|ResourceCollectionIdentification $attachments + * @param null|MailAttachments $attachments */ public function __construct( private readonly int $mail_id, @@ -45,7 +45,7 @@ public function __construct( private readonly ?string $rcp_to = null, private readonly ?string $rcp_cc = null, private readonly ?string $rcp_bc = null, - private readonly null|array|ResourceCollectionIdentification $attachments = null, + private readonly ?MailAttachments $attachments = null, private readonly ?string $tpl_ctx_id = null, private readonly ?string $tpl_ctx_params = null ) { @@ -116,10 +116,7 @@ public function getRcpBc(): ?string return $this->rcp_bc; } - /** - * @return null|non-empty-list|ResourceCollectionIdentification - */ - public function getAttachments(): null|array|ResourceCollectionIdentification + public function getAttachments(): ?MailAttachments { return $this->attachments; } @@ -141,7 +138,7 @@ public function isRead(): bool public function hasAttachments(): bool { - return !empty($this->attachments); + return $this->attachments !== null && !$this->attachments->isEmpty(); } public function hasPersonalSender(): bool diff --git a/components/ILIAS/Mail/classes/Setup/MailDBUpdateSteps11.php b/components/ILIAS/Mail/classes/Setup/MailDBUpdateSteps11.php index 5a476ae33cc7..f9154cd088de 100644 --- a/components/ILIAS/Mail/classes/Setup/MailDBUpdateSteps11.php +++ b/components/ILIAS/Mail/classes/Setup/MailDBUpdateSteps11.php @@ -68,4 +68,21 @@ public function step_2(): void ); } } + + public function step_3(): void + { + if (!$this->db->tableColumnExists('mail_attachment', 'rcid')) { + $this->db->addTableColumn( + 'mail_attachment', + 'rcid', + [ + 'type' => ilDBConstants::T_TEXT, + 'length' => 64, + 'notnull' => false, + 'default' => null, + ] + ); + } + } + } diff --git a/components/ILIAS/Mail/classes/Setup/Migration/MigrateMailAttachmentsToIRSS.php b/components/ILIAS/Mail/classes/Setup/Migration/MigrateMailAttachmentsToIRSS.php new file mode 100644 index 000000000000..3423d393021c --- /dev/null +++ b/components/ILIAS/Mail/classes/Setup/Migration/MigrateMailAttachmentsToIRSS.php @@ -0,0 +1,179 @@ +helper = new ilResourceStorageMigrationHelper( + new ilMailAttachmentStakeholder(), + $environment + ); + } + + public function step(Environment $environment): void + { + $db = $this->helper->getDatabase(); + $res = $db->query( + 'SELECT path FROM mail_attachment + WHERE (rcid IS NULL OR rcid = "") + AND path IS NOT NULL AND path != "" + GROUP BY path + LIMIT ' . self::PATHS_PER_STEP + ); + + $mail_path = rtrim($this->helper->getClientDataDir(), '/') . '/mail'; + + while ($row = $db->fetchObject($res)) { + $relative_path = (string) $row->path; + $absolute_path = $mail_path . '/' . $relative_path; + + if (!is_dir($absolute_path)) { + $this->markPathAsSkipped($relative_path); + + continue; + } + + $owner_id = $this->resolveOwnerIdForPath($relative_path); + $rcid = $this->helper->moveFilesOfPathToCollection( + $absolute_path, + $owner_id, + $owner_id + ); + + if ($rcid === null) { + $this->markPathAsSkipped($relative_path); + + continue; + } + + $this->assignRcidToPath($relative_path, $rcid); + $this->updateMailAttachmentFields($relative_path, $rcid); + } + } + + public function getRemainingAmountOfSteps(): int + { + return (int) $this->helper->getDatabase()->fetchObject( + $this->helper->getDatabase()->query( + 'SELECT COUNT(DISTINCT path) cnt FROM mail_attachment + WHERE (rcid IS NULL OR rcid = "") + AND path IS NOT NULL AND path != ""' + ) + )->cnt; + } + + private function resolveOwnerIdForPath(string $relative_path): int + { + $db = $this->helper->getDatabase(); + $res = $db->queryF( + 'SELECT m.sender_id FROM mail_attachment ma + INNER JOIN mail m ON m.mail_id = ma.mail_id + WHERE ma.path = %s + ORDER BY m.send_time ASC + LIMIT 1', + [ilDBConstants::T_TEXT], + [$relative_path] + ); + + $row = $db->fetchObject($res); + if ($row !== null && (int) $row->sender_id > 0) { + return (int) $row->sender_id; + } + + return defined('SYSTEM_USER_ID') ? (int) SYSTEM_USER_ID : 6; + } + + private function assignRcidToPath(string $relative_path, ResourceCollectionIdentification $rcid): void + { + $this->helper->getDatabase()->manipulateF( + 'UPDATE mail_attachment SET rcid = %s WHERE path = %s', + [ilDBConstants::T_TEXT, ilDBConstants::T_TEXT], + [$rcid->serialize(), $relative_path] + ); + } + + private function updateMailAttachmentFields(string $relative_path, ResourceCollectionIdentification $rcid): void + { + $db = $this->helper->getDatabase(); + $res = $db->queryF( + 'SELECT m.mail_id, m.attachments FROM mail_attachment ma + INNER JOIN mail m ON m.mail_id = ma.mail_id + WHERE ma.path = %s', + [ilDBConstants::T_TEXT], + [$relative_path] + ); + + while ($row = $db->fetchObject($res)) { + if (!is_string($row->attachments) || $row->attachments === '') { + continue; + } + if (!str_contains($row->attachments, 'a:')) { + continue; + } + + $db->update( + 'mail', + [ + 'attachments' => [ilDBConstants::T_CLOB, $rcid->serialize()], + ], + [ + 'mail_id' => [ilDBConstants::T_INTEGER, (int) $row->mail_id], + ] + ); + } + } + + private function markPathAsSkipped(string $relative_path): void + { + $this->helper->getDatabase()->manipulateF( + 'UPDATE mail_attachment SET rcid = %s WHERE path = %s', + [ilDBConstants::T_TEXT, ilDBConstants::T_TEXT], + ['-', $relative_path] + ); + } +} diff --git a/components/ILIAS/Mail/classes/Setup/class.ilMailSetupAgent.php b/components/ILIAS/Mail/classes/Setup/class.ilMailSetupAgent.php index c0c184280aa5..45e8337ea07e 100755 --- a/components/ILIAS/Mail/classes/Setup/class.ilMailSetupAgent.php +++ b/components/ILIAS/Mail/classes/Setup/class.ilMailSetupAgent.php @@ -23,6 +23,7 @@ use ILIAS\Setup\ObjectiveCollection; use ILIAS\Mail\Setup\MailDBUpdateSteps11; use ILIAS\Mail\Setup\Migration\MailOutboxMigration; +use ILIAS\Mail\Setup\Migration\MigrateMailAttachmentsToIRSS; class ilMailSetupAgent implements Setup\Agent { @@ -74,7 +75,8 @@ public function getStatusObjective(Setup\Metrics\Storage $storage): Setup\Object public function getMigrations(): array { return [ - new MailOutboxMigration() + new MailOutboxMigration(), + new MigrateMailAttachmentsToIRSS(), ]; } } diff --git a/components/ILIAS/Mail/classes/class.ilFileDataMail.php b/components/ILIAS/Mail/classes/class.ilFileDataMail.php index 2b4f44873b79..83ee27b13d1d 100755 --- a/components/ILIAS/Mail/classes/class.ilFileDataMail.php +++ b/components/ILIAS/Mail/classes/class.ilFileDataMail.php @@ -19,7 +19,14 @@ declare(strict_types=1); use ILIAS\Filesystem\Filesystem; +use ILIAS\ResourceStorage\Services; +use ILIAS\Filesystem\Stream\Streams; use ILIAS\FileUpload\DTO\UploadResult; +use ILIAS\ResourceStorage\Collection\ResourceCollection; +use ILIAS\ResourceStorage\Identification\ResourceIdentification; +use ILIAS\ResourceStorage\Resource\Repository\CollectionDBRepository; +use ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification; +use ILIAS\FileDelivery\Delivery as FileDelivery; class ilFileDataMail extends ilFileData { @@ -29,6 +36,8 @@ class ilFileDataMail extends ilFileData protected Filesystem $storage_directory; protected ilDBInterface $db; protected ILIAS $ilias; + private readonly Services $irss; + private readonly ilMailAttachmentStakeholder $stakeholder; public function __construct(public int $user_id = 0) { @@ -43,23 +52,14 @@ public function __construct(public int $user_id = 0) $this->db = $DIC->database(); $this->tmp_directory = $DIC->filesystem()->temp(); $this->storage_directory = $DIC->filesystem()->storage(); + $this->irss = $DIC->resourceStorage(); + $this->stakeholder = new ilMailAttachmentStakeholder(); + $this->stakeholder->setOwner($this->user_id); $this->checkReadWrite(); $this->initAttachmentMaxUploadSize(); } - public function initDirectory(): bool - { - if (is_writable($this->getPath()) - && mkdir($this->getPath() . '/' . MAILPATH) - && chmod($this->getPath() . '/' . MAILPATH, 0755)) { - $this->mail_path = $this->getPath() . '/' . MAILPATH; - return true; - } - - return false; - } - public function getUploadLimit(): int { return $this->mail_max_upload_file_size; @@ -91,6 +91,22 @@ public function getAbsoluteAttachmentPoolPathPrefix(): string */ public function getAttachmentPathAndFilenameByMd5Hash(string $md5FileHash, int $mail_id): array { + $rcid = $this->getRcidForMail($mail_id); + if ($rcid !== null) { + $resource_identification = $this->getResourceIdByHash($rcid, $md5FileHash); + if ($resource_identification === null) { + throw new OutOfBoundsException(); + } + $info = $this->irss->manage()->getCurrentRevision($resource_identification)->getInformation(); + + return [ + 'path' => '', + 'filename' => $info->getTitle(), + 'rcid' => $rcid, + 'md5' => $md5FileHash, + ]; + } + $res = $this->db->queryF( 'SELECT path FROM mail_attachment WHERE mail_id = %s', ['integer'], @@ -375,6 +391,17 @@ public function checkFilesExist(array $a_files): bool return true; } + public function assignAttachmentsToCollection( + int $mail_id, + ResourceCollectionIdentification $rcid + ): void { + $this->db->manipulateF( + 'INSERT INTO mail_attachment (mail_id, path, rcid) VALUES (%s, %s, %s)', + [ilDBConstants::T_INTEGER, ilDBConstants::T_TEXT, ilDBConstants::T_TEXT], + [$mail_id, '', $rcid->serialize()] + ); + } + public function assignAttachmentsToDirectory(int $a_mail_id, int $a_sent_mail_id): void { $storage = self::getStorage($a_sent_mail_id, $this->user_id); @@ -387,18 +414,56 @@ public function assignAttachmentsToDirectory(int $a_mail_id, int $a_sent_mail_id ); } + public function getRcidForMail(int $mail_id): ?ResourceCollectionIdentification + { + $res = $this->db->queryF( + 'SELECT rcid FROM mail_attachment WHERE mail_id = %s', + ['integer'], + [$mail_id] + ); + + if ($this->db->numRows($res) !== 1) { + return null; + } + + $row = $this->db->fetchAssoc($res); + $rcid = (string) ($row['rcid'] ?? ''); + if ($rcid === '' || $rcid === '-') { + return null; + } + + return new ResourceCollectionIdentification($rcid); + } + + public function countMailsReferencingRcid(string $rcid): int + { + $res = $this->db->queryF( + 'SELECT COUNT(mail_id) cnt FROM mail_attachment WHERE rcid = %s', + ['text'], + [$rcid] + ); + + return (int) $this->db->fetchObject($res)->cnt; + } + public function deassignAttachmentFromDirectory(int $a_mail_id): bool { $res = $this->db->query( - 'SELECT path FROM mail_attachment WHERE mail_id = ' . $this->db->quote($a_mail_id, 'integer') + 'SELECT path, rcid FROM mail_attachment WHERE mail_id = ' . $this->db->quote($a_mail_id, 'integer') ); $path = ''; + $rcid = ''; while ($row = $this->db->fetchObject($res)) { $path = (string) $row->path; + $rcid = (string) ($row->rcid ?? ''); } - if ($path !== '') { + if ($rcid !== '' && $rcid !== '-') { + if ($this->countMailsReferencingRcid($rcid) === 1) { + $this->removeCollection(new ResourceCollectionIdentification($rcid)); + } + } elseif ($path !== '') { $res = $this->db->query( 'SELECT COUNT(mail_id) count_mail_id FROM mail_attachment WHERE path = ' . $this->db->quote($path, 'text') @@ -495,6 +560,7 @@ public function onUserDelete(): void INNER JOIN mail ON mail.mail_id = ma1.mail_id WHERE mail.user_id = %s + AND ma1.path IS NOT NULL AND ma1.path != "" AND (SELECT COUNT(tmp.path) FROM mail_attachment tmp WHERE tmp.path = ma1.path) = 1 '; $res = $this->db->queryF( @@ -522,6 +588,26 @@ public function onUserDelete(): void } } + $rcid_query = ' + SELECT DISTINCT(ma1.rcid) + FROM mail_attachment ma1 + INNER JOIN mail ON mail.mail_id = ma1.mail_id + WHERE mail.user_id = %s + AND ma1.rcid IS NOT NULL AND ma1.rcid != "" AND ma1.rcid != "-" + AND (SELECT COUNT(tmp.rcid) FROM mail_attachment tmp WHERE tmp.rcid = ma1.rcid) = 1 + '; + $rcid_res = $this->db->queryF( + $rcid_query, + ['integer'], + [$this->user_id] + ); + while ($row = $this->db->fetchAssoc($rcid_res)) { + try { + $this->removeCollection(new ResourceCollectionIdentification($row['rcid'])); + } catch (Exception) { + } + } + // Delete each mail attachment rows assigned to a message of the deleted user. $this->db->manipulateF( ' @@ -547,6 +633,12 @@ public function deliverAttachmentsAsZip( array $files = [], bool $is_draft = false ): void { + $rcid = $this->getRcidForMail($mail_id); + if ($rcid !== null && !$is_draft) { + $this->deliverCollectionAsZip($rcid, $basename); + return; + } + $path = ''; if (!$is_draft) { $path = $this->getAttachmentPathByMailId($mail_id); @@ -600,4 +692,307 @@ public function deliverAttachmentsAsZip( ilFileUtils::getValidFilename($download_filename . '.zip') ); } + + public function getStakeholder(): ilMailAttachmentStakeholder + { + return $this->stakeholder; + } + + public function streamFromPath(string $absolute_path, ?string $revision_title = null): ResourceIdentification + { + $stream = Streams::ofResource(fopen($absolute_path, 'rb')); + + return $this->irss->manage()->stream( + $stream, + $this->stakeholder, + $revision_title ?? md5(basename($absolute_path)) + ); + } + + public function uploadToIrss(UploadResult $result): ResourceIdentification + { + return $this->irss->manage()->upload( + $result, + $this->stakeholder, + md5($result->getName()) + ); + } + + /** + * @param list $resource_identifications + */ + public function createCollectionFromResourceIdentifications( + array $resource_identifications + ): ResourceCollectionIdentification { + $rcid = $this->irss->collection()->id(null, $this->user_id); + $collection = $this->irss->collection()->get($rcid); + foreach ($resource_identifications as $resource_identification) { + $collection->add($resource_identification); + } + $this->irss->collection()->store($collection); + + return $collection->getIdentification(); + } + + /** + * @param list $filenames Pool filenames without user prefix + */ + public function createCollectionFromPoolFilenames(array $filenames): ?ResourceCollectionIdentification + { + return $this->adoptPoolFilenamesToCollection(null, $filenames); + } + + /** + * @param list $filenames Pool filenames without user prefix + */ + public function adoptPoolFilenamesToCollection( + ?ResourceCollectionIdentification $rcid, + array $filenames + ): ?ResourceCollectionIdentification { + if ($filenames === []) { + return null; + } + + if ($rcid !== null && $this->irss->collection()->exists($rcid->serialize())) { + $collection_id = $this->irss->collection()->id($rcid->serialize(), $this->user_id); + } else { + $collection_id = $this->irss->collection()->id(null, $this->user_id); + } + + $collection = $this->irss->collection()->get($collection_id); + $added = false; + + foreach ($filenames as $filename) { + $path = $this->getAbsoluteAttachmentPoolPathByFilename($filename); + if (!is_file($path)) { + continue; + } + + $hash = md5(basename($path)); + if ($this->resourceIdByHashInCollection($collection, $hash) !== null) { + continue; + } + + $collection->add($this->streamFromPath($path, $hash)); + $added = true; + } + + if (!$added) { + return null; + } + + $this->irss->collection()->store($collection); + + return $collection->getIdentification(); + } + + public function poolFilenameHash(string $pool_filename): string + { + return md5(basename($this->getAbsoluteAttachmentPoolPathByFilename($pool_filename))); + } + + /** + * @param list $absolute_paths + */ + public function createCollectionFromPaths(array $absolute_paths): ?ResourceCollectionIdentification + { + $resource_identifications = []; + foreach ($absolute_paths as $absolute_path) { + if (!is_file($absolute_path)) { + continue; + } + $resource_identifications[] = $this->streamFromPath($absolute_path); + } + + if ($resource_identifications === []) { + return null; + } + + return $this->createCollectionFromResourceIdentifications($resource_identifications); + } + + public function getCollection(ResourceCollectionIdentification $rcid): ResourceCollection + { + if (!$this->collectionIsKnown($rcid)) { + throw new OutOfBoundsException( + sprintf('Mail attachment collection "%s" does not exist.', $rcid->serialize()) + ); + } + + $this->repairCollectionHeaderIfNeeded($rcid); + + return $this->irss->collection()->get($rcid, $this->user_id); + } + + /** + * @return list + */ + private function getAssignedRidStrings(ResourceCollectionIdentification $rcid): array + { + $res = $this->db->queryF( + 'SELECT ' . CollectionDBRepository::R_IDENTIFICATION . + ' FROM ' . CollectionDBRepository::COLLECTION_ASSIGNMENT_TABLE_NAME . + ' WHERE ' . CollectionDBRepository::C_IDENTIFICATION . ' = %s ORDER BY position ASC', + [ilDBConstants::T_TEXT], + [$rcid->serialize()] + ); + + $rids = []; + while ($row = $this->db->fetchAssoc($res)) { + $rids[] = (string) $row[CollectionDBRepository::R_IDENTIFICATION]; + } + + return $rids; + } + + private function collectionIsKnown(ResourceCollectionIdentification $rcid): bool + { + return $this->irss->collection()->exists($rcid->serialize()) + || $this->getAssignedRidStrings($rcid) !== []; + } + + private function repairCollectionHeaderIfNeeded(ResourceCollectionIdentification $rcid): void + { + if ($this->irss->collection()->exists($rcid->serialize()) + || $this->getAssignedRidStrings($rcid) === []) { + return; + } + + $this->db->replace( + CollectionDBRepository::COLLECTION_TABLE_NAME, + [ + CollectionDBRepository::C_IDENTIFICATION => [ilDBConstants::T_TEXT, $rcid->serialize()], + ], + [ + 'title' => [ilDBConstants::T_TEXT, ''], + 'owner_id' => [ilDBConstants::T_INTEGER, $this->user_id], + ] + ); + } + + public function getResourceIdByHash( + ResourceCollectionIdentification $rcid, + string $hash + ): ?ResourceIdentification { + if (!$this->collectionIsKnown($rcid)) { + return null; + } + + foreach ($this->getAssignedRidStrings($rcid) as $rid) { + $resource_identification = $this->irss->manage()->find($rid); + if ($resource_identification === null) { + continue; + } + + $revision = $this->irss->manage()->getCurrentRevision($resource_identification); + if ($revision->getTitle() === $hash) { + return $resource_identification; + } + } + + return null; + } + + private function resourceIdByHashInCollection(ResourceCollection $collection, string $hash): ?ResourceIdentification + { + foreach ($collection->getResourceIdentifications() as $resource_identification) { + $revision = $this->irss->manage()->getCurrentRevision($resource_identification); + if ($revision->getTitle() === $hash) { + return $resource_identification; + } + } + + return null; + } + + /** + * @return list + */ + public function getRidsFromCollection(ResourceCollectionIdentification $rcid): array + { + $rids = []; + foreach ($this->getAssignedRidStrings($rcid) as $rid) { + if ($this->irss->manage()->find($rid) !== null) { + $rids[] = $rid; + } + } + + return $rids; + } + + /** + * @return array + */ + public function getAttachmentListing(ResourceCollectionIdentification $rcid): array + { + $files = []; + foreach ($this->getAssignedRidStrings($rcid) as $rid) { + $resource_identification = $this->irss->manage()->find($rid); + if ($resource_identification === null) { + continue; + } + + $revision = $this->irss->manage()->getCurrentRevision($resource_identification); + $info = $revision->getInformation(); + $file_title = $info->getTitle(); + $files[$file_title] = [ + 'md5' => $revision->getTitle(), + 'name' => $file_title, + 'size' => $info->getSize(), + 'ctime' => $info->getCreationDate()->format('Y-m-d H:i:s'), + ]; + } + + return $files; + } + + public function deliverFile(ResourceCollectionIdentification $rcid, string $md5_hash): void + { + $resource_identification = $this->getResourceIdByHash($rcid, $md5_hash); + if ($resource_identification === null) { + throw new OutOfBoundsException('mail_error_reading_attachment'); + } + + $this->irss->consume()->download($resource_identification)->run(); + } + + public function deliverCollectionAsZip(ResourceCollectionIdentification $rcid, string $zip_basename): void + { + $zip_filename = FileDelivery::returnASCIIFileName($zip_basename . '.zip'); + $this->irss + ->consume() + ->downloadCollection($rcid, $zip_filename) + ->useRevisionTitlesForFileNames(false) + ->run(); + } + + public function removeCollection(ResourceCollectionIdentification $rcid, bool $ignore_usage = true): void + { + $this->irss->collection()->remove( + $this->irss->collection()->id($rcid->serialize()), + $this->stakeholder, + $ignore_usage + ); + } + + /** + * @return list + */ + public function getPathsForMimeAttachments(ResourceCollectionIdentification $rcid): array + { + $attachments = []; + foreach ($this->getCollection($rcid)->getResourceIdentifications() as $resource_identification) { + $revision = $this->irss->manage()->getCurrentRevision($resource_identification); + $info = $revision->getInformation(); + $stream = $this->irss->consume()->stream($resource_identification); + $temp_path = ilFileUtils::ilTempnam(); + file_put_contents($temp_path, (string) $stream->getStream()); + $attachments[] = [ + 'path' => $temp_path, + 'name' => $info->getTitle(), + ]; + } + + return $attachments; + } } diff --git a/components/ILIAS/Mail/classes/class.ilMail.php b/components/ILIAS/Mail/classes/class.ilMail.php index a3c04849cf89..69ddbd5b8b54 100755 --- a/components/ILIAS/Mail/classes/class.ilMail.php +++ b/components/ILIAS/Mail/classes/class.ilMail.php @@ -24,7 +24,7 @@ use ILIAS\Mail\Recipient; use ILIAS\Mail\Service\MailSignatureService; use ILIAS\Mail\Transformation\Utf8Mb4Sanitizer; -use ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification; +use ILIAS\Mail\Attachments\MailAttachments; use ILIAS\Mail\Folder\MailScheduleData; class ilMail @@ -399,11 +399,8 @@ public function fetchMailData(?array $row): ?array return null; } - if (isset($row['attachments']) && is_string($row['attachments']) && str_contains($row['attachments'], '{')) { - $unserialized_attachments = unserialize($row['attachments'], ['allowed_classes' => false]); - $row['attachments'] = is_array($unserialized_attachments) ? $unserialized_attachments : null; - } elseif (isset($row['attachments']) && is_string($row['attachments']) && $row['attachments'] !== '') { - $row['attachments'] = new ResourceCollectionIdentification($row['attachments']); + if (isset($row['attachments']) && is_string($row['attachments'])) { + $row['attachments'] = MailAttachments::fromDb($row['attachments']); } else { $row['attachments'] = null; } @@ -458,12 +455,9 @@ public function getNewDraftId(int $folder_id): int return $next_id; } - /** - * @param list $a_attachments - */ public function updateDraft( int $a_folder_id, - array $a_attachments, + MailAttachments $a_attachments, string $a_rcp_to, string $a_rcp_cc, string $a_rcp_bcc, @@ -479,7 +473,7 @@ public function updateDraft( $this->table_mail, [ 'folder_id' => ['integer', $a_folder_id], - 'attachments' => ['clob', serialize($a_attachments)], + 'attachments' => [ilDBConstants::T_BLOB, $a_attachments->toDb()], 'send_time' => ['timestamp', date('Y-m-d H:i:s')], 'rcp_to' => ['clob', $a_rcp_to], 'rcp_cc' => ['clob', $a_rcp_cc], @@ -520,7 +514,7 @@ public function scheduledMail( 'user_id' => [ilDBConstants::T_INTEGER, $sender_usr_id], 'folder_id' => [ilDBConstants::T_INTEGER, $folder_id], 'sender_id' => [ilDBConstants::T_INTEGER, $sender_usr_id], - 'attachments' => [ilDBConstants::T_CLOB, serialize($mail_data->getMailDeliveryData()->getAttachments())], + 'attachments' => [ilDBConstants::T_CLOB, $mail_data->getMailDeliveryData()->getAttachments()->toDb()], 'send_time' => [ilDBConstants::T_TIMESTAMP, date('Y-m-d H:i:s')], 'rcp_to' => [ilDBConstants::T_CLOB, $mail_data->getMailDeliveryData()->getTo()], 'rcp_cc' => [ilDBConstants::T_CLOB, $mail_data->getMailDeliveryData()->getCC()], @@ -551,7 +545,7 @@ public function scheduledMail( private function sendInternalMail( int $folder_id, int $sender_usr_id, - array $attachments, + MailAttachments $attachments, string $to, string $cc, string $bcc, @@ -577,7 +571,7 @@ private function sendInternalMail( 'user_id' => ['integer', $usr_id], 'folder_id' => ['integer', $folder_id], 'sender_id' => ['integer', $sender_usr_id], - 'attachments' => ['clob', serialize($attachments)], + 'attachments' => [ilDBConstants::T_BLOB, $attachments->toDb()], 'send_time' => ['timestamp', date('Y-m-d H:i:s')], 'rcp_to' => ['clob', $to], 'rcp_cc' => ['clob', $cc], @@ -820,9 +814,11 @@ private function sendChanneledMails( $this->getMailOptionsByUserId($this->user_id), ); - if ($mail_data->getAttachments() !== []) { - $this->mail_file_data->assignAttachmentsToDirectory($internal_mail_id, $mail_data->getInternalMailId()); - } + $this->assignMailAttachments( + $internal_mail_id, + $mail_data->getAttachments(), + $mail_data->getInternalMailId() + ); } $this->delegateExternalEmails( @@ -833,13 +829,9 @@ private function sendChanneledMails( ); } - /** - * @param list $attachments - * @param array $usr_id_to_external_email_addresses_map - */ private function delegateExternalEmails( string $subject, - array $attachments, + MailAttachments $attachments, string $message, array $usr_id_to_external_email_addresses_map ): void { @@ -973,9 +965,6 @@ private function checkRecipients(string $recipients): array return array_merge(...$errors); } - /** - * @param list $a_attachments - */ public function persistToStage( int $a_user_id, string $a_rcp_to, @@ -983,13 +972,14 @@ public function persistToStage( string $a_rcp_bcc, string $a_m_subject, string $a_m_message, - ?\ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification $a_attachments = null, + ?MailAttachments $a_attachments = null, bool $a_use_placeholders = false, ?string $a_tpl_context_id = null, ?array $a_tpl_ctx_params = [] ): bool { - if (!is_null($a_attachments)) { - $a_attachments = $a_attachments->serialize(); + $attachment_value = null; + if ($a_attachments !== null && $a_attachments->isIrss()) { + $attachment_value = $a_attachments->rcid()->serialize(); } $this->db->replace( $this->table_mail_saved, @@ -997,7 +987,7 @@ public function persistToStage( 'user_id' => ['integer', $this->user_id], ], [ - 'attachments' => ['text', $a_attachments], + 'attachments' => [ilDBConstants::T_TEXT, $attachment_value], 'rcp_to' => ['clob', $a_rcp_to], 'rcp_cc' => ['clob', $a_rcp_cc], 'rcp_bcc' => ['clob', $a_rcp_bcc], @@ -1030,18 +1020,13 @@ public function retrieveFromStage(): array return $this->mail_data; } - /** - * Should be used to enqueue a 'mail'. A validation is executed before, errors are returned - * @param list $a_attachment - * @return list - */ public function enqueue( string $a_rcp_to, string $a_rcp_cc, string $a_rcp_bcc, string $a_m_subject, string $a_m_message, - array $a_attachment, + MailAttachments $a_attachment, bool $a_use_placeholders = false ): array { global $DIC; @@ -1059,8 +1044,11 @@ public function enqueue( ' | Attachments: ' . print_r($a_attachment, true) ); - if ($a_attachment && !$this->mail_file_data->checkFilesExist($a_attachment)) { - return [new ilMailError('mail_attachment_file_not_exist', [implode(', ', $a_attachment)])]; + if ( + $a_attachment->isLegacy() + && !$this->mail_file_data->checkFilesExist($a_attachment->legacyFilenames()) + ) { + return [new ilMailError('mail_attachment_file_not_exist', [implode(', ', $a_attachment->legacyFilenames())])]; } $errors = $this->checkMail($a_rcp_to, $a_rcp_cc, $a_rcp_bcc, $a_m_subject); @@ -1116,7 +1104,7 @@ public function enqueue( $rcp_bcc, $a_m_subject, $a_m_message, - serialize($a_attachment), + $a_attachment->toBackgroundTask(), $a_use_placeholders, $this->getSaveInSentbox(), (string) $this->context_id, @@ -1163,9 +1151,16 @@ public function sendMail( ); $mail_data = $mail_data->withInternalMailId($internal_message_id); - if ($mail_data->getAttachments() !== []) { - $this->mail_file_data->assignAttachmentsToDirectory($internal_message_id, $internal_message_id); - $this->mail_file_data->saveFiles($internal_message_id, $mail_data->getAttachments()); + $this->assignMailAttachments( + $internal_message_id, + $mail_data->getAttachments(), + $internal_message_id + ); + if ($mail_data->getAttachments()->isLegacy()) { + $this->mail_file_data->saveFiles( + $internal_message_id, + $mail_data->getAttachments()->legacyFilenames() + ); } $num_external_email_addresses = $this->getCountRecipients( @@ -1254,11 +1249,8 @@ private function getSubjectSentFolderId(): int return $send_folder_id; } - /** - * @param list $attachment - */ private function saveInSentbox( - array $attachment, + MailAttachments $attachment, string $to, string $cc, string $bcc, @@ -1279,16 +1271,13 @@ private function saveInSentbox( ); } - /** - * @param list $attachments - */ private function sendMimeMail( string $to, string $cc, string $bcc, string $subject, string $message, - array $attachments + MailAttachments $attachments ): void { $mailer = new ilMimeMail(); $mailer->From($this->sender_factory->getSenderByUsrId($this->user_id)); @@ -1317,32 +1306,62 @@ function (string $message): string { $mailer->Bcc($bcc); } - foreach ($attachments as $attachment) { - $mailer->Attach( - $this->mail_file_data->getAbsoluteAttachmentPoolPathByFilename($attachment), - '', - 'inline', - $attachment - ); + if ($attachments->isIrss()) { + $mime_attachments = $this->mail_file_data->getPathsForMimeAttachments($attachments->rcid()); + foreach ($mime_attachments as $attachment) { + $mailer->Attach( + $attachment['path'], + '', + 'inline', + $attachment['name'] + ); + } + } elseif ($attachments->isLegacy()) { + foreach ($attachments->legacyFilenames() as $attachment) { + $mailer->Attach( + $this->mail_file_data->getAbsoluteAttachmentPoolPathByFilename($attachment), + '', + 'inline', + $attachment + ); + } } $mailer->Send(); } - public function saveAttachments(?ResourceCollectionIdentification $attachments): void - { - if (!is_null($attachments)) { - $attachments = $attachments->serialize(); + private function assignMailAttachments( + int $mail_id, + MailAttachments $attachments, + int $sent_mail_id + ): void { + if ($attachments->isEmpty()) { + return; } - $this->db->update( - $this->table_mail_saved, - [ - 'attachments' => ['text', $attachments], - ], - [ - 'user_id' => ['integer', $this->user_id], - ] + if ($attachments->isIrss()) { + $this->mail_file_data->assignAttachmentsToCollection($mail_id, $attachments->rcid()); + return; + } + + $this->mail_file_data->assignAttachmentsToDirectory($mail_id, $sent_mail_id); + } + + public function saveAttachments(?MailAttachments $attachments): void + { + $stage = $this->retrieveFromStage(); + + $this->persistToStage( + $this->user_id, + (string) ($stage['rcp_to'] ?? ''), + (string) ($stage['rcp_cc'] ?? ''), + (string) ($stage['rcp_bcc'] ?? ''), + (string) ($stage['m_subject'] ?? ''), + (string) ($stage['m_message'] ?? ''), + $attachments, + (bool) ($stage['use_placeholders'] ?? false), + $stage['tpl_ctx_id'] ?? null, + (array) ($stage['tpl_ctx_params'] ?? []) ); } diff --git a/components/ILIAS/Mail/classes/class.ilMailAttachmentGUI.php b/components/ILIAS/Mail/classes/class.ilMailAttachmentGUI.php index fcecc2d65943..f382f6cb50d8 100644 --- a/components/ILIAS/Mail/classes/class.ilMailAttachmentGUI.php +++ b/components/ILIAS/Mail/classes/class.ilMailAttachmentGUI.php @@ -19,6 +19,7 @@ declare(strict_types=1); use ILIAS\Refinery\Factory as Refinery; +use ILIAS\Mail\Attachments\MailAttachments; use ILIAS\FileUpload\Handler\AbstractCtrlAwareUploadHandler; use ILIAS\FileUpload\Handler\FileInfoResult; use ILIAS\FileUpload\Handler\BasicHandlerResult; @@ -112,12 +113,6 @@ public function executeCommand(): void private function saveAttachments(): void { - $files = []; - - // Important: Do not check for uploaded files here, - // otherwise it is no more possible to remove files (please ignore bug reports like 10137) - - $size_of_affected_files = 0; $files_of_request = $this->http->wrapper()->query()->retrieve( 'mail_attachments_filename', $this->refinery->byTrying([ @@ -130,19 +125,27 @@ private function saveAttachments(): void $files_of_request = array_map(static fn(array $file): string => $file['name'], $this->fdm->getUserFilesData()); } + $files = []; + $size_of_affected_files = 0; foreach ($files_of_request as $file) { - if (is_file($this->fdm->getMailPath() . '/' . basename($this->user->getId() . '_' . urldecode((string) $file)))) { - $files[] = urldecode((string) $file); - $size_of_affected_files += filesize( - $this->fdm->getMailPath() . '/' . - basename($this->user->getId() . '_' . urldecode((string) $file)) - ); + $decoded = urldecode((string) $file); + $pool_path = $this->fdm->getAbsoluteAttachmentPoolPathByFilename($decoded); + if (!is_file($pool_path)) { + continue; } + + $files[] = $decoded; + $size_of_affected_files += (int) filesize($pool_path); } - if ($files !== [] && - $this->fdm->getAttachmentsTotalSizeLimit() !== null && - $size_of_affected_files > $this->fdm->getAttachmentsTotalSizeLimit()) { + if ($files === []) { + $this->tpl->setOnScreenMessage($this->tpl::MESSAGE_TYPE_INFO, $this->lng->txt('select_one'), true); + $this->showAttachmentsCommand(); + return; + } + + if ($this->fdm->getAttachmentsTotalSizeLimit() !== null + && $size_of_affected_files > $this->fdm->getAttachmentsTotalSizeLimit()) { $this->tpl->setOnScreenMessage( $this->tpl::MESSAGE_TYPE_FAILURE, $this->lng->txt('mail_max_size_attachments_total_error') . ' ' . @@ -152,10 +155,26 @@ private function saveAttachments(): void return; } - $rcid_for_files = $this->getIdforCollection($files); - $this->umail->saveAttachments($rcid_for_files); + try { + $mail_data = $this->umail->retrieveFromStage(); + $stage_attachments = $mail_data['attachments'] ?? null; + $existing_rcid = ($stage_attachments instanceof MailAttachments && $stage_attachments->isIrss()) + ? $stage_attachments->rcid() + : null; + + $rcid = $this->fdm->adoptPoolFilenamesToCollection($existing_rcid, $files); + if ($rcid === null) { + throw new RuntimeException($this->lng->txt('mail_error_reading_attachment')); + } - $this->ctrl->returnToParent($this); + $this->umail->saveAttachments(MailAttachments::fromIrss($rcid)); + } catch (Throwable $e) { + $this->tpl->setOnScreenMessage($this->tpl::MESSAGE_TYPE_FAILURE, $e->getMessage(), true); + $this->showAttachmentsCommand(); + return; + } + + $this->ctrl->redirectByClass(ilMailFormGUI::class, 'returnFromAttachments'); } private function cancelSaveAttachmentsCommand(): void @@ -229,20 +248,11 @@ private function deleteAttachmentsCommand(): void $this->tpl->setOnScreenMessage($this->tpl::MESSAGE_TYPE_SUCCESS, $this->lng->txt('mail_error_delete_file') . ' ' . $error, true); } else { $mail_data = $this->umail->retrieveFromStage(); - if (!is_null($mail_data['attachments'])) { - $files_to_legacy = $this->FilesFromIRSSToLegacy($mail_data['attachments']); - $files = $this->handleAttachments($files_to_legacy); - $rcid = null; - if (is_array($files)) { - foreach ($files as $attachment) { - $tmp = []; - if (!in_array($attachment, $decoded_files, true)) { - $tmp[] = $attachment; - } - $rcid = $this->getIdforCollection($tmp); - } - $this->umail->saveAttachments($rcid); - } + $stage_attachments = $mail_data['attachments'] ?? null; + if ($stage_attachments instanceof MailAttachments && $stage_attachments->isIrss()) { + $files_to_legacy = $this->FilesFromIRSSToLegacy($stage_attachments->rcid()); + $rcid = $this->handleAttachments($files_to_legacy); + $this->umail->saveAttachments(MailAttachments::fromIrss($rcid)); } $this->tpl->setOnScreenMessage($this->tpl::MESSAGE_TYPE_SUCCESS, $this->lng->txt('mail_files_deleted'), true); @@ -288,11 +298,22 @@ private function showAttachmentsCommand(): void } $mail_data = $this->umail->retrieveFromStage(); + $stage_attachments = $mail_data['attachments'] ?? null; + $adopted_pool_hashes = []; + if ($stage_attachments instanceof MailAttachments && $stage_attachments->isIrss()) { + foreach ($this->fdm->getAttachmentListing($stage_attachments->rcid()) as $file) { + $adopted_pool_hashes[$file['md5']] = true; + } + } + $files = $this->fdm->getUserFilesData(); $records = []; $checked_items = []; foreach ($files as $file) { - if (is_array($mail_data['attachments']) && in_array($file['name'], $mail_data['attachments'], true)) { + if ($stage_attachments instanceof MailAttachments && $stage_attachments->isLegacy() + && in_array($file['name'], $stage_attachments->legacyFilenames(), true)) { + $checked_items[] = urlencode($file['name']); + } elseif (isset($adopted_pool_hashes[$this->fdm->poolFilenameHash($file['name'])])) { $checked_items[] = urlencode($file['name']); } @@ -340,6 +361,12 @@ private function handleTableActionsCommand(): void { $query = $this->http->wrapper()->query(); if (!$query->has('mail_attachments_table_action')) { + $this->tpl->setOnScreenMessage( + $this->tpl::MESSAGE_TYPE_FAILURE, + $this->lng->txt('select_one'), + true + ); + $this->showAttachmentsCommand(); return; } @@ -347,7 +374,7 @@ private function handleTableActionsCommand(): void match ($action) { self::TABLE_ACTION_SAVE_ATTACHMENTS => $this->saveAttachments(), self::TABLE_CONFIRM_DELETE_ATTACHMENTS => $this->confirmDeleteAttachments(), - default => $this->ctrl->redirect($this), + default => $this->showAttachmentsCommand(), }; } diff --git a/components/ILIAS/Mail/classes/class.ilMailAttachmentStakeholder.php b/components/ILIAS/Mail/classes/class.ilMailAttachmentStakeholder.php index af9a116d3bab..cac1dd147e8a 100644 --- a/components/ILIAS/Mail/classes/class.ilMailAttachmentStakeholder.php +++ b/components/ILIAS/Mail/classes/class.ilMailAttachmentStakeholder.php @@ -18,14 +18,11 @@ declare(strict_types=1); +use ILIAS\ResourceStorage\Identification\ResourceIdentification; use ILIAS\ResourceStorage\Stakeholder\AbstractResourceStakeholder; class ilMailAttachmentStakeholder extends AbstractResourceStakeholder { - public function __construct() - { - } - public function getId(): string { return 'mail_attachments'; @@ -33,6 +30,36 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return 6; + return $this->default_owner; + } + + public function getOwnerOfResource(ResourceIdentification $identification): int + { + return $this->default_owner; + } + + public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool + { + global $DIC; + + if (!$DIC->isDependencyAvailable('user') || !$DIC->isDependencyAvailable('database')) { + return false; + } + + $user_id = $DIC->user()->getId(); + $db = $DIC->database(); + $rid = $identification->serialize(); + + $res = $db->queryF( + 'SELECT 1 FROM il_resource_rca rca + INNER JOIN mail_attachment ma ON ma.rcid = rca.rcid + INNER JOIN mail m ON m.mail_id = ma.mail_id + WHERE rca.rid = %s AND m.user_id = %s + LIMIT 1', + [ilDBConstants::T_TEXT, ilDBConstants::T_INTEGER], + [$rid, $user_id] + ); + + return $db->numRows($res) > 0; } } diff --git a/components/ILIAS/Mail/classes/class.ilMailFolderGUI.php b/components/ILIAS/Mail/classes/class.ilMailFolderGUI.php index 2eb886d7e37c..c887130ffd4a 100755 --- a/components/ILIAS/Mail/classes/class.ilMailFolderGUI.php +++ b/components/ILIAS/Mail/classes/class.ilMailFolderGUI.php @@ -28,8 +28,8 @@ use ILIAS\Mail\Folder\MailFolderSearch; use ILIAS\Mail\Folder\MailFolderTableUI; use ILIAS\Mail\Folder\MailFolderData; -use ILIAS\Filesystem\Stream\Streams; -use ILIAS\User\Profile\PublicProfileGUI; +use ILIAS\Mail\Attachments\MailAttachments; +use ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification; /** * @ilCtrl_Calls ilMailFolderGUI: ILIAS\User\Profile\PublicProfileGUI @@ -929,12 +929,20 @@ protected function showMail(): void $form->addItem($message); - if ($mail_data['attachments']) { + $mail_attachments = $mail_data['attachments'] ?? null; + if ($mail_attachments instanceof MailAttachments && !$mail_attachments->isEmpty()) { $att = new ilCustomInputGUI($this->lng->txt('attachments') . ':'); + $mail_file_data = new ilFileDataMail($this->user->getId()); $radiog = new ilRadioGroupInputGUI('', 'filename'); - foreach ($mail_data['attachments'] as $file) { - $radiog->addOption(new ilRadioOption($file, md5($file))); + if ($mail_attachments->isIrss()) { + foreach ($mail_file_data->getAttachmentListing($mail_attachments->rcid()) as $file) { + $radiog->addOption(new ilRadioOption($file['name'], $file['md5'])); + } + } else { + foreach ($mail_attachments->legacyFilenames() as $file) { + $radiog->addOption(new ilRadioOption($file, md5($file))); + } } $att->setHtml($radiog->render()); @@ -1167,7 +1175,11 @@ protected function deliverFile(): void $mail_file_data = new ilFileDataMail($this->user->getId()); try { $file = $mail_file_data->getAttachmentPathAndFilenameByMd5Hash($filename, (int) $mail_id); - ilFileDelivery::deliverFileLegacy($file['path'], $file['filename']); + if (isset($file['rcid']) && $file['rcid'] instanceof ResourceCollectionIdentification) { + $mail_file_data->deliverFile($file['rcid'], $file['md5']); + } else { + ilFileDelivery::deliverFileLegacy($file['path'], $file['filename']); + } } catch (OutOfBoundsException $e) { throw new ilMailException('mail_error_reading_attachment', $e->getCode(), $e); } @@ -1186,18 +1198,38 @@ protected function deliverAttachments(): void try { $mail_id = $this->getMailIdsFromRequest()[0] ?? 0; $mail_data = $this->umail->getMail($mail_id); - if ($mail_data === null || [] === (array) $mail_data['attachments']) { + if ($mail_data === null + || !($mail_data['attachments'] instanceof MailAttachments) + || $mail_data['attachments']->isEmpty()) { throw new ilMailException('mail_error_reading_attachment'); } + $attachments = $mail_data['attachments']; $type = $this->http->wrapper()->query()->retrieve( 'type', $this->refinery->byTrying([$this->refinery->kindlyTo()->string(), $this->refinery->always('')]) ); $mail_file_data = new ilFileDataMail($this->user->getId()); - if (count($mail_data['attachments']) === 1) { - $attachment = current($mail_data['attachments']); + + if ($attachments->isIrss()) { + $listing = $mail_file_data->getAttachmentListing($attachments->rcid()); + if (count($listing) === 1) { + $file = reset($listing); + $mail_file_data->deliverFile($attachments->rcid(), $file['md5']); + } else { + $mail_file_data->deliverAttachmentsAsZip( + $mail_data['m_subject'], + $mail_id, + [], + $type === 'draft' + ); + } + return; + } + + if ($attachments->isLegacy() && count($attachments->legacyFilenames()) === 1) { + $attachment = current($attachments->legacyFilenames()); try { if ($type === 'draft') { @@ -1206,25 +1238,30 @@ protected function deliverAttachments(): void } $path_to_file = $mail_file_data->getAbsoluteAttachmentPoolPathByFilename($attachment); $filename = $attachment; + ilFileDelivery::deliverFileLegacy($path_to_file, $filename); } else { $file = $mail_file_data->getAttachmentPathAndFilenameByMd5Hash( md5((string) $attachment), $mail_id ); - $path_to_file = $file['path']; - $filename = $file['filename']; + if (isset($file['rcid']) && $file['rcid'] instanceof ResourceCollectionIdentification) { + $mail_file_data->deliverFile($file['rcid'], $file['md5']); + } else { + ilFileDelivery::deliverFileLegacy($file['path'], $file['filename']); + } } - ilFileDelivery::deliverFileLegacy($path_to_file, $filename); } catch (OutOfBoundsException $e) { throw new ilMailException('mail_error_reading_attachment', $e->getCode(), $e); } - } else { + } elseif ($attachments->isLegacy()) { $mail_file_data->deliverAttachmentsAsZip( $mail_data['m_subject'], $mail_id, - $mail_data['attachments'], + $attachments->legacyFilenames(), $type === 'draft' ); + } else { + throw new ilMailException('mail_error_reading_attachment'); } } catch (Exception $e) { $this->tpl->setOnScreenMessage('failure', $this->lng->txt($e->getMessage()), true); diff --git a/components/ILIAS/Mail/classes/class.ilMailFormGUI.php b/components/ILIAS/Mail/classes/class.ilMailFormGUI.php index 4f9c2878c016..d00bb6ce97d1 100755 --- a/components/ILIAS/Mail/classes/class.ilMailFormGUI.php +++ b/components/ILIAS/Mail/classes/class.ilMailFormGUI.php @@ -34,6 +34,7 @@ use ILIAS\Mail\RecipientSearch\UserSearchEndpointConfigurator; use ILIAS\Data\Clock\ClockFactory; use ILIAS\Data\Factory as DataFactory; +use ILIAS\Mail\Attachments\MailAttachments; use ILIAS\Mail\Folder\MailScheduleData; use ILIAS\UI\URLBuilder; use ILIAS\Data\URI; @@ -69,7 +70,7 @@ class ilMailFormGUI private readonly ilFileDataMail $mfile; private readonly GlobalHttpState $http; private readonly Refinery $refinery; - private ?array $request_attachments = null; + private ?MailAttachments $request_attachments = null; protected ilMailTemplateService $template_service; private readonly ilMailBodyPurifier $purifier; private string $mail_form_type = ''; @@ -229,9 +230,9 @@ protected function decodeAttachmentFiles(array $files): array public function saveMessageToOutbox(array $form_values, Form $form): void { - $files = []; + $attachments = MailAttachments::empty(); if (count($form_values['attachments']) > 0) { - $files = $this->handleAttachments($form_values['attachments']); + $attachments = MailAttachments::fromIrss($this->handleAttachments($form_values['attachments'])); } $rcp_to = ''; @@ -276,7 +277,7 @@ public function saveMessageToOutbox(array $form_values, Form $form): void $rcp_bcc, ilUtil::securePlainString($form_values['m_subject'] ?? $this->lng->txt('mail_no_subject')), $sanitized_message, - $files, + $attachments, $form_values['use_placeholders'], $outbox_id ?? null ), @@ -323,9 +324,9 @@ public function sendMessage(): void return; } - $files = []; + $attachments = MailAttachments::empty(); if (count($value['attachments']) > 0) { - $files = $this->handleAttachments($value['attachments']); + $attachments = MailAttachments::fromIrss($this->handleAttachments($value['attachments'])); } $mailer = $this->umail @@ -355,7 +356,7 @@ public function sendMessage(): void $rcp_bcc, ilUtil::securePlainString($value['m_subject']), (new ilMailBody($value['m_message'], $this->purifier))->getContent(), - $files, + $attachments, $value['use_placeholders'] )) { $mailer->autoresponder()->disableAutoresponder(); @@ -419,9 +420,9 @@ public function saveDraft(): void if ($value['m_subject'] === '') { $value['m_subject'] = $this->lng->txt('mail_no_subject'); } - $files = []; + $attachments = MailAttachments::empty(); if (count($value['attachments']) > 0) { - $files = $this->handleAttachments($value['attachments']); + $attachments = MailAttachments::fromIrss($this->handleAttachments($value['attachments'])); } $draft_folder_id = $this->mbox->getDraftsFolder(); @@ -431,7 +432,7 @@ public function saveDraft(): void $rcp_bcc = !empty($value['rcp_bcc']) ? implode(',', $value['rcp_bcc']) : ''; if ($errors = $this->umail->validateRecipients($rcp_to, $rcp_cc, $rcp_bcc)) { - $this->request_attachments = $files; + $this->request_attachments = $attachments; $this->showSubmissionErrors($errors); $this->showForm($form); return; @@ -446,7 +447,7 @@ public function saveDraft(): void $this->umail->updateDraft( $draft_folder_id, - $files, + $attachments, $rcp_to, $rcp_cc, $rcp_bcc, @@ -711,8 +712,14 @@ public function showForm(?Form $form = null): void ilSession::set('draft', $mail_id); $mail_data = $this->umail->getMail($mail_id); - if (!is_null($mail_data['attachments']) || !empty($mail_data['attachments'])) { - $mail_data['attachments'] = $this->filesFromLegacyToIRSS($mail_data); + if ($mail_data['attachments'] instanceof MailAttachments) { + if ($mail_data['attachments']->isIrss()) { + $mail_data['attachments'] = $this->FilesFromIRSSToLegacy($mail_data['attachments']->rcid()); + } elseif ($mail_data['attachments']->isLegacy()) { + $mail_data['attachments'] = $this->filesFromLegacyToIRSS($mail_data); + } else { + $mail_data['attachments'] = []; + } } ilMailFormCall::setContextId($mail_data['tpl_ctx_id']); @@ -731,15 +738,20 @@ public function showForm(?Form $form = null): void $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( - $mail_data['attachments'], - $mail_id - )) { - $this->tpl->setOnScreenMessage('info', $error); - } - - if (!is_null($mail_data['attachments']) || ($mail_data['attachments'] != '')) { - $mail_data['attachments'] = $this->filesFromLegacyToIRSS($mail_data); + if ($mail_data['attachments'] instanceof MailAttachments) { + if ($mail_data['attachments']->isIrss()) { + $mail_data['attachments'] = $this->FilesFromIRSSToLegacy($mail_data['attachments']->rcid()); + } elseif ($mail_data['attachments']->isLegacy()) { + if ($error = $this->mfile->adoptAttachments( + $mail_data['attachments']->legacyFilenames(), + $mail_id + )) { + $this->tpl->setOnScreenMessage('info', $error); + } + $mail_data['attachments'] = $this->filesFromLegacyToIRSS($mail_data); + } else { + $mail_data['attachments'] = []; + } } break; @@ -841,8 +853,12 @@ public function showForm(?Form $form = null): void } } - if ($this->request_attachments) { - $mail_data['attachments'] = $this->request_attachments; + if ($this->request_attachments instanceof MailAttachments) { + if ($this->request_attachments->isIrss()) { + $mail_data['attachments'] = $this->FilesFromIRSSToLegacy($this->request_attachments->rcid()); + } elseif ($this->request_attachments->isLegacy()) { + $mail_data['attachments'] = $this->request_attachments->legacyFilenames(); + } } break; } @@ -919,10 +935,11 @@ private function saveMailBeforeSearch(?array $input_results = null): void $result = $input_results; } - $resource_collection_id = null; + $stage_attachments = null; if (!empty($result['attachments']->getValue())) { - $files = $this->handleAttachments($result['attachments']->getValue()); - $resource_collection_id = $this->getIdforCollection($files); + $stage_attachments = MailAttachments::fromIrss( + $this->handleAttachments($result['attachments']->getValue()) + ); } $rcp_to = implode(',', $result['rcp_to']->getValue() ?? []); @@ -936,7 +953,7 @@ private function saveMailBeforeSearch(?array $input_results = null): void $rcp_bcc, ilUtil::securePlainString($result['m_subject']->getValue()), ilUtil::securePlainString($result['m_message']->getValue()), - $resource_collection_id, + $stage_attachments, (bool) $result['use_placeholders']->getValue(), ilMailFormCall::getContextId(), ilMailFormCall::getContextParameters() @@ -982,6 +999,7 @@ private function getUserSearchConfigurator(): \ILIAS\User\Search\EndpointConfigu protected function buildFormElements(?array $mail_data): array { + $mail_data ??= []; $ff = $this->ui_factory->input()->field(); $rcp_to = $this->user_search->getInput( @@ -1014,17 +1032,23 @@ protected function buildFormElements(?array $mail_data): array } } - $has_files = !empty($mail_data['attachments']); $attachments = $ff->file( $this->upload_handler, $this->lng->txt('attachments') )->withMaxFiles(10); - if (isset($mail_data['attachments']) && $has_files) { - if ($mail_data['attachments'] instanceof \ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification) { - $mail_data['attachments'] = $this->FilesFromIRSSToLegacy($mail_data['attachments']); + $mail_attachments = $mail_data['attachments'] ?? null; + if ($mail_attachments instanceof MailAttachments && !$mail_attachments->isEmpty()) { + if ($mail_attachments->isIrss()) { + $mail_data['attachments'] = $this->FilesFromIRSSToLegacy($mail_attachments->rcid()); + } elseif ($mail_attachments->isLegacy()) { + $mail_data['attachments'] = $mail_attachments->legacyFilenames(); + } else { + $mail_data['attachments'] = []; } - $attachments = $attachments->withValue($mail_data['attachments'] ?? []); + $attachments = $attachments->withValue($mail_data['attachments']); + } elseif (is_array($mail_attachments) && $mail_attachments !== []) { + $attachments = $attachments->withValue($mail_attachments); } $template_chb = null; diff --git a/components/ILIAS/Mail/classes/class.ilMailNotification.php b/components/ILIAS/Mail/classes/class.ilMailNotification.php index 0b79f5d3313d..a2c9a230148b 100755 --- a/components/ILIAS/Mail/classes/class.ilMailNotification.php +++ b/components/ILIAS/Mail/classes/class.ilMailNotification.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Mail\Attachments\MailAttachments; + abstract class ilMailNotification { final public const int SUBJECT_TITLE_LENGTH = 60; @@ -239,7 +241,7 @@ public function sendMail(array $a_rcp, bool $a_parse_recipients = true): void '', $this->getSubject(), $this->getBody(), - $this->getAttachments() + MailAttachments::fromLegacyFilenames($this->getAttachments()) ); if ($errors !== []) { ilLoggerFactory::getLogger('mail')->dump($errors, ilLogLevel::ERROR); diff --git a/components/ILIAS/Mail/classes/class.ilObjMailGUI.php b/components/ILIAS/Mail/classes/class.ilObjMailGUI.php index 20093d1c0aea..bd29e1722d56 100755 --- a/components/ILIAS/Mail/classes/class.ilObjMailGUI.php +++ b/components/ILIAS/Mail/classes/class.ilObjMailGUI.php @@ -19,6 +19,7 @@ declare(strict_types=1); use ILIAS\Mail\Autoresponder\AutoresponderService; +use ILIAS\Mail\Attachments\MailAttachments; use ILIAS\Mail\Signature\MailUserSignature; use ILIAS\Mail\Signature\Signature; use ILIAS\Mail\Signature\MailInstallationSignature; @@ -388,7 +389,7 @@ protected function sendTestMail(bool $is_manual_mail = false): void '', $this->lng->txt('mail_email_' . $lng_variable_prefix . '_subject'), $this->lng->txt('mail_email_' . $lng_variable_prefix . '_body'), - [] + MailAttachments::empty() ); $this->tpl->setOnScreenMessage('success', $this->lng->txt('mail_external_test_sent')); diff --git a/components/ILIAS/Mail/tests/MailAttachmentsTest.php b/components/ILIAS/Mail/tests/MailAttachmentsTest.php new file mode 100644 index 000000000000..4357cdc8f219 --- /dev/null +++ b/components/ILIAS/Mail/tests/MailAttachmentsTest.php @@ -0,0 +1,59 @@ +serialize()); + + $this->assertInstanceOf(MailAttachments::class, $parsed); + $this->assertTrue($parsed->isIrss()); + $this->assertSame('rcid-123', $parsed->rcid()->serialize()); + } + + public function testParseLegacyArray(): void + { + $parsed = MailAttachments::fromDb(serialize(['file.pdf', 'image.png'])); + + $this->assertTrue($parsed->isLegacy()); + $this->assertSame(['file.pdf', 'image.png'], $parsed->legacyFilenames()); + } + + public function testSerializeRcidForBackgroundTask(): void + { + $attachments = MailAttachments::fromIrss(new ResourceCollectionIdentification('rcid-456')); + $parsed = MailAttachments::fromBackgroundTask($attachments->toBackgroundTask()); + + $this->assertTrue($parsed->isIrss()); + $this->assertSame('rcid-456', $parsed->rcid()->serialize()); + } + + public function testIsEmpty(): void + { + $this->assertTrue(MailAttachments::empty()->isEmpty()); + $this->assertFalse(MailAttachments::fromIrss(new ResourceCollectionIdentification('rcid-789'))->isEmpty()); + } +} diff --git a/components/ILIAS/Mail/tests/ilMailTest.php b/components/ILIAS/Mail/tests/ilMailTest.php index af1b17589623..40e4db495006 100755 --- a/components/ILIAS/Mail/tests/ilMailTest.php +++ b/components/ILIAS/Mail/tests/ilMailTest.php @@ -21,6 +21,7 @@ use ILIAS\Refinery\Factory; use PHPUnit\Framework\MockObject\MockObject; use ILIAS\Mail\Autoresponder\AutoresponderService; +use ILIAS\Mail\Attachments\MailAttachments; use ILIAS\LegalDocuments\Conductor; use ILIAS\Refinery\Transformation; use ILIAS\Data\Result\Ok; @@ -28,6 +29,7 @@ use PHPUnit\Framework\Attributes\DataProvider; use ILIAS\Mail\TemplateEngine\TemplateEngineFactoryInterface; use ILIAS\Mail\TemplateEngine\Mustache\MustacheTemplateEngineFactory; +use ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification; class ilMailTest extends ilMailBaseTestCase { @@ -264,7 +266,7 @@ public function render(string $template, object|array $context): string implode(',', array_slice(array_keys($active_users_login_to_id_map), 5, 2)), 'Subject', 'Message', - [], + MailAttachments::empty(), false ); $mail_service->sendMail($mail_data); @@ -482,7 +484,7 @@ public function testUpdateDraft(): void $draft_id, $instance->updateDraft( $folder_id, - [], + MailAttachments::empty(), $to, $cc, $bcc, @@ -630,17 +632,34 @@ public function testGetIliasMailerName(): void public function testSaveAttachments(): void { $usr_id = 89; - $attachments = new \ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification('657497dc-5079-4f95-b19d-aecdaf81ff1a'); + $attachments = MailAttachments::fromIrss( + new ResourceCollectionIdentification('657497dc-5079-4f95-b19d-aecdaf81ff1a') + ); $instance = $this->create(789, $usr_id); + $mock_statement = $this->getMockBuilder(ilDBStatement::class)->getMock(); - $this->mock_database->expects($this->once())->method('update')->with( + $this->mock_database->expects($this->exactly(2))->method('queryF')->willReturnCallback( + $this->queryCallback($mock_statement, ['integer'], [$usr_id]) + ); + $this->mock_database->expects($this->exactly(2))->method('fetchAssoc')->with($mock_statement)->willReturn([ + 'attachments' => null, + 'rcp_to' => '', + 'rcp_cc' => '', + 'rcp_bcc' => '', + 'm_subject' => '', + 'm_message' => '', + 'use_placeholders' => 0, + 'tpl_ctx_id' => null, + 'tpl_ctx_params' => '[]', + ]); + $this->mock_database->expects($this->once())->method('replace')->with( 'mail_saved', - [ - 'attachments' => ['text', $attachments->serialize()], - ], [ 'user_id' => ['integer', $usr_id], - ] + ], + $this->callback(static function (array $fields) use ($attachments): bool { + return ($fields['attachments'][1] ?? null) === $attachments->rcid()->serialize(); + }) ); $instance->saveAttachments($attachments); From 440186a57018bd0238aa7bd47e4de22575102b12 Mon Sep 17 00:00:00 2001 From: Fabian Helfer Date: Thu, 18 Jun 2026 14:46:06 +0200 Subject: [PATCH 2/4] [Feature] Mail: IRSS MimeMailTransport --- .../Mail/classes/Mime/MailMimeAttachment.php | 95 +++++++++++++++++++ .../class.ilMailMimeTransportBase.php | 48 +++++++++- .../Mail/classes/class.ilFileDataMail.php | 19 ++-- .../ILIAS/Mail/classes/class.ilMail.php | 10 +- .../ILIAS/Mail/classes/class.ilMimeMail.php | 60 +++++++----- 5 files changed, 192 insertions(+), 40 deletions(-) create mode 100644 components/ILIAS/Mail/classes/Mime/MailMimeAttachment.php diff --git a/components/ILIAS/Mail/classes/Mime/MailMimeAttachment.php b/components/ILIAS/Mail/classes/Mime/MailMimeAttachment.php new file mode 100644 index 000000000000..768d8841f49e --- /dev/null +++ b/components/ILIAS/Mail/classes/Mime/MailMimeAttachment.php @@ -0,0 +1,95 @@ +resource_identification !== null; + } + + public function getPath(): ?string + { + return $this->path; + } + + public function getResourceIdentification(): ?ResourceIdentification + { + return $this->resource_identification; + } + + public function getMimeType(): string + { + return $this->mime_type; + } + + public function getDisposition(): string + { + return $this->disposition; + } + + public function getDisplayName(): string + { + return $this->display_name; + } +} diff --git a/components/ILIAS/Mail/classes/Mime/Transport/class.ilMailMimeTransportBase.php b/components/ILIAS/Mail/classes/Mime/Transport/class.ilMailMimeTransportBase.php index 97eab62834c2..0f8e867cab3d 100755 --- a/components/ILIAS/Mail/classes/Mime/Transport/class.ilMailMimeTransportBase.php +++ b/components/ILIAS/Mail/classes/Mime/Transport/class.ilMailMimeTransportBase.php @@ -19,6 +19,8 @@ declare(strict_types=1); use PHPMailer\PHPMailer\PHPMailer; +use ILIAS\Mail\Mime\MailMimeAttachment; +use ILIAS\ResourceStorage\Services; abstract class ilMailMimeTransportBase implements ilMailMimeTransport { @@ -101,8 +103,8 @@ final public function send(ilMimeMail $mail): bool ilLoggerFactory::getLogger('mail')->warning('{error}', ['error' => $this->getMailer()->ErrorInfo]); } - foreach ($mail->getAttachments() as $attachment) { - if (!$this->getMailer()->addAttachment($attachment['path'], $attachment['name'])) { + foreach ($mail->getMimeAttachments() as $attachment) { + if (!$this->addMimeAttachment($attachment)) { ilLoggerFactory::getLogger('mail')->warning('{error}', ['error' => $this->getMailer()->ErrorInfo]); } } @@ -187,4 +189,46 @@ final public function send(ilMimeMail $mail): bool return $result; } + + private function addMimeAttachment(MailMimeAttachment $attachment): bool + { + if ($attachment->isResource()) { + return $this->addResourceAttachment($attachment); + } + + $path = $attachment->getPath(); + if ($path === null) { + return false; + } + + return $this->getMailer()->addAttachment( + $path, + $attachment->getDisplayName(), + PHPMailer::ENCODING_BASE64, + $attachment->getMimeType(), + $attachment->getDisposition() + ); + } + + private function addResourceAttachment(MailMimeAttachment $attachment): bool + { + $resource_identification = $attachment->getResourceIdentification(); + if ($resource_identification === null) { + return false; + } + + /** @var Services $irss */ + $irss = $GLOBALS['DIC']->resourceStorage(); + $stream = $irss->consume()->stream($resource_identification)->getStream(); + + return $this->getMailer()->addStringAttachment( + (string) $stream, + $attachment->getDisplayName() !== '' + ? $attachment->getDisplayName() + : $resource_identification->serialize(), + PHPMailer::ENCODING_BASE64, + $attachment->getMimeType(), + $attachment->getDisposition() + ); + } } diff --git a/components/ILIAS/Mail/classes/class.ilFileDataMail.php b/components/ILIAS/Mail/classes/class.ilFileDataMail.php index 83ee27b13d1d..e1ff17448f61 100755 --- a/components/ILIAS/Mail/classes/class.ilFileDataMail.php +++ b/components/ILIAS/Mail/classes/class.ilFileDataMail.php @@ -976,19 +976,20 @@ public function removeCollection(ResourceCollectionIdentification $rcid, bool $i } /** - * @return list + * @return list */ - public function getPathsForMimeAttachments(ResourceCollectionIdentification $rcid): array + public function getIrssMimeAttachments(ResourceCollectionIdentification $rcid): array { $attachments = []; - foreach ($this->getCollection($rcid)->getResourceIdentifications() as $resource_identification) { - $revision = $this->irss->manage()->getCurrentRevision($resource_identification); - $info = $revision->getInformation(); - $stream = $this->irss->consume()->stream($resource_identification); - $temp_path = ilFileUtils::ilTempnam(); - file_put_contents($temp_path, (string) $stream->getStream()); + foreach ($this->getAssignedRidStrings($rcid) as $rid) { + $resource_identification = $this->irss->manage()->find($rid); + if ($resource_identification === null) { + continue; + } + + $info = $this->irss->manage()->getCurrentRevision($resource_identification)->getInformation(); $attachments[] = [ - 'path' => $temp_path, + 'rid' => $rid, 'name' => $info->getTitle(), ]; } diff --git a/components/ILIAS/Mail/classes/class.ilMail.php b/components/ILIAS/Mail/classes/class.ilMail.php index 69ddbd5b8b54..c6137be204dc 100755 --- a/components/ILIAS/Mail/classes/class.ilMail.php +++ b/components/ILIAS/Mail/classes/class.ilMail.php @@ -26,6 +26,7 @@ use ILIAS\Mail\Transformation\Utf8Mb4Sanitizer; use ILIAS\Mail\Attachments\MailAttachments; use ILIAS\Mail\Folder\MailScheduleData; +use ILIAS\ResourceStorage\Identification\ResourceIdentification; class ilMail { @@ -1307,12 +1308,9 @@ function (string $message): string { } if ($attachments->isIrss()) { - $mime_attachments = $this->mail_file_data->getPathsForMimeAttachments($attachments->rcid()); - foreach ($mime_attachments as $attachment) { - $mailer->Attach( - $attachment['path'], - '', - 'inline', + foreach ($this->mail_file_data->getIrssMimeAttachments($attachments->rcid()) as $attachment) { + $mailer->AttachResource( + new ResourceIdentification($attachment['rid']), $attachment['name'] ); } diff --git a/components/ILIAS/Mail/classes/class.ilMimeMail.php b/components/ILIAS/Mail/classes/class.ilMimeMail.php index 9e80aee45fb7..4c31635c85f0 100755 --- a/components/ILIAS/Mail/classes/class.ilMimeMail.php +++ b/components/ILIAS/Mail/classes/class.ilMimeMail.php @@ -19,7 +19,9 @@ declare(strict_types=1); use ILIAS\Data\Factory; +use ILIAS\Mail\Mime\MailMimeAttachment; use ILIAS\Refinery\Factory as Refinery; +use ILIAS\ResourceStorage\Identification\ResourceIdentification; class ilMimeMail { @@ -47,14 +49,8 @@ class ilMimeMail protected array $abcc = []; /** @var array */ protected array $images = []; - /** @var string[] */ - protected array $aattach = []; - /** @var string[] */ - protected array $actype = []; - /** @var string[] */ - protected array $adispo = []; - /** @var string[] */ - protected array $adisplay = []; + /** @var list */ + protected array $mime_attachments = []; private readonly Refinery $refinery; /** @var Closure(string): string|null */ private ?Closure $to_html_transformation = null; @@ -195,35 +191,53 @@ public function Attach( string $disposition = 'inline', ?string $display_name = null ): void { - if ($file_type === '') { - $file_type = 'application/octet-stream'; - } + $this->mime_attachments[] = MailMimeAttachment::fromPath( + $filename, + $file_type, + $disposition, + $display_name + ); + } - $this->aattach[] = $filename; - $this->actype[] = $file_type; - $this->adispo[] = $disposition; - $this->adisplay[] = $display_name; + public function AttachResource( + ResourceIdentification $resource_identification, + string $display_name, + string $file_type = '', + string $disposition = 'inline' + ): void { + $this->mime_attachments[] = MailMimeAttachment::fromResource( + $resource_identification, + $display_name, + $file_type, + $disposition + ); + } + + /** + * @return list + */ + public function getMimeAttachments(): array + { + return $this->mime_attachments; } /** * @return array{path: string, name: string}[] + * @deprecated Use getMimeAttachments() for path and IRSS attachments. */ public function getAttachments(): array { $attachments = []; - $i = 0; - foreach ($this->aattach as $attachment) { - $name = ''; - if (isset($this->adisplay[$i]) && is_string($this->adisplay[$i]) && $this->adisplay[$i] !== '') { - $name = $this->adisplay[$i]; + foreach ($this->mime_attachments as $attachment) { + if ($attachment->isResource() || $attachment->getPath() === null) { + continue; } $attachments[] = [ - 'path' => $attachment, - 'name' => $name + 'path' => $attachment->getPath(), + 'name' => $attachment->getDisplayName(), ]; - ++$i; } return $attachments; From 98c6708ef9e6883f2920eac7b322c275fe7841ac Mon Sep 17 00:00:00 2001 From: Fabian Helfer Date: Mon, 22 Jun 2026 11:04:43 +0200 Subject: [PATCH 3/4] [Feature] Mail: Fix DBConstants Type failing Unit Tests --- components/ILIAS/Mail/classes/class.ilMail.php | 2 +- components/ILIAS/Mail/tests/ilMailTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ILIAS/Mail/classes/class.ilMail.php b/components/ILIAS/Mail/classes/class.ilMail.php index c6137be204dc..a1fc5e65d09a 100755 --- a/components/ILIAS/Mail/classes/class.ilMail.php +++ b/components/ILIAS/Mail/classes/class.ilMail.php @@ -474,7 +474,7 @@ public function updateDraft( $this->table_mail, [ 'folder_id' => ['integer', $a_folder_id], - 'attachments' => [ilDBConstants::T_BLOB, $a_attachments->toDb()], + 'attachments' => [ilDBConstants::T_CLOB, $a_attachments->toDb()], 'send_time' => ['timestamp', date('Y-m-d H:i:s')], 'rcp_to' => ['clob', $a_rcp_to], 'rcp_cc' => ['clob', $a_rcp_cc], diff --git a/components/ILIAS/Mail/tests/ilMailTest.php b/components/ILIAS/Mail/tests/ilMailTest.php index 40e4db495006..1fd882f0d249 100755 --- a/components/ILIAS/Mail/tests/ilMailTest.php +++ b/components/ILIAS/Mail/tests/ilMailTest.php @@ -463,7 +463,7 @@ public function testUpdateDraft(): void $this->mock_database->expects($this->once())->method('update')->with('mail', [ 'folder_id' => ['integer', $folder_id], - 'attachments' => ['clob', serialize([])], + 'attachments' => [ilDBConstants::T_CLOB, serialize([])], 'send_time' => ['timestamp', date('Y-m-d H:i:s')], 'rcp_to' => ['clob', $to], 'rcp_cc' => ['clob', $cc], From 6ca5738f30607375fd146ea0f8b9487efad4769a Mon Sep 17 00:00:00 2001 From: Fabian Helfer Date: Mon, 6 Jul 2026 11:02:05 +0200 Subject: [PATCH 4/4] [Feature] Mail: IRSS Migrate Component MailAttachement APIs --- .../Badge/classes/class.ilBadgeHandler.php | 3 +- .../class.ilCalendarMailNotification.php | 12 ++- .../classes/class.ilTimingsCronReminder.php | 3 +- .../classes/Notification/Notification.php | 14 +-- .../class.ilExAssignmentReminder.php | 4 +- .../Exercise/classes/class.ilObjExercise.php | 23 ++--- ...ss.ilForumCronNotificationDataProvider.php | 39 ++++++-- ...ass.ilForumMailEventNotificationSender.php | 6 +- .../class.ilForumNotificationDataProvider.php | 39 ++++++-- .../classes/class.ilForumMailNotification.php | 2 +- .../interface.ilForumNotificationMailData.php | 4 + .../class.ilLearningModuleNotification.php | 4 +- .../Attachments/FileDataRCHandling.php | 84 ++++++++++++++++- .../class.ilMassMailDeliveryJob.php | 2 +- .../MailDeletionHandler.php | 1 + .../Object/class.ilMailValueObject.php | 15 ++- .../class.ilMailValueObjectJsonService.php | 19 +++- .../Mail/classes/class.ilAccountMail.php | 15 ++- .../Mail/classes/class.ilFileDataMail.php | 78 ++++++++++++++++ .../classes/class.ilMailAttachmentGUI.php | 5 +- .../Mail/classes/class.ilMailFormGUI.php | 93 ++++++++++++------- .../Mail/classes/class.ilMailNotification.php | 11 ++- .../Mail/tests/ilMailTaskProcessorTest.php | 19 ++-- .../Cron/class.ilMembershipCronMinMembers.php | 3 +- .../class.ilMembershipCronNotifications.php | 3 +- .../class.NotificationsManager.php | 4 +- .../classes/ilNotificationMailHandler.php | 3 +- .../classes/class.ilSkillNotifications.php | 3 +- .../classes/Mail/class.ilPRGMail.php | 3 +- .../class.ilSurveyParticipantsGUI.php | 3 +- .../Participants/class.ilSurveyRaterGUI.php | 4 +- .../Survey/classes/class.ilObjSurvey.php | 15 +-- .../classes/class.ilTestMailNotification.php | 12 ++- .../Cron/class.ilUserCronCheckAccounts.php | 3 +- .../User/src/Settings/NewAccountMail/Mail.php | 1 + .../classes/class.ilECSAppEventListener.php | 4 +- .../ILIAS/Wiki/classes/class.ilWikiUtil.php | 6 +- 37 files changed, 423 insertions(+), 139 deletions(-) diff --git a/components/ILIAS/Badge/classes/class.ilBadgeHandler.php b/components/ILIAS/Badge/classes/class.ilBadgeHandler.php index 1381a4e2c0f1..f4886786bcd1 100755 --- a/components/ILIAS/Badge/classes/class.ilBadgeHandler.php +++ b/components/ILIAS/Badge/classes/class.ilBadgeHandler.php @@ -21,6 +21,7 @@ use ILIAS\Notifications\Model\ilNotificationLink; use ILIAS\Notifications\Model\ilNotificationParameter; use ILIAS\Badge\GlobalScreen\BadgeNotificationProvider; +use ILIAS\Mail\Attachments\MailAttachments; /** * Class ilBadgeHandler @@ -537,7 +538,7 @@ public function sendNotification( '', $lng->txt('badge_notification_subject'), $ntf->composeAndGetMessage($user_id, null, 'read', true), - [] + MailAttachments::empty() ); diff --git a/components/ILIAS/Calendar/classes/class.ilCalendarMailNotification.php b/components/ILIAS/Calendar/classes/class.ilCalendarMailNotification.php index 41935ce7cd3c..66346174d4be 100755 --- a/components/ILIAS/Calendar/classes/class.ilCalendarMailNotification.php +++ b/components/ILIAS/Calendar/classes/class.ilCalendarMailNotification.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Mail\Attachments\MailAttachments; + /** * Distributes calendar mail notifications * @author Stefan Meyer @@ -462,17 +464,19 @@ protected function addAttachment(): void $ics_filename = 'appointment_' . (new DateTimeImmutable('now'))->format('Ymd_His_u') . '.ics'; $attachment = new ilFileDataMail($this->getSender()); - $effective_ics_filename = $attachment->storeAsAttachment( + $rcid = $attachment->createCollectionFromContent( $ics_filename, $export->getExportString() ); - $this->setAttachments([$effective_ics_filename]); + $this->setAttachments( + $rcid !== null + ? MailAttachments::fromIrss($rcid) + : MailAttachments::empty() + ); } protected function deleteAttachments(): void { - $attachment = new ilFileDataMail($this->getSender()); - $attachment->unlinkFiles($this->getAttachments()); } } diff --git a/components/ILIAS/Course/classes/class.ilTimingsCronReminder.php b/components/ILIAS/Course/classes/class.ilTimingsCronReminder.php index 88a264feb927..30b1d3731c8c 100755 --- a/components/ILIAS/Course/classes/class.ilTimingsCronReminder.php +++ b/components/ILIAS/Course/classes/class.ilTimingsCronReminder.php @@ -21,6 +21,7 @@ use ILIAS\Cron\Job\Schedule\JobScheduleType; use ILIAS\Cron\Job\JobResult; use ILIAS\Cron\CronJob; +use ILIAS\Mail\Attachments\MailAttachments; class ilTimingsCronReminder extends CronJob { @@ -309,7 +310,7 @@ protected function sendExceededMail(int $user_id, array $ref_ids, string $mail_b '', $this->user_lang->txt('timings_cron_reminder_exceeded_subject'), $mail_body, - [], + MailAttachments::empty(), true ); $this->log->debug('...mail send for user ' . $user_id . ' to mail ' . $login . ' has exceeded timings for ' . $mail_body); diff --git a/components/ILIAS/EmployeeTalk/classes/Notification/Notification.php b/components/ILIAS/EmployeeTalk/classes/Notification/Notification.php index 4158b62b48a6..ab6ab46b8451 100755 --- a/components/ILIAS/EmployeeTalk/classes/Notification/Notification.php +++ b/components/ILIAS/EmployeeTalk/classes/Notification/Notification.php @@ -20,6 +20,8 @@ namespace ILIAS\EmployeeTalk\Notification; +use ILIAS\Mail\Attachments\MailAttachments; + class Notification { protected \ilObjUser $to; @@ -117,10 +119,10 @@ public function send(): void } $attachment_name = 'appointments.ics'; - $attachment->storeAsAttachment( - $attachment_name, - $this->attachment - ); + $rcid = $attachment->createCollectionFromContent($attachment_name, $this->attachment); + $attachments = $rcid !== null + ? MailAttachments::fromIrss($rcid) + : MailAttachments::empty(); $mail = new \ilMail(ANONYMOUS_USER_ID); $mail->enqueue( @@ -129,10 +131,8 @@ public function send(): void '', $subject, $notif->composeAndGetMessage($this->to->getId(), null, '', true), - [$attachment_name] + $attachments ); - - $attachment->unlinkFile($attachment_name); } protected function getTalkGoto(): string diff --git a/components/ILIAS/Exercise/Assignment/class.ilExAssignmentReminder.php b/components/ILIAS/Exercise/Assignment/class.ilExAssignmentReminder.php index 0581655d17d6..92be6900ce82 100755 --- a/components/ILIAS/Exercise/Assignment/class.ilExAssignmentReminder.php +++ b/components/ILIAS/Exercise/Assignment/class.ilExAssignmentReminder.php @@ -16,6 +16,8 @@ * *********************************************************************/ +use ILIAS\Mail\Attachments\MailAttachments; + /** * * TODO: import/export reminder data with the exercise/assignment. @@ -561,7 +563,7 @@ protected function sendReminders(array $reminders): int "", $subject, $message, - array() + MailAttachments::empty() ); } diff --git a/components/ILIAS/Exercise/classes/class.ilObjExercise.php b/components/ILIAS/Exercise/classes/class.ilObjExercise.php index 2f5dfdf9b923..c979348db31f 100755 --- a/components/ILIAS/Exercise/classes/class.ilObjExercise.php +++ b/components/ILIAS/Exercise/classes/class.ilObjExercise.php @@ -23,6 +23,8 @@ use ILIAS\Filesystem\Exception\IOException; use ILIAS\Exercise\InternalService; use ILIAS\Exercise\Assignment\Mandatory\MandatoryAssignmentsManager; +use ILIAS\Mail\Attachments\MailAttachments; +use ILIAS\ResourceStorage\Identification\ResourceIdentification; /** * Class ilObjExercise @@ -455,15 +457,16 @@ public function sendAssignment(ilExAssignment $a_ass, array $a_members): void // instruction files $if = $this->service->domain()->assignment()->instructionFiles($a_ass->getId()); $files = $if->getFiles(); - $file_names = []; + $attachments = MailAttachments::empty(); if (count($files) > 0) { $mfile_obj = new ilFileDataMail($GLOBALS['DIC']['ilUser']->getId()); + $source_ids = []; foreach ($if->getFiles() as $file) { - $file_names[] = $file["name"]; - $mfile_obj->storeAsAttachment( - $file["name"], - $if->getStream($file["rid"])->getContents() - ); + $source_ids[] = new ResourceIdentification($file['rid']); + } + $rcid = $mfile_obj->createCollectionFromForeignResources($source_ids); + if ($rcid !== null) { + $attachments = MailAttachments::fromIrss($rcid); } } @@ -485,16 +488,10 @@ public function sendAssignment(ilExAssignment $a_ass, array $a_members): void "", $subject, $body, - $file_names + $attachments ); unset($tmp_mail_obj); - // remove tmp files - if (count($file_names) && $mfile_obj) { - $mfile_obj->unlinkFiles($file_names); - unset($mfile_obj); - } - // set recipients mail status foreach ($a_members as $member_id) { $member_status = $a_ass->getMemberStatus($member_id); diff --git a/components/ILIAS/Forum/classes/Notification/class.ilForumCronNotificationDataProvider.php b/components/ILIAS/Forum/classes/Notification/class.ilForumCronNotificationDataProvider.php index bdb50c48e0d5..d8653fee13ad 100755 --- a/components/ILIAS/Forum/classes/Notification/class.ilForumCronNotificationDataProvider.php +++ b/components/ILIAS/Forum/classes/Notification/class.ilForumCronNotificationDataProvider.php @@ -18,6 +18,9 @@ declare(strict_types=1); +use ILIAS\Mail\Attachments\MailAttachments; +use ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification; + /** * Class ilForumCronNotificationDataProvider * @author Nadia Matuschek @@ -46,6 +49,7 @@ class ilForumCronNotificationDataProvider implements ilForumNotificationMailData private readonly string $import_name; /** @var list */ private array $attachments = []; + private ?MailAttachments $mail_attachments = null; /** @var int[] */ private array $cron_recipients = []; private readonly int $post_update_user_id; @@ -105,13 +109,31 @@ protected function read(): void private function readAttachments(): void { - if (ilForumProperties::isSendAttachmentsByMailEnabled()) { - $fileDataForum = new ilFileDataForum($this->getObjId(), $this->getPostId()); - $filesOfPost = $fileDataForum->getFilesOfPost(); + if (!ilForumProperties::isSendAttachmentsByMailEnabled()) { + return; + } - foreach ($filesOfPost as $attachment) { - $this->attachments[] = $attachment['name']; - } + $fileDataForum = new ilFileDataForum($this->getObjId(), $this->getPostId()); + foreach ($fileDataForum->getFilesOfPost() as $attachment) { + $this->attachments[] = $attachment['name']; + } + + $post = new ilForumPost($this->getPostId()); + $rcid_string = $post->getRCID(); + if ($rcid_string === '' || $rcid_string === ilForumPost::NO_RCID) { + return; + } + + global $DIC; + $forum_collection = $DIC->resourceStorage()->collection()->get( + new ResourceCollectionIdentification($rcid_string) + ); + $fileDataMail = new ilFileDataMail(ANONYMOUS_USER_ID); + $mail_rcid = $fileDataMail->createCollectionFromForeignResources( + iterator_to_array($forum_collection->getResourceIdentifications(), false) + ); + if ($mail_rcid !== null) { + $this->mail_attachments = MailAttachments::fromIrss($mail_rcid); } } @@ -216,6 +238,11 @@ public function getAttachments(): array return $this->attachments; } + public function getMailAttachments(): MailAttachments + { + return $this->mail_attachments ?? MailAttachments::empty(); + } + public function getPosDisplayUserId(): int { return $this->pos_display_user_id; diff --git a/components/ILIAS/Forum/classes/Notification/class.ilForumMailEventNotificationSender.php b/components/ILIAS/Forum/classes/Notification/class.ilForumMailEventNotificationSender.php index 5849107f6763..3810b29c71a4 100755 --- a/components/ILIAS/Forum/classes/Notification/class.ilForumMailEventNotificationSender.php +++ b/components/ILIAS/Forum/classes/Notification/class.ilForumMailEventNotificationSender.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Mail\Attachments\MailAttachments; + /** * @author Niels Theen */ @@ -360,7 +362,7 @@ private function createMailValueObjectsWithAttachments( '', ilStr::strLen($subjectText) > 255 ? ilStr::substr($subjectText, 0, 255) : $subjectText, $bodyText, - $this->provider->getAttachments(), + $this->provider->getMailAttachments(), false, false ); @@ -393,7 +395,7 @@ private function createMailValueObjectWithoutAttachments( '', ilStr::strLen($subjectText) > 255 ? ilStr::substr($subjectText, 0, 255) : $subjectText, $bodyText, - [], + MailAttachments::empty(), false, false ); diff --git a/components/ILIAS/Forum/classes/Notification/class.ilForumNotificationDataProvider.php b/components/ILIAS/Forum/classes/Notification/class.ilForumNotificationDataProvider.php index 3cd624cd6d11..60e6dfc44ee4 100755 --- a/components/ILIAS/Forum/classes/Notification/class.ilForumNotificationDataProvider.php +++ b/components/ILIAS/Forum/classes/Notification/class.ilForumNotificationDataProvider.php @@ -18,6 +18,9 @@ declare(strict_types=1); +use ILIAS\Mail\Attachments\MailAttachments; +use ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification; + /** * Class ilForumNotificationDataProvider * @author Nadia Matuschek @@ -35,6 +38,7 @@ class ilForumNotificationDataProvider implements ilForumNotificationMailData protected string $thread_title = ''; /** @var array */ protected array $attachments = []; + private ?MailAttachments $mail_attachments = null; private readonly ilDBInterface $db; private readonly ilAccessHandler $access; private readonly ilObjUser $user; @@ -148,6 +152,11 @@ public function getAttachments(): array return $this->attachments; } + public function getMailAttachments(): MailAttachments + { + return $this->mail_attachments ?? MailAttachments::empty(); + } + public function getPosUserAlias(): string { return $this->objPost->getUserAlias(); @@ -314,16 +323,30 @@ public function determineClosestContainer(int $frm_ref_id): ?ilObject private function readAttachments(): void { - if (ilForumProperties::isSendAttachmentsByMailEnabled()) { - $fileDataForum = new ilFileDataForum($this->getObjId(), $this->objPost->getId()); - $filesOfPost = $fileDataForum->getFilesOfPost(); + if (!ilForumProperties::isSendAttachmentsByMailEnabled()) { + return; + } - $fileDataMail = new ilFileDataMail(ANONYMOUS_USER_ID); + $fileDataForum = new ilFileDataForum($this->getObjId(), $this->objPost->getId()); + foreach ($fileDataForum->getFilesOfPost() as $attachment) { + $this->attachments[$attachment['path']] = $attachment['name']; + } - foreach ($filesOfPost as $attachment) { - $this->attachments[$attachment['path']] = $attachment['name']; - $fileDataMail->copyAttachmentFile($attachment['path'], $attachment['name']); - } + $rcid_string = $this->objPost->getRCID(); + if ($rcid_string === '' || $rcid_string === ilForumPost::NO_RCID) { + return; + } + + global $DIC; + $forum_collection = $DIC->resourceStorage()->collection()->get( + new ResourceCollectionIdentification($rcid_string) + ); + $fileDataMail = new ilFileDataMail(ANONYMOUS_USER_ID); + $mail_rcid = $fileDataMail->createCollectionFromForeignResources( + iterator_to_array($forum_collection->getResourceIdentifications(), false) + ); + if ($mail_rcid !== null) { + $this->mail_attachments = MailAttachments::fromIrss($mail_rcid); } } diff --git a/components/ILIAS/Forum/classes/class.ilForumMailNotification.php b/components/ILIAS/Forum/classes/class.ilForumMailNotification.php index 808c0575b6e0..aea3f5fa15db 100755 --- a/components/ILIAS/Forum/classes/class.ilForumMailNotification.php +++ b/components/ILIAS/Forum/classes/class.ilForumMailNotification.php @@ -77,7 +77,7 @@ protected function appendAttachments(): void $this->appendBody($this->getLanguageText('attachment') . ': ' . $attachment . "\n"); } $this->appendBody("\n------------------------------------------------------------\n"); - $this->setAttachments($this->provider->getAttachments()); + $this->setAttachments($this->provider->getMailAttachments()); } } diff --git a/components/ILIAS/Forum/interfaces/interface.ilForumNotificationMailData.php b/components/ILIAS/Forum/interfaces/interface.ilForumNotificationMailData.php index 191ad0b26bef..cd28220a4d8f 100755 --- a/components/ILIAS/Forum/interfaces/interface.ilForumNotificationMailData.php +++ b/components/ILIAS/Forum/interfaces/interface.ilForumNotificationMailData.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Mail\Attachments\MailAttachments; + /** * Interface ilForumNotificationMailData * @author Nadia Matuschek @@ -71,5 +73,7 @@ public function getCensorshipComment(): string; public function getAttachments(): array; + public function getMailAttachments(): MailAttachments; + public function getDeletedBy(): string; } diff --git a/components/ILIAS/LearningModule/classes/class.ilLearningModuleNotification.php b/components/ILIAS/LearningModule/classes/class.ilLearningModuleNotification.php index ee135bcbeebd..737d93067a66 100755 --- a/components/ILIAS/LearningModule/classes/class.ilLearningModuleNotification.php +++ b/components/ILIAS/LearningModule/classes/class.ilLearningModuleNotification.php @@ -16,6 +16,8 @@ * *********************************************************************/ +use ILIAS\Mail\Attachments\MailAttachments; + /** * Class ilLearningModuleNotification class * @@ -109,7 +111,7 @@ public function send(): void "", $subject, $message, - [] + MailAttachments::empty() ); } } diff --git a/components/ILIAS/Mail/classes/Attachments/FileDataRCHandling.php b/components/ILIAS/Mail/classes/Attachments/FileDataRCHandling.php index 581e6440291a..87abda602379 100644 --- a/components/ILIAS/Mail/classes/Attachments/FileDataRCHandling.php +++ b/components/ILIAS/Mail/classes/Attachments/FileDataRCHandling.php @@ -20,6 +20,7 @@ use ILIAS\Mail\Attachments\MailAttachments; use ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification; +use ILIAS\ResourceStorage\Identification\ResourceIdentification; trait FileDataRCHandling { @@ -48,13 +49,12 @@ public function filesFromLegacyToIRSS(array $mail_data): array throw new InvalidArgumentException('Legacy mail attachments expected.'); } - $path_to_files = []; - foreach ($attachments->legacyFilenames() as $file) { - $path_to_files[] = $this->fdm->getAbsoluteAttachmentPoolPathByFilename($file); + $rcid = $this->fdm->createCollectionFromPoolFilenames($attachments->legacyFilenames()); + if ($rcid === null) { + return []; } - $collection = $this->getCurrentCollection($path_to_files); - return $this->fdm->getRidsFromCollection($collection->getIdentification()); + return $this->fdm->getRidsFromCollection($rcid); } /** @@ -73,6 +73,43 @@ public function FilesFromIRSSToLegacy(ResourceCollectionIdentification $identifi return $this->fdm->getRidsFromCollection($identification); } + /** + * @param list $form_attachment_rids + */ + protected function attachmentsFromFormUpload( + array $form_attachment_rids, + ?MailAttachments $stage_attachments = null + ): MailAttachments { + if ($form_attachment_rids === []) { + return $stage_attachments ?? MailAttachments::empty(); + } + + $resource_identifications = []; + foreach ($form_attachment_rids as $attachment) { + $found = $this->storage->manage()->find($attachment); + if ($found === null) { + continue; + } + $resource_identifications[] = $found; + } + + if ($resource_identifications === []) { + return $stage_attachments ?? MailAttachments::empty(); + } + + $stage_rcid = ($stage_attachments instanceof MailAttachments && $stage_attachments->isIrss()) + ? $stage_attachments->rcid() + : null; + + if ($stage_rcid !== null && $this->fdm->collectionContainsResources($stage_rcid, $resource_identifications)) { + return MailAttachments::fromIrss($stage_rcid); + } + + return MailAttachments::fromIrss( + $this->fdm->createCollectionFromResourceIdentifications($resource_identifications) + ); + } + /** * @param array $attachments */ @@ -97,4 +134,41 @@ protected function handleAttachments(array $attachments): ResourceCollectionIden return $this->fdm->createCollectionFromResourceIdentifications($resource_identifications); } + + protected function stageAttachmentsFromMailAttachments(MailAttachments $attachments): MailAttachments + { + if ($attachments->isEmpty()) { + return MailAttachments::empty(); + } + + if ($attachments->isIrss()) { + $foreign = iterator_to_array( + $this->fdm->getCollection($attachments->rcid())->getResourceIdentifications(), + false + ); + $cloned_rcid = $this->fdm->createCollectionFromForeignResources($foreign); + + return $cloned_rcid !== null + ? MailAttachments::fromIrss($cloned_rcid) + : MailAttachments::empty(); + } + + $rcid = $this->fdm->createCollectionFromPoolFilenames($attachments->legacyFilenames()); + + return $rcid !== null + ? MailAttachments::fromIrss($rcid) + : MailAttachments::empty(); + } + + /** + * @return list + */ + protected function formRidsFromMailAttachments(MailAttachments $attachments): array + { + if ($attachments->isIrss()) { + return $this->FilesFromIRSSToLegacy($attachments->rcid()); + } + + return []; + } } diff --git a/components/ILIAS/Mail/classes/BackgroundTask/class.ilMassMailDeliveryJob.php b/components/ILIAS/Mail/classes/BackgroundTask/class.ilMassMailDeliveryJob.php index 777b298ac8ad..6ac98a4f7175 100755 --- a/components/ILIAS/Mail/classes/BackgroundTask/class.ilMassMailDeliveryJob.php +++ b/components/ILIAS/Mail/classes/BackgroundTask/class.ilMassMailDeliveryJob.php @@ -74,7 +74,7 @@ public function run(array $input, Observer $observer): Value $recipients_bcc, $value_object->getSubject(), $value_object->getBody(), - MailAttachments::fromLegacyFilenames($value_object->getAttachments()), + $value_object->getAttachments(), $value_object->isUsingPlaceholders() ); $mail->sendMail($mail_data); diff --git a/components/ILIAS/Mail/classes/Cron/ExpiredOrOrphanedMails/MailDeletionHandler.php b/components/ILIAS/Mail/classes/Cron/ExpiredOrOrphanedMails/MailDeletionHandler.php index ee6ecb035326..218ecc60ee62 100755 --- a/components/ILIAS/Mail/classes/Cron/ExpiredOrOrphanedMails/MailDeletionHandler.php +++ b/components/ILIAS/Mail/classes/Cron/ExpiredOrOrphanedMails/MailDeletionHandler.php @@ -33,6 +33,7 @@ use RecursiveDirectoryIterator; use ILIAS\ResourceStorage\Identification\ResourceCollectionIdentification; use SplFileInfo; +use ilFileDataMail; class MailDeletionHandler { diff --git a/components/ILIAS/Mail/classes/Object/class.ilMailValueObject.php b/components/ILIAS/Mail/classes/Object/class.ilMailValueObject.php index e7dbc3e223bf..de1236a63fd7 100755 --- a/components/ILIAS/Mail/classes/Object/class.ilMailValueObject.php +++ b/components/ILIAS/Mail/classes/Object/class.ilMailValueObject.php @@ -18,13 +18,13 @@ declare(strict_types=1); +use ILIAS\Mail\Attachments\MailAttachments; + class ilMailValueObject { - /** @var list */ - private readonly array $attachments; + private readonly MailAttachments $attachments; /** - * @param list $attachments * @throws InvalidArgumentException */ public function __construct( @@ -34,14 +34,14 @@ public function __construct( private readonly string $recipients_bcc, private readonly string $subject, private readonly string $body, - array $attachments, + ?MailAttachments $attachments = null, private readonly bool $use_placeholders = false, private readonly bool $save_in_sent_box = false ) { if (ilStr::strLen($this->subject) > 255) { throw new InvalidArgumentException('Subject must not be longer than 255 characters'); } - $this->attachments = array_filter(array_map('trim', $attachments)); + $this->attachments = $attachments ?? MailAttachments::empty(); } public function getRecipients(): string @@ -69,10 +69,7 @@ public function getBody(): string return $this->body; } - /** - * @return list - */ - public function getAttachments(): array + public function getAttachments(): MailAttachments { return $this->attachments; } diff --git a/components/ILIAS/Mail/classes/Object/class.ilMailValueObjectJsonService.php b/components/ILIAS/Mail/classes/Object/class.ilMailValueObjectJsonService.php index 2e02d8dfd1d3..080455b3bfab 100755 --- a/components/ILIAS/Mail/classes/Object/class.ilMailValueObjectJsonService.php +++ b/components/ILIAS/Mail/classes/Object/class.ilMailValueObjectJsonService.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Mail\Attachments\MailAttachments; + class ilMailValueObjectJsonService { /** @@ -32,7 +34,7 @@ public function convertToJson(array $mail_value_objects): string $mail_data['recipients'] = $mail_value_object->getRecipients(); $mail_data['recipients_cc'] = $mail_value_object->getRecipientsCC(); $mail_data['recipients_bcc'] = $mail_value_object->getRecipientsBCC(); - $mail_data['attachments'] = $mail_value_object->getAttachments(); + $mail_data['attachments'] = $mail_value_object->getAttachments()->toBackgroundTask(); $mail_data['body'] = $mail_value_object->getBody(); $mail_data['subject'] = $mail_value_object->getSubject(); $mail_data['is_using_placholders'] = $mail_value_object->isUsingPlaceholders(); @@ -60,7 +62,7 @@ public function convertFromJson(string $json): array $object_values['recipients_bcc'], ilStr::strLen($object_values['subject']) > 255 ? ilStr::substr($object_values['subject'], 0, 255) : $object_values['subject'], $object_values['body'], - $object_values['attachments'], + $this->attachmentsFromJson($object_values['attachments'] ?? null), $object_values['is_using_placholders'], $object_values['should_save_in_sent_box'] ); @@ -68,4 +70,17 @@ public function convertFromJson(string $json): array return $result; } + + private function attachmentsFromJson(mixed $attachments): MailAttachments + { + if (is_string($attachments)) { + return MailAttachments::fromBackgroundTask($attachments); + } + + if (is_array($attachments)) { + return MailAttachments::fromLegacyFilenames($attachments); + } + + return MailAttachments::empty(); + } } diff --git a/components/ILIAS/Mail/classes/class.ilAccountMail.php b/components/ILIAS/Mail/classes/class.ilAccountMail.php index c0fc6f57141f..aea9615499eb 100755 --- a/components/ILIAS/Mail/classes/class.ilAccountMail.php +++ b/components/ILIAS/Mail/classes/class.ilAccountMail.php @@ -23,6 +23,7 @@ use ILIAS\User\Settings\NewAccountMail\Repository as NewAccountMailRepository; use ILIAS\User\Settings\NewAccountMail\Mail as NewAccountMail; use ILIAS\ResourceStorage\Services as ResourceStorage; +use ILIAS\ResourceStorage\Identification\ResourceIdentification; class ilAccountMail { @@ -169,9 +170,17 @@ public function send(): bool $mail_body .= $tmp_lang->txt('reg_mail_body_text3') . "\n\r"; $mail_body .= $user->getProfileAsString($tmp_lang); } else { - $attachment = $amail->getAttachment($this->irss); - if ($attachment !== null) { - $mmail->Attach($attachment->getPath(), '', 'attachment', $attachment->getFilename()); + if ($amail->getAttachmentRid() !== null) { + $rid = $this->irss->manage()->find($amail->getAttachmentRid()); + if ($rid instanceof ResourceIdentification) { + $filename = $this->irss->manage()->getCurrentRevision($rid)->getInformation()->getTitle(); + $mmail->AttachResource($rid, $filename); + } + } else { + $attachment = $amail->getAttachment($this->irss); + if ($attachment !== null) { + $mmail->Attach($attachment->getPath(), '', 'attachment', $attachment->getFilename()); + } } // replace placeholders diff --git a/components/ILIAS/Mail/classes/class.ilFileDataMail.php b/components/ILIAS/Mail/classes/class.ilFileDataMail.php index e1ff17448f61..b4552c3f114e 100755 --- a/components/ILIAS/Mail/classes/class.ilFileDataMail.php +++ b/components/ILIAS/Mail/classes/class.ilFileDataMail.php @@ -811,6 +811,56 @@ public function createCollectionFromPaths(array $absolute_paths): ?ResourceColle return $this->createCollectionFromResourceIdentifications($resource_identifications); } + public function createCollectionFromContent(string $name, string $content): ?ResourceCollectionIdentification + { + if ($name === '' || $content === '') { + return null; + } + + if (strlen($content) >= $this->getUploadLimit()) { + throw new DomainException( + sprintf( + 'Mail upload limit reached for user with id %s', + $this->user_id + ) + ); + } + + $sanitized_name = ilFileUtils::_sanitizeFilemame($name); + + return $this->createCollectionFromResourceIdentifications([ + $this->irss->manage()->stream( + Streams::ofString($content), + $this->stakeholder, + md5($sanitized_name) + ), + ]); + } + + /** + * @param list $resource_identifications + */ + public function createCollectionFromForeignResources( + array $resource_identifications + ): ?ResourceCollectionIdentification { + if ($resource_identifications === []) { + return null; + } + + $mail_resources = []; + foreach ($resource_identifications as $source) { + $revision = $this->irss->manage()->getCurrentRevision($source); + $stream = $this->irss->consume()->stream($source); + $mail_resources[] = $this->irss->manage()->stream( + $stream->getStream(), + $this->stakeholder, + $revision->getTitle() + ); + } + + return $this->createCollectionFromResourceIdentifications($mail_resources); + } + public function getCollection(ResourceCollectionIdentification $rcid): ResourceCollection { if (!$this->collectionIsKnown($rcid)) { @@ -920,6 +970,34 @@ public function getRidsFromCollection(ResourceCollectionIdentification $rcid): a return $rids; } + + /** + * @param list $resource_identifications + */ + public function collectionContainsResources( + ResourceCollectionIdentification $rcid, + array $resource_identifications + ): bool { + if ($resource_identifications === []) { + return true; + } + + $collection_serials = []; + foreach ($this->getCollection($rcid)->getResourceIdentifications() as $resource_identification) { + $collection_serials[] = $resource_identification->serialize(); + } + + $expected_serials = []; + foreach ($resource_identifications as $resource_identification) { + $expected_serials[] = $resource_identification->serialize(); + } + + sort($collection_serials); + sort($expected_serials); + + return $collection_serials === $expected_serials; + } + /** * @return array */ diff --git a/components/ILIAS/Mail/classes/class.ilMailAttachmentGUI.php b/components/ILIAS/Mail/classes/class.ilMailAttachmentGUI.php index f382f6cb50d8..7d0a8b9d5132 100644 --- a/components/ILIAS/Mail/classes/class.ilMailAttachmentGUI.php +++ b/components/ILIAS/Mail/classes/class.ilMailAttachmentGUI.php @@ -310,10 +310,7 @@ private function showAttachmentsCommand(): void $records = []; $checked_items = []; foreach ($files as $file) { - if ($stage_attachments instanceof MailAttachments && $stage_attachments->isLegacy() - && in_array($file['name'], $stage_attachments->legacyFilenames(), true)) { - $checked_items[] = urlencode($file['name']); - } elseif (isset($adopted_pool_hashes[$this->fdm->poolFilenameHash($file['name'])])) { + if (isset($adopted_pool_hashes[$this->fdm->poolFilenameHash($file['name'])])) { $checked_items[] = urlencode($file['name']); } diff --git a/components/ILIAS/Mail/classes/class.ilMailFormGUI.php b/components/ILIAS/Mail/classes/class.ilMailFormGUI.php index d00bb6ce97d1..d6d6122ad2b7 100755 --- a/components/ILIAS/Mail/classes/class.ilMailFormGUI.php +++ b/components/ILIAS/Mail/classes/class.ilMailFormGUI.php @@ -230,10 +230,11 @@ protected function decodeAttachmentFiles(array $files): array public function saveMessageToOutbox(array $form_values, Form $form): void { - $attachments = MailAttachments::empty(); - if (count($form_values['attachments']) > 0) { - $attachments = MailAttachments::fromIrss($this->handleAttachments($form_values['attachments'])); - } + $stage = $this->umail->retrieveFromStage(); + $stage_attachments = ($stage['attachments'] ?? null) instanceof MailAttachments + ? $stage['attachments'] + : null; + $attachments = $this->attachmentsFromFormUpload($form_values['attachments'], $stage_attachments); $rcp_to = ''; $rcp_cc = ''; @@ -324,10 +325,11 @@ public function sendMessage(): void return; } - $attachments = MailAttachments::empty(); - if (count($value['attachments']) > 0) { - $attachments = MailAttachments::fromIrss($this->handleAttachments($value['attachments'])); - } + $stage = $this->umail->retrieveFromStage(); + $stage_attachments = ($stage['attachments'] ?? null) instanceof MailAttachments + ? $stage['attachments'] + : null; + $attachments = $this->attachmentsFromFormUpload($value['attachments'], $stage_attachments); $mailer = $this->umail ->withContextId(ilMailFormCall::getContextId() ?: '') @@ -420,10 +422,11 @@ public function saveDraft(): void if ($value['m_subject'] === '') { $value['m_subject'] = $this->lng->txt('mail_no_subject'); } - $attachments = MailAttachments::empty(); - if (count($value['attachments']) > 0) { - $attachments = MailAttachments::fromIrss($this->handleAttachments($value['attachments'])); - } + $stage = $this->umail->retrieveFromStage(); + $stage_attachments = ($stage['attachments'] ?? null) instanceof MailAttachments + ? $stage['attachments'] + : null; + $attachments = $this->attachmentsFromFormUpload($value['attachments'], $stage_attachments); $draft_folder_id = $this->mbox->getDraftsFolder(); @@ -712,14 +715,23 @@ public function showForm(?Form $form = null): void ilSession::set('draft', $mail_id); $mail_data = $this->umail->getMail($mail_id); - if ($mail_data['attachments'] instanceof MailAttachments) { - if ($mail_data['attachments']->isIrss()) { - $mail_data['attachments'] = $this->FilesFromIRSSToLegacy($mail_data['attachments']->rcid()); - } elseif ($mail_data['attachments']->isLegacy()) { - $mail_data['attachments'] = $this->filesFromLegacyToIRSS($mail_data); - } else { - $mail_data['attachments'] = []; - } + if ($mail_data['attachments'] instanceof MailAttachments && !$mail_data['attachments']->isEmpty()) { + $stage_attachments = $this->stageAttachmentsFromMailAttachments($mail_data['attachments']); + $this->umail->persistToStage( + $this->user->getId(), + (string) ($mail_data['rcp_to'] ?? ''), + (string) ($mail_data['rcp_cc'] ?? ''), + (string) ($mail_data['rcp_bcc'] ?? ''), + (string) ($mail_data['m_subject'] ?? ''), + (string) ($mail_data['m_message'] ?? ''), + $stage_attachments, + (bool) ($mail_data['use_placeholders'] ?? false), + $mail_data['tpl_ctx_id'] ?? null, + (array) ($mail_data['tpl_ctx_params'] ?? []) + ); + $mail_data['attachments'] = $this->formRidsFromMailAttachments($stage_attachments); + } else { + $mail_data['attachments'] = []; } ilMailFormCall::setContextId($mail_data['tpl_ctx_id']); @@ -738,20 +750,21 @@ public function showForm(?Form $form = null): void $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 ($mail_data['attachments'] instanceof MailAttachments) { - if ($mail_data['attachments']->isIrss()) { - $mail_data['attachments'] = $this->FilesFromIRSSToLegacy($mail_data['attachments']->rcid()); - } elseif ($mail_data['attachments']->isLegacy()) { - if ($error = $this->mfile->adoptAttachments( - $mail_data['attachments']->legacyFilenames(), - $mail_id - )) { - $this->tpl->setOnScreenMessage('info', $error); - } - $mail_data['attachments'] = $this->filesFromLegacyToIRSS($mail_data); - } else { - $mail_data['attachments'] = []; - } + if ($mail_data['attachments'] instanceof MailAttachments && !$mail_data['attachments']->isEmpty()) { + $stage_attachments = $this->stageAttachmentsFromMailAttachments($mail_data['attachments']); + $this->umail->persistToStage( + $this->user->getId(), + '', + '', + '', + $mail_data['m_subject'] ?? '', + $mail_data['m_message'] ?? '', + $stage_attachments, + false + ); + $mail_data['attachments'] = $this->formRidsFromMailAttachments($stage_attachments); + } else { + $mail_data['attachments'] = []; } break; @@ -857,7 +870,12 @@ public function showForm(?Form $form = null): void if ($this->request_attachments->isIrss()) { $mail_data['attachments'] = $this->FilesFromIRSSToLegacy($this->request_attachments->rcid()); } elseif ($this->request_attachments->isLegacy()) { - $mail_data['attachments'] = $this->request_attachments->legacyFilenames(); + $rcid = $this->fdm->createCollectionFromPoolFilenames( + $this->request_attachments->legacyFilenames() + ); + $mail_data['attachments'] = $rcid !== null + ? $this->FilesFromIRSSToLegacy($rcid) + : []; } } break; @@ -1042,7 +1060,10 @@ protected function buildFormElements(?array $mail_data): array if ($mail_attachments->isIrss()) { $mail_data['attachments'] = $this->FilesFromIRSSToLegacy($mail_attachments->rcid()); } elseif ($mail_attachments->isLegacy()) { - $mail_data['attachments'] = $mail_attachments->legacyFilenames(); + $rcid = $this->fdm->createCollectionFromPoolFilenames($mail_attachments->legacyFilenames()); + $mail_data['attachments'] = $rcid !== null + ? $this->FilesFromIRSSToLegacy($rcid) + : []; } else { $mail_data['attachments'] = []; } diff --git a/components/ILIAS/Mail/classes/class.ilMailNotification.php b/components/ILIAS/Mail/classes/class.ilMailNotification.php index a2c9a230148b..b7fffae61980 100755 --- a/components/ILIAS/Mail/classes/class.ilMailNotification.php +++ b/components/ILIAS/Mail/classes/class.ilMailNotification.php @@ -29,7 +29,7 @@ abstract class ilMailNotification protected ?ilMail $mail = null; protected string $subject = ''; protected string $body = ''; - protected array $attachments = []; + protected MailAttachments $attachments; protected ilLanguage $language; protected array $lang_modules = []; protected array $recipients = []; @@ -43,6 +43,7 @@ abstract class ilMailNotification public function __construct(protected bool $is_in_wsp = false) { global $DIC; + $this->attachments = MailAttachments::empty(); $this->setSender(ANONYMOUS_USER_ID); $this->language = ilLanguageFactory::_getLanguage($DIC->language()->getDefaultLanguage()); @@ -111,12 +112,12 @@ public function getRecipients(): array return $this->recipients; } - public function setAttachments(array $a_att): void + public function setAttachments(MailAttachments $attachments): void { - $this->attachments = $a_att; + $this->attachments = $attachments; } - public function getAttachments(): array + public function getAttachments(): MailAttachments { return $this->attachments; } @@ -241,7 +242,7 @@ public function sendMail(array $a_rcp, bool $a_parse_recipients = true): void '', $this->getSubject(), $this->getBody(), - MailAttachments::fromLegacyFilenames($this->getAttachments()) + $this->getAttachments() ); if ($errors !== []) { ilLoggerFactory::getLogger('mail')->dump($errors, ilLogLevel::ERROR); diff --git a/components/ILIAS/Mail/tests/ilMailTaskProcessorTest.php b/components/ILIAS/Mail/tests/ilMailTaskProcessorTest.php index 32a1b31d6efb..f397da5d97f9 100755 --- a/components/ILIAS/Mail/tests/ilMailTaskProcessorTest.php +++ b/components/ILIAS/Mail/tests/ilMailTaskProcessorTest.php @@ -21,6 +21,7 @@ use ILIAS\BackgroundTasks\Implementation\TaskManager\BasicTaskManager; use ILIAS\BackgroundTasks\Task\TaskFactory; use ILIAS\DI\Container; +use ILIAS\Mail\Attachments\MailAttachments; class ilMailTaskProcessorTest extends ilMailBaseTestCase { @@ -58,7 +59,7 @@ public function testMailValueObjectCannotBeCreatedWithUnsupportedSubjectLength() '', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce mollis posuere tincidunt. Phasellus et euismod ligula. Suspendisse dignissim eget dui nec imperdiet. Donec in pretium tellus. Maecenas lacinia eleifend erat ut euismod. Aenean eu malesuada est.', 'Dear Steve, great!', - [] + MailAttachments::empty() ); } @@ -108,7 +109,7 @@ public function testOneTask(): void '', 'That is awesome!', 'Dear Steve, great!', - [] + MailAttachments::empty() ); $value_objects = [ @@ -175,7 +176,7 @@ public function testRunTwoTasks(): void '', 'That is awesome!', 'Dear Steve, great!', - [] + MailAttachments::empty() ); $value_objects[] = new ilMailValueObject( @@ -185,7 +186,7 @@ public function testRunTwoTasks(): void '', 'Greate', 'Steve, Steve, Steve. Wait that is not Steve', - [] + MailAttachments::empty() ); $usr_id = 100; @@ -248,7 +249,7 @@ public function testRunThreeTasksInDifferentBuckets(): void '', 'That is awesome!', 'Dear Steve, great!', - [] + MailAttachments::empty() ); $value_objects[] = new ilMailValueObject( @@ -258,7 +259,7 @@ public function testRunThreeTasksInDifferentBuckets(): void '', 'Greate', 'Steve, Steve, Steve. Wait that is not Steve', - [] + MailAttachments::empty() ); $value_objects[] = new ilMailValueObject( @@ -268,7 +269,7 @@ public function testRunThreeTasksInDifferentBuckets(): void '', 'That is awesome!', 'Hey Alan! Alan! Alan!', - [] + MailAttachments::empty() ); $usr_id = 100; @@ -334,7 +335,7 @@ public function testRunHasWrongTypeAndWillResultInException(): void '', 'That is awesome!', 'Dear Steve, great!', - [] + MailAttachments::empty() ); $value_objects[] = new ilMailValueObject( @@ -344,7 +345,7 @@ public function testRunHasWrongTypeAndWillResultInException(): void '', 'Greate', 'Steve, Steve, Steve. Wait that is not Steve', - [] + MailAttachments::empty() ); $value_objects[] = 'This should fail'; diff --git a/components/ILIAS/Membership/classes/Cron/class.ilMembershipCronMinMembers.php b/components/ILIAS/Membership/classes/Cron/class.ilMembershipCronMinMembers.php index b35b70407284..f49fe7db0e84 100755 --- a/components/ILIAS/Membership/classes/Cron/class.ilMembershipCronMinMembers.php +++ b/components/ILIAS/Membership/classes/Cron/class.ilMembershipCronMinMembers.php @@ -21,6 +21,7 @@ use ILIAS\Cron\Job\Schedule\JobScheduleType; use ILIAS\Cron\Job\JobResult; use ILIAS\Cron\CronJob; +use ILIAS\Mail\Attachments\MailAttachments; /** * Cron for course/group minimum members @@ -162,7 +163,7 @@ protected function sendMessage(int $a_reci_id, array $a_items): void '', $lng->txt("mem_cron_min_members_subject"), $ntf->composeAndGetMessage($a_reci_id, null, "read", true), - [] + MailAttachments::empty() ); } } diff --git a/components/ILIAS/Membership/classes/Cron/class.ilMembershipCronNotifications.php b/components/ILIAS/Membership/classes/Cron/class.ilMembershipCronNotifications.php index c0fea84531eb..501c8432f7d0 100755 --- a/components/ILIAS/Membership/classes/Cron/class.ilMembershipCronNotifications.php +++ b/components/ILIAS/Membership/classes/Cron/class.ilMembershipCronNotifications.php @@ -21,6 +21,7 @@ use ILIAS\Cron\Job\Schedule\JobScheduleType; use ILIAS\Cron\Job\JobResult; use ILIAS\Cron\CronJob; +use ILIAS\Mail\Attachments\MailAttachments; /** * Course/group notifications @@ -475,7 +476,7 @@ protected function sendMail(int $a_user_id, array $a_objects, string $a_last_run (string) null, $subject, $mail_content, - [] + MailAttachments::empty() ); } diff --git a/components/ILIAS/Notes/Notifications/class.NotificationsManager.php b/components/ILIAS/Notes/Notifications/class.NotificationsManager.php index c54a86fb1f97..ebe13666ad2c 100755 --- a/components/ILIAS/Notes/Notifications/class.NotificationsManager.php +++ b/components/ILIAS/Notes/Notifications/class.NotificationsManager.php @@ -20,6 +20,8 @@ namespace ILIAS\Notes; +use ILIAS\Mail\Attachments\MailAttachments; + /** * @author Alexander Killing */ @@ -162,7 +164,7 @@ public function sendNotifications( "", $subject, $message, - array() + MailAttachments::empty() ); } } diff --git a/components/ILIAS/Notifications/classes/ilNotificationMailHandler.php b/components/ILIAS/Notifications/classes/ilNotificationMailHandler.php index 4d04e1a17238..628febdfe86b 100755 --- a/components/ILIAS/Notifications/classes/ilNotificationMailHandler.php +++ b/components/ILIAS/Notifications/classes/ilNotificationMailHandler.php @@ -22,6 +22,7 @@ use ILIAS\Notifications\Model\ilNotificationObject; use ilMail; +use ILIAS\Mail\Attachments\MailAttachments; /** * @author Jan Posselt @@ -39,7 +40,7 @@ public function notify(ilNotificationObject $notification): void '', $notification->title, $notification->longDescription, - [] + MailAttachments::empty() ); } } diff --git a/components/ILIAS/Skill/classes/class.ilSkillNotifications.php b/components/ILIAS/Skill/classes/class.ilSkillNotifications.php index 37faa43c7cf2..91c864a20d32 100755 --- a/components/ILIAS/Skill/classes/class.ilSkillNotifications.php +++ b/components/ILIAS/Skill/classes/class.ilSkillNotifications.php @@ -22,6 +22,7 @@ use ILIAS\Cron\Job\Schedule\JobScheduleType; use ILIAS\Cron\Job\JobResult; use ILIAS\Cron\CronJob; +use ILIAS\Mail\Attachments\MailAttachments; /** * Course/group skill notification @@ -233,7 +234,7 @@ protected function sendMail(int $a_user_id, array $a_achievements, string $a_las "", $subject, $ntf->composeAndGetMessage($a_user_id, null, "read", true), - [] + MailAttachments::empty() ); } } diff --git a/components/ILIAS/StudyProgramme/classes/Mail/class.ilPRGMail.php b/components/ILIAS/StudyProgramme/classes/Mail/class.ilPRGMail.php index 7bb1abd6b321..8bc7c6b2c123 100755 --- a/components/ILIAS/StudyProgramme/classes/Mail/class.ilPRGMail.php +++ b/components/ILIAS/StudyProgramme/classes/Mail/class.ilPRGMail.php @@ -19,6 +19,7 @@ declare(strict_types=1); use ILIAS\User\Settings\Settings as UserSettings; +use ILIAS\Mail\Attachments\MailAttachments; /** * Send mails to users (usually triggered by cron) @@ -96,7 +97,7 @@ protected function sendMail( $mail = new ilMail(ANONYMOUS_USER_ID); try { - $mail->enqueue($login, '', '', $subject, $body, []); + $mail->enqueue($login, '', '', $subject, $body, MailAttachments::empty()); return true; } catch (Exception $e) { $this->log->write($e->getMessage()); diff --git a/components/ILIAS/Survey/Participants/class.ilSurveyParticipantsGUI.php b/components/ILIAS/Survey/Participants/class.ilSurveyParticipantsGUI.php index 70fb9d0f124f..355260fab847 100755 --- a/components/ILIAS/Survey/Participants/class.ilSurveyParticipantsGUI.php +++ b/components/ILIAS/Survey/Participants/class.ilSurveyParticipantsGUI.php @@ -18,6 +18,7 @@ use ILIAS\Survey\CSVReader; use ILIAS\Survey\Participants; +use ILIAS\Mail\Attachments\MailAttachments; /** * Class ilSurveyParticipantsGUI @@ -1729,7 +1730,7 @@ public function mailRatersActionObject(): void "", // bcc $subj, // subject $mytxt, // message - array() // attachments + MailAttachments::empty() // attachments ); $this->object->set360RaterSent( diff --git a/components/ILIAS/Survey/Participants/class.ilSurveyRaterGUI.php b/components/ILIAS/Survey/Participants/class.ilSurveyRaterGUI.php index 608833e4f878..1b146f3a509c 100755 --- a/components/ILIAS/Survey/Participants/class.ilSurveyRaterGUI.php +++ b/components/ILIAS/Survey/Participants/class.ilSurveyRaterGUI.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Mail\Attachments\MailAttachments; + /** * @author Alexander Killing * @ilCtrl_Calls ilSurveyRaterGUI: ilRepositorySearchGUI @@ -385,7 +387,7 @@ public function mailRatersAction(): void "", // bcc $subj, // subject $mytxt, // message - array() // attachments + MailAttachments::empty() // attachments ); $this->survey->set360RaterSent( diff --git a/components/ILIAS/Survey/classes/class.ilObjSurvey.php b/components/ILIAS/Survey/classes/class.ilObjSurvey.php index b46c6b549556..d9e63d0405cb 100755 --- a/components/ILIAS/Survey/classes/class.ilObjSurvey.php +++ b/components/ILIAS/Survey/classes/class.ilObjSurvey.php @@ -19,6 +19,7 @@ use ILIAS\Survey\Participants; use ILIAS\Survey\Mode; use ILIAS\Survey\InternalDomainService; +use ILIAS\Mail\Attachments\MailAttachments; /** * @author Helmut Schottmüller @@ -3817,7 +3818,7 @@ public function sendCodes( "", // bcc $subject, // subject $messagetext, // message - array() // attachments + MailAttachments::empty() // attachments ); } } @@ -4321,7 +4322,7 @@ public function sendAppraiseeNotification( "", $subject, $ntf->composeAndGetMessage($a_user_id, null, "read", true), - [] + MailAttachments::empty() ); } @@ -4350,7 +4351,7 @@ public function sendAppraiseeCloseNotification( "", $subject, $ntf->composeAndGetMessage($a_user_id, null, "read", true), - [] + MailAttachments::empty() ); } @@ -4381,7 +4382,7 @@ public function sendRaterNotification( "", $subject, $ntf->composeAndGetMessage($a_user_id, null, "read", true), - [] + MailAttachments::empty() ); } @@ -5244,7 +5245,7 @@ public function send360ReminderToUser( "", $subject, $ntf->composeAndGetMessage($a_user_id, null, "read", true), - [] + MailAttachments::empty() ); } @@ -5307,7 +5308,7 @@ protected function sendTutorNotification(): void "", $subject, $message, - array() + MailAttachments::empty() ); } } @@ -5461,7 +5462,7 @@ protected function sentReminder( "", $subject, $message, - array() + MailAttachments::empty() ); } } diff --git a/components/ILIAS/Test/classes/class.ilTestMailNotification.php b/components/ILIAS/Test/classes/class.ilTestMailNotification.php index 2a7b1b5cd174..94678e4e01a8 100755 --- a/components/ILIAS/Test/classes/class.ilTestMailNotification.php +++ b/components/ILIAS/Test/classes/class.ilTestMailNotification.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Mail\Attachments\MailAttachments; + /** * Class ilTestMailNotification * @author Nadia Ahmad @@ -100,7 +102,15 @@ public function sendAdvancedNotification($owner_id, $title, $usr_data, $file_nam $this->appendBody($this->language->txt('tst_notification_explanation_admin')); $this->appendBody("\n"); - $this->setAttachments($file_names); + $attachments = MailAttachments::empty(); + if ($file_names !== []) { + $file_data_mail = new ilFileDataMail(ANONYMOUS_USER_ID); + $rcid = $file_data_mail->createCollectionFromPoolFilenames($file_names); + if ($rcid !== null) { + $attachments = MailAttachments::fromIrss($rcid); + } + } + $this->setAttachments($attachments); $this->getMail()->appendInstallationSignature(true); $this->sendMail([$owner_id]); diff --git a/components/ILIAS/User/classes/Cron/class.ilUserCronCheckAccounts.php b/components/ILIAS/User/classes/Cron/class.ilUserCronCheckAccounts.php index 5a65cf5f6bcf..53ccd7940ce8 100755 --- a/components/ILIAS/User/classes/Cron/class.ilUserCronCheckAccounts.php +++ b/components/ILIAS/User/classes/Cron/class.ilUserCronCheckAccounts.php @@ -21,6 +21,7 @@ use ILIAS\Cron\Job\Schedule\JobScheduleType; use ILIAS\Cron\Job\JobResult; use ILIAS\Cron\CronJob; +use ILIAS\Mail\Attachments\MailAttachments; /** * This cron send notifications about expiring user accounts @@ -133,7 +134,7 @@ public function run(): JobResult '', $lng->txt('account_expires_subject'), $body, - [] + MailAttachments::empty() ); // set status 'mail sent' diff --git a/components/ILIAS/User/src/Settings/NewAccountMail/Mail.php b/components/ILIAS/User/src/Settings/NewAccountMail/Mail.php index 7d5ab877d531..22199e7012b3 100644 --- a/components/ILIAS/User/src/Settings/NewAccountMail/Mail.php +++ b/components/ILIAS/User/src/Settings/NewAccountMail/Mail.php @@ -31,4 +31,5 @@ public function getSalutationNoneSpecific(): string; public function getSalutationMale(): string; public function getSalutationFemale(): string; public function getAttachment(ResourceStorage $irss): ?MailAttachment; + public function getAttachmentRid(): ?string; } diff --git a/components/ILIAS/WebServices/ECS/classes/class.ilECSAppEventListener.php b/components/ILIAS/WebServices/ECS/classes/class.ilECSAppEventListener.php index 0215acd1f65b..73f808e14281 100755 --- a/components/ILIAS/WebServices/ECS/classes/class.ilECSAppEventListener.php +++ b/components/ILIAS/WebServices/ECS/classes/class.ilECSAppEventListener.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Mail\Attachments\MailAttachments; + /** * ECS Event Handler * @author Stefan Meyer @@ -220,7 +222,7 @@ private function sendNotification(ilECSSetting $server, ilObjUser $user_obj): vo $body .= $user_obj->getProfileAsString($lang) . "\n\n"; $body .= ilMail::_getAutoGeneratedMessageString($lang); - $mail->enqueue($server->getUserRecipientsAsString(), "", "", $subject, $body, array()); + $mail->enqueue($server->getUserRecipientsAsString(), "", "", $subject, $body, MailAttachments::empty()); // Store sub_id = 1 in ecs import which means mail is send $import->setSubId("1"); diff --git a/components/ILIAS/Wiki/classes/class.ilWikiUtil.php b/components/ILIAS/Wiki/classes/class.ilWikiUtil.php index 7d1130d1aa30..d3aa1a5ad0d7 100755 --- a/components/ILIAS/Wiki/classes/class.ilWikiUtil.php +++ b/components/ILIAS/Wiki/classes/class.ilWikiUtil.php @@ -30,6 +30,8 @@ * */ // From include/Unicode/UtfNormal.php +use ILIAS\Mail\Attachments\MailAttachments; + if (!defined('UTF8_REPLACEMENT')) { define('UTF8_REPLACEMENT', "\xef\xbf\xbd" /*codepointToUtf8( UNICODE_REPLACEMENT )*/); } @@ -626,7 +628,7 @@ public static function sendNotification( "", $subject, $message, - array() + MailAttachments::empty() );*/ $message .= ilMail::_getInstallationSignature(); $mails[] = new ilMailValueObject( @@ -636,7 +638,7 @@ public static function sendNotification( '', $subject, $message, - [], + MailAttachments::empty(), false, false );