ILIAS  release_8 Revision v8.24
class.ilFileDataMail.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
31{
32 public int $user_id;
33 public string $mail_path;
37 protected ilDBInterface $db;
38 protected ILIAS $ilias;
39
40 public function __construct(int $a_user_id = 0)
41 {
42 global $DIC;
43
44 if (!defined('MAILPATH')) {
45 define('MAILPATH', 'mail');
46 }
48 $this->mail_path = $this->getPath() . "/" . MAILPATH;
49 $this->ilias = $DIC['ilias'];
50 $this->checkReadWrite();
51 $this->user_id = $a_user_id;
52
53 $this->db = $DIC->database();
54 $this->tmpDirectory = $DIC->filesystem()->temp();
55 $this->storageDirectory = $DIC->filesystem()->storage();
56
58 }
59
60 public function initDirectory(): bool
61 {
62 if (is_writable($this->getPath())
63 && mkdir($this->getPath() . '/' . MAILPATH)
64 && chmod($this->getPath() . '/' . MAILPATH, 0755)) {
65 $this->mail_path = $this->getPath() . '/' . MAILPATH;
66 return true;
67 }
68 return false;
69 }
70
71 public function getUploadLimit(): int
72 {
74 }
75
76 public function getAttachmentsTotalSizeLimit(): ?float
77 {
78 $max_size = $this->ilias->getSetting('mail_maxsize_attach', '');
79 if ($max_size === '') {
80 return null;
81 }
82
83 return (float) $this->ilias->getSetting('mail_maxsize_attach', '0') * 1024;
84 }
85
86 public function getMailPath(): string
87 {
88 return $this->mail_path;
89 }
90
91 public function getAbsoluteAttachmentPoolPathPrefix(): string
92 {
93 return $this->mail_path . '/' . $this->user_id . '_';
94 }
95
100 public function getAttachmentPathAndFilenameByMd5Hash(string $md5FileHash, int $mailId): array
101 {
102 $res = $this->db->queryF(
103 "SELECT path FROM mail_attachment WHERE mail_id = %s",
104 ['integer'],
105 [$mailId]
106 );
107
108 if (1 !== $this->db->numRows($res)) {
109 throw new OutOfBoundsException();
110 }
111
112 $row = $this->db->fetchAssoc($res);
113
114 $relativePath = $row['path'];
115 $path = $this->getMailPath() . '/' . $row['path'];
116
117 $files = ilFileUtils::getDir($path);
118 foreach ($files as $file) {
119 if ($file['type'] === 'file' && md5($file['entry']) === $md5FileHash) {
120 return [
121 'path' => $this->getMailPath() . '/' . $relativePath . '/' . $file['entry'],
122 'filename' => $file['entry'],
123 ];
124 }
125 }
126
127 throw new OutOfBoundsException();
128 }
129
130
131 private function getAttachmentPathByMailId(int $mailId): string
132 {
133 $query = $this->db->query(
134 "SELECT path FROM mail_attachment WHERE mail_id = " . $this->db->quote($mailId, 'integer')
135 );
136
137 while ($row = $this->db->fetchObject($query)) {
138 return $row->path;
139 }
140
141 return '';
142 }
143
144 public function getAttachmentPath(string $a_filename, int $a_mail_id): string
145 {
146 $path = $this->getMailPath() . '/' . $this->getAttachmentPathByMailId($a_mail_id) . '/' . $a_filename;
147
148 if (is_readable($path)) {
149 return $path;
150 }
151
152 return '';
153 }
154
161 public function adoptAttachments(array $a_attachments, int $a_mail_id): string
162 {
163 foreach ($a_attachments as $file) {
164 $path = $this->getAttachmentPath($file, $a_mail_id);
165 if (!copy($path, $this->getMailPath() . '/' . $this->user_id . '_' . $file)) {
166 return 'ERROR: ' . $this->getMailPath() . '/' . $this->user_id . '_' . $file . ' cannot be created';
167 }
168 }
169
170 return '';
171 }
172
173 public function checkReadWrite(): bool
174 {
175 if (is_writable($this->mail_path) && is_readable($this->mail_path)) {
176 return true;
177 }
178
179 $this->ilias->raiseError(
180 "Mail directory is not readable/writable by webserver: " .
181 $this->mail_path,
182 $this->ilias->error_obj->FATAL
183 );
184
185 return false;
186 }
187
191 public function getUserFilesData(): array
192 {
193 return $this->getUnsentFiles();
194 }
195
199 private function getUnsentFiles(): array
200 {
201 $files = [];
202
203 $iter = new RegexIterator(new DirectoryIterator($this->mail_path), "/^{$this->user_id}_(.+)$/");
204 foreach ($iter as $file) {
206 if (!$file->isFile()) {
207 continue;
208 }
209
210 [$uid, $rest] = explode('_', $file->getFilename(), 2);
211 if ($uid === (string) $this->user_id) {
212 $files[] = [
213 'name' => $rest,
214 'size' => $file->getSize(),
215 'ctime' => $file->getCTime(),
216 ];
217 }
218 }
219
220 return $files;
221 }
222
223 public function storeAsAttachment(string $a_filename, string $a_content): string
224 {
225 if (strlen($a_content) >= $this->getUploadLimit()) {
226 throw new DomainException(
227 sprintf(
228 'Mail upload limit reached for user with id %s',
229 $this->user_id
230 )
231 );
232 }
233
235 $this->rotateFiles($this->getMailPath() . '/' . $this->user_id . '_' . $name);
236
237 $abs_path = $this->getMailPath() . '/' . $this->user_id . '_' . $name;
238
239 $fp = fopen($abs_path, 'wb+');
240 if (!is_resource($fp)) {
241 throw new RuntimeException(
242 sprintf(
243 'Could not read file: %s',
244 $abs_path
245 )
246 );
247 }
248
249 if (fwrite($fp, $a_content) === false) {
250 fclose($fp);
251 throw new RuntimeException(
252 sprintf(
253 'Could not write file: %s',
254 $abs_path
255 )
256 );
257 }
258
259 fclose($fp);
260
261 return $name;
262 }
263
267 public function storeUploadedFile(array $file): string
268 {
269 $sanitized_filename = ilFileUtils::_sanitizeFilemame($file['name']);
270
271 $this->rotateFiles($this->getMailPath() . '/' . $this->user_id . '_' . $sanitized_filename);
272
274 $file['tmp_name'],
275 $sanitized_filename,
276 $this->getMailPath() . '/' . $this->user_id . '_' . $sanitized_filename
277 );
278
279 return $sanitized_filename;
280 }
281
288 public function copyAttachmentFile(string $a_abs_path, string $a_new_name): bool
289 {
290 @copy($a_abs_path, $this->getMailPath() . "/" . $this->user_id . "_" . $a_new_name);
291
292 return true;
293 }
294
295 private function rotateFiles(string $a_path): bool
296 {
297 if (is_file($a_path)) {
298 $this->rotateFiles($a_path . ".old");
299 return ilFileUtils::rename($a_path, $a_path . '.old');
300 }
301
302 return true;
303 }
304
309 public function unlinkFiles(array $a_filenames): string
310 {
311 foreach ($a_filenames as $file) {
312 if (!$this->unlinkFile($file)) {
313 return $file;
314 }
315 }
316
317 return '';
318 }
319
320 public function unlinkFile(string $a_filename): bool
321 {
322 if (is_file($this->mail_path . '/' . basename($this->user_id . '_' . $a_filename))) {
323 return unlink($this->mail_path . '/' . basename($this->user_id . '_' . $a_filename));
324 }
325
326 return false;
327 }
328
335 public function getAbsoluteAttachmentPoolPathByFilename(string $fileName): string
336 {
337 return $this->getAbsoluteAttachmentPoolPathPrefix() . $fileName;
338 }
339
345 public function saveFiles(int $a_mail_id, array $a_attachments): void
346 {
347 if (!is_numeric($a_mail_id) || $a_mail_id < 1) {
348 throw new InvalidArgumentException('The passed mail_id must be a valid integer!');
349 }
350
351 foreach ($a_attachments as $attachment) {
352 $this->saveFile($a_mail_id, $attachment);
353 }
354 }
355
356 public static function getStorage(int $a_mail_id, int $a_usr_id): ilFSStorageMail
357 {
358 static $fsstorage_cache = [];
359
360 $fsstorage_cache[$a_mail_id][$a_usr_id] = new ilFSStorageMail($a_mail_id, $a_usr_id);
361
362 return $fsstorage_cache[$a_mail_id][$a_usr_id];
363 }
364
371 public function saveFile(int $a_mail_id, string $a_attachment): bool
372 {
373 $oStorage = self::getStorage($a_mail_id, $this->user_id);
374 $oStorage->create();
375 $storage_directory = $oStorage->getAbsolutePath();
376
377 if (!is_dir($storage_directory)) {
378 return false;
379 }
380
381 return copy(
382 $this->mail_path . '/' . $this->user_id . '_' . $a_attachment,
383 $storage_directory . '/' . $a_attachment
384 );
385 }
386
391 public function checkFilesExist(array $a_files): bool
392 {
393 if ($a_files) {
394 foreach ($a_files as $file) {
395 if (!is_file($this->mail_path . '/' . $this->user_id . '_' . $file)) {
396 return false;
397 }
398 }
399 return true;
400 }
401 return true;
402 }
403
404 public function assignAttachmentsToDirectory(int $a_mail_id, int $a_sent_mail_id): void
405 {
406 global $ilDB;
407
408 $oStorage = self::getStorage($a_sent_mail_id, $this->user_id);
409 $res = $ilDB->manipulateF(
410 '
411 INSERT INTO mail_attachment
412 ( mail_id, path) VALUES (%s, %s)',
413 ['integer', 'text'],
414 [$a_mail_id, $oStorage->getRelativePathExMailDirectory()]
415 );
416 }
417
418 public function deassignAttachmentFromDirectory(int $a_mail_id): bool
419 {
420 global $ilDB;
421 // IF IT'S THE LAST MAIL CONTAINING THESE ATTACHMENTS => DELETE ATTACHMENTS
422 $res = $ilDB->query(
423 'SELECT path FROM mail_attachment WHERE mail_id = ' .
424 $ilDB->quote($a_mail_id, 'integer')
425 );
426
427 $path = '';
428 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
429 $path = (string) $row->path;
430 }
431
432 if ($path !== '') {
433 $res = $ilDB->query(
434 'SELECT COUNT(mail_id) count_mail_id FROM mail_attachment WHERE path = ' .
435 $ilDB->quote($path, 'text')
436 ) ;
437
438 $cnt_mail_id = 0;
439 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
440 $cnt_mail_id = (int) $row->count_mail_id;
441 }
442
443 if ($cnt_mail_id === 1) {
445 }
446 }
447
448 $ilDB->manipulateF(
449 'DELETE FROM mail_attachment WHERE mail_id = %s',
450 ['integer'],
451 [$a_mail_id]
452 );
453
454 return true;
455 }
456
457 private function deleteAttachmentDirectory(string $a_rel_path): void
458 {
459 ilFileUtils::delDir($this->mail_path . "/" . $a_rel_path);
460 }
461
462 protected function initAttachmentMaxUploadSize(): void
463 {
466 // Copy of ilFileInputGUI: begin
467 // get the value for the maximal uploadable filesize from the php.ini (if available)
468 $umf = ini_get("upload_max_filesize");
469 // get the value for the maximal post data from the php.ini (if available)
470 $pms = ini_get("post_max_size");
471
472 //convert from short-string representation to "real" bytes
473 $multiplier_a = ["K" => 1024, "M" => 1024 * 1024, "G" => 1024 * 1024 * 1024];
474
475 $umf_parts = preg_split(
476 "/(\d+)([K|G|M])/",
477 $umf,
478 -1,
479 PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
480 );
481 $pms_parts = preg_split(
482 "/(\d+)([K|G|M])/",
483 $pms,
484 -1,
485 PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
486 );
487
488 if (count($umf_parts) === 2) {
489 $umf = (float) $umf_parts[0] * $multiplier_a[$umf_parts[1]];
490 }
491 if (count($pms_parts) === 2) {
492 $pms = (float) $pms_parts[0] * $multiplier_a[$pms_parts[1]];
493 }
494
495 // use the smaller one as limit
496 $max_filesize = min($umf, $pms);
497
498 if (!$max_filesize) {
499 $max_filesize = max($umf, $pms);
500 }
501
502 $this->mail_max_upload_file_size = (int) $max_filesize;
503 }
504
505 public function onUserDelete(): void
506 {
510 global $ilDB;
511
512 // Delete uploaded mail files which are not attached to any message
513 try {
514 $iter = new RegexIterator(
515 new DirectoryIterator($this->getMailPath()),
516 '/^' . $this->user_id . '_/'
517 );
518 foreach ($iter as $file) {
523 if ($file->isFile()) {
524 @unlink($file->getPathname());
525 }
526 }
527 } catch (Exception $e) {
528 }
529
530 // Select all files attached to messages which are not shared (... = 1) with other messages anymore
531 $query = '
532 SELECT DISTINCT(ma1.path)
533 FROM mail_attachment ma1
534 INNER JOIN mail
535 ON mail.mail_id = ma1.mail_id
536 WHERE mail.user_id = %s
537 AND (SELECT COUNT(tmp.path) FROM mail_attachment tmp WHERE tmp.path = ma1.path) = 1
538 ';
539 $res = $ilDB->queryF(
540 $query,
541 ['integer'],
542 [$this->user_id]
543 );
544 while ($row = $ilDB->fetchAssoc($res)) {
545 try {
546 $path = $this->getMailPath() . DIRECTORY_SEPARATOR . $row['path'];
547 $iter = new RecursiveIteratorIterator(
548 new RecursiveDirectoryIterator($path),
549 RecursiveIteratorIterator::CHILD_FIRST
550 );
551 foreach ($iter as $file) {
556 if ($file->isDir()) {
557 @rmdir($file->getPathname());
558 } else {
559 @unlink($file->getPathname());
560 }
561 }
562 @rmdir($path);
563 } catch (Exception $e) {
564 }
565 }
566
567 // Delete each mail attachment rows assigned to a message of the deleted user.
568 $ilDB->manipulateF(
569 '
570 DELETE
571 FROM mail_attachment
572 WHERE EXISTS(
573 SELECT mail.mail_id
574 FROM mail
575 WHERE mail.user_id = %s AND mail.mail_id = mail_attachment.mail_id
576 )
577 ',
578 ['integer'],
579 [$this->user_id]
580 );
581 }
582
589 public function deliverAttachmentsAsZip(
590 string $basename,
591 int $mailId,
592 array $files = [],
593 bool $isDraft = false
594 ): void {
595 $path = '';
596 if (!$isDraft) {
597 $path = $this->getAttachmentPathByMailId($mailId);
598 if ($path === '') {
599 throw new ilException('mail_download_zip_no_attachments');
600 }
601 }
602
603 $downloadFilename = ilFileUtils::getASCIIFilename($basename);
604 if ($downloadFilename === '') {
605 $downloadFilename = 'attachments';
606 }
607
608 $processingDirectory = ilFileUtils::ilTempnam();
609 $relativeProcessingDirectory = basename($processingDirectory);
610
611 $absoluteZipDirectory = $processingDirectory . '/' . $downloadFilename;
612 $relativeZipDirectory = $relativeProcessingDirectory . '/' . $downloadFilename;
613
614 $this->tmpDirectory->createDir($relativeZipDirectory);
615
616 foreach ($files as $fileName) {
617 if ($isDraft) {
618 $source = str_replace(
619 $this->mail_path,
620 MAILPATH,
622 );
623 } else {
624 $source = MAILPATH . '/' . $path . '/' . $fileName;
625 }
626
627 $source = str_replace('//', '/', $source);
628 if (!$this->storageDirectory->has($source)) {
629 continue;
630 }
631
632 $target = $relativeZipDirectory . '/' . $fileName;
633
634 $stream = $this->storageDirectory->readStream($source);
635 $this->tmpDirectory->writeStream($target, $stream);
636 }
637
638 $pathToZipFile = $processingDirectory . '/' . $downloadFilename . '.zip';
639 ilFileUtils::zip($absoluteZipDirectory, $pathToZipFile);
640
641 $this->tmpDirectory->deleteDir($relativeZipDirectory);
642
644 $processingDirectory . '/' . $downloadFilename . '.zip',
645 ilFileUtils::getValidFilename($downloadFilename . '.zip')
646 );
647 }
648}
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This class handles all operations on files (attachments) in directory ilias_data/mail.
getAbsoluteAttachmentPoolPathByFilename(string $fileName)
Resolves a path for a passed filename in regards of a user's mail attachment pool,...
deleteAttachmentDirectory(string $a_rel_path)
Filesystem $storageDirectory
unlinkFiles(array $a_filenames)
static getStorage(int $a_mail_id, int $a_usr_id)
getAttachmentPath(string $a_filename, int $a_mail_id)
storeUploadedFile(array $file)
deliverAttachmentsAsZip(string $basename, int $mailId, array $files=[], bool $isDraft=false)
saveFile(int $a_mail_id, string $a_attachment)
Save attachment file in a specific mail directory .../mail/<calculated_path>/mail_<mail_id>_<user_id>...
checkFilesExist(array $a_files)
unlinkFile(string $a_filename)
assignAttachmentsToDirectory(int $a_mail_id, int $a_sent_mail_id)
saveFiles(int $a_mail_id, array $a_attachments)
Saves all attachment files in a specific mail directory .../mail/<calculated_path>/mail_<mail_id>_<us...
getAttachmentPathAndFilenameByMd5Hash(string $md5FileHash, int $mailId)
deassignAttachmentFromDirectory(int $a_mail_id)
__construct(int $a_user_id=0)
copyAttachmentFile(string $a_abs_path, string $a_new_name)
Copy files in mail directory.
adoptAttachments(array $a_attachments, int $a_mail_id)
Adopt attachments (in case of forwarding a mail)
storeAsAttachment(string $a_filename, string $a_content)
getAttachmentPathByMailId(int $mailId)
rotateFiles(string $a_path)
static deliverFileAttached(string $path_to_file, ?string $download_file_name=null, ?string $mime_type=null, bool $delete_file=false)
static getASCIIFilename(string $a_filename)
static getDir(string $a_dir, bool $a_rec=false, ?string $a_sub_dir="")
get directory
static zip(string $a_dir, string $a_file, bool $compress_content=false)
zips given directory/file into given zip.file
static ilTempnam(?string $a_temp_path=null)
Returns a unique and non existing Path for e temporary file or directory.
static rename(string $a_source, string $a_target)
static delDir(string $a_dir, bool $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
static getValidFilename(string $a_filename)
static _sanitizeFilemame(string $a_filename)
static moveUploadedFile(string $a_file, string $a_name, string $a_target, bool $a_raise_errors=true, string $a_mode="move_uploaded")
move uploaded file
string $path
const MAILPATH
Definition: constants.php:50
global $DIC
Definition: feed.php:28
$rest
Definition: goto.php:49
Interface Filesystem.
Definition: Filesystem.php:40
Interface ilDBInterface.
$res
Definition: ltiservices.php:69
if($format !==null) $name
Definition: metadata.php:247
$source
Definition: metadata.php:93
Class FlySystemFileAccessTest \Provider\FlySystem @runTestsInSeparateProcesses @preserveGlobalState d...
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
Class ChatMainBarProvider \MainMenu\Provider.
header include for all ilias files.
$query