ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilFileDataMail.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
23
25{
26 public string $mail_path;
30 protected ilDBInterface $db;
31 protected ILIAS $ilias;
32
33 public function __construct(public int $user_id = 0)
34 {
35 global $DIC;
36
37 if (!defined('MAILPATH')) {
38 define('MAILPATH', 'mail');
39 }
41 $this->mail_path = $this->getPath() . '/' . MAILPATH;
42 $this->ilias = $DIC['ilias'];
43 $this->db = $DIC->database();
44 $this->tmp_directory = $DIC->filesystem()->temp();
45 $this->storage_directory = $DIC->filesystem()->storage();
46
47 $this->checkReadWrite();
49 }
50
51 public function initDirectory(): bool
52 {
53 if (is_writable($this->getPath())
54 && mkdir($this->getPath() . '/' . MAILPATH)
55 && chmod($this->getPath() . '/' . MAILPATH, 0755)) {
56 $this->mail_path = $this->getPath() . '/' . MAILPATH;
57 return true;
58 }
59
60 return false;
61 }
62
63 public function getUploadLimit(): int
64 {
66 }
67
68 public function getAttachmentsTotalSizeLimit(): ?float
69 {
70 $max_size = $this->ilias->getSetting('mail_maxsize_attach', '');
71 if ($max_size === '') {
72 return null;
73 }
74
75 return (float) $this->ilias->getSetting('mail_maxsize_attach', '0') * 1024;
76 }
77
78 public function getMailPath(): string
79 {
80 return $this->mail_path;
81 }
82
83 public function getAbsoluteAttachmentPoolPathPrefix(): string
84 {
85 return $this->mail_path . '/' . $this->user_id . '_';
86 }
87
92 public function getAttachmentPathAndFilenameByMd5Hash(string $md5FileHash, int $mail_id): array
93 {
94 $res = $this->db->queryF(
95 'SELECT path FROM mail_attachment WHERE mail_id = %s',
96 ['integer'],
97 [$mail_id]
98 );
99
100 if ($this->db->numRows($res) !== 1) {
101 throw new OutOfBoundsException();
102 }
103
104 $row = $this->db->fetchAssoc($res);
105
106 $relative_path = $row['path'];
107 $path = $this->getMailPath() . '/' . $row['path'];
108
109 $files = ilFileUtils::getDir($path);
110 foreach ($files as $file) {
111 if ($file['type'] === 'file' && md5($file['entry']) === $md5FileHash) {
112 return [
113 'path' => $this->getMailPath() . '/' . $relative_path . '/' . $file['entry'],
114 'filename' => $file['entry'],
115 ];
116 }
117 }
118
119 throw new OutOfBoundsException();
120 }
121
122
123 private function getAttachmentPathByMailId(int $mail_id): string
124 {
125 $query = $this->db->query(
126 'SELECT path FROM mail_attachment WHERE mail_id = ' . $this->db->quote($mail_id, 'integer')
127 );
128
129 while ($row = $this->db->fetchObject($query)) {
130 return $row->path;
131 }
132
133 return '';
134 }
135
136 public function getAttachmentPath(string $a_filename, int $a_mail_id): string
137 {
138 $path = $this->getMailPath() . '/' . $this->getAttachmentPathByMailId($a_mail_id) . '/' . $a_filename;
139
140 if (is_readable($path)) {
141 return $path;
142 }
143
144 return '';
145 }
146
150 public function adoptAttachments(array $a_attachments, int $a_mail_id): string
151 {
152 foreach ($a_attachments as $file) {
153 $path = $this->getAttachmentPath($file, $a_mail_id);
154 if (!copy($path, $this->getMailPath() . '/' . $this->user_id . '_' . $file)) {
155 return 'ERROR: ' . $this->getMailPath() . '/' . $this->user_id . '_' . $file . ' cannot be created';
156 }
157 }
158
159 return '';
160 }
161
162 public function checkReadWrite(): bool
163 {
164 if (is_writable($this->mail_path) && is_readable($this->mail_path)) {
165 return true;
166 }
167
168 $this->ilias->raiseError(
169 'Mail directory is not readable/writable by webserver: ' .
170 $this->mail_path,
171 $this->ilias->error_obj->FATAL
172 );
173
174 return false;
175 }
176
180 public function getUserFilesData(): array
181 {
182 return $this->getUnsentFiles();
183 }
184
188 private function getUnsentFiles(): array
189 {
190 $files = [];
191
192 $iter = new RegexIterator(new DirectoryIterator($this->mail_path), "/^{$this->user_id}_(.+)$/");
193 foreach ($iter as $file) {
195 if (!$file->isFile()) {
196 continue;
197 }
198
199 [$uid, $rest] = explode('_', $file->getFilename(), 2);
200 if ($uid === (string) $this->user_id) {
201 $files[] = [
202 'name' => $rest,
203 'size' => $file->getSize(),
204 'ctime' => $file->getCTime(),
205 ];
206 }
207 }
208
209 return $files;
210 }
211
212 public function storeAsAttachment(string $a_filename, string $a_content): string
213 {
214 if (strlen($a_content) >= $this->getUploadLimit()) {
215 throw new DomainException(
216 sprintf(
217 'Mail upload limit reached for user with id %s',
218 $this->user_id
219 )
220 );
221 }
222
223 $name = ilFileUtils::_sanitizeFilemame($a_filename);
224 $this->rotateFiles($this->getMailPath() . '/' . $this->user_id . '_' . $name);
225
226 $abs_path = $this->getMailPath() . '/' . $this->user_id . '_' . $name;
227
228 $fp = fopen($abs_path, 'wb+');
229 if (!is_resource($fp)) {
230 throw new RuntimeException(
231 sprintf(
232 'Could not read file: %s',
233 $abs_path
234 )
235 );
236 }
237
238 if (fwrite($fp, $a_content) === false) {
239 fclose($fp);
240 throw new RuntimeException(
241 sprintf(
242 'Could not write file: %s',
243 $abs_path
244 )
245 );
246 }
247
248 fclose($fp);
249
250 return $name;
251 }
252
253 public function storeUploadedFile(UploadResult $result): string
254 {
256 $result->getName()
257 );
258
259 $this->rotateFiles($this->getMailPath() . '/' . $this->user_id . '_' . $filename);
260
262 $result->getPath(),
263 $filename,
264 $this->getMailPath() . '/' . $this->user_id . '_' . $filename
265 );
266
267 return $filename;
268 }
269
270 public function copyAttachmentFile(string $a_abs_path, string $a_new_name): bool
271 {
272 @copy($a_abs_path, $this->getMailPath() . '/' . $this->user_id . '_' . $a_new_name);
273
274 return true;
275 }
276
277 private function rotateFiles(string $a_path): bool
278 {
279 if (is_file($a_path)) {
280 $this->rotateFiles($a_path . '.old');
281 return ilFileUtils::rename($a_path, $a_path . '.old');
282 }
283
284 return true;
285 }
286
290 public function unlinkFiles(array $a_filenames): string
291 {
292 foreach ($a_filenames as $file) {
293 if (!$this->unlinkFile($file)) {
294 return $file;
295 }
296 }
297
298 return '';
299 }
300
301 public function unlinkFile(string $a_filename): bool
302 {
303 if (is_file($this->mail_path . '/' . basename($this->user_id . '_' . $a_filename))) {
304 return unlink($this->mail_path . '/' . basename($this->user_id . '_' . $a_filename));
305 }
306
307 return false;
308 }
309
315 {
317 }
318
323 public function saveFiles(int $a_mail_id, array $a_attachments): void
324 {
325 if (!is_numeric($a_mail_id) || $a_mail_id < 1) {
326 throw new InvalidArgumentException('The passed mail_id must be a valid integer!');
327 }
328
329 foreach ($a_attachments as $attachment) {
330 $this->saveFile($a_mail_id, $attachment);
331 }
332 }
333
334 public static function getStorage(int $a_mail_id, int $a_usr_id): ilFSStorageMail
335 {
336 static $fsstorage_cache = [];
337
338 $fsstorage_cache[$a_mail_id][$a_usr_id] = new ilFSStorageMail($a_mail_id, $a_usr_id);
339
340 return $fsstorage_cache[$a_mail_id][$a_usr_id];
341 }
342
346 public function saveFile(int $a_mail_id, string $a_attachment): bool
347 {
348 $storage = self::getStorage($a_mail_id, $this->user_id);
349 $storage->create();
350 $storage_directory = $storage->getAbsolutePath();
351
352 if (!is_dir($storage_directory)) {
353 return false;
354 }
355
356 return copy(
357 $this->mail_path . '/' . $this->user_id . '_' . $a_attachment,
358 $storage_directory . '/' . $a_attachment
359 );
360 }
361
365 public function checkFilesExist(array $a_files): bool
366 {
367 if ($a_files !== []) {
368 foreach ($a_files as $file) {
369 if (!is_file($this->mail_path . '/' . $this->user_id . '_' . $file)) {
370 return false;
371 }
372 }
373 }
374
375 return true;
376 }
377
378 public function assignAttachmentsToDirectory(int $a_mail_id, int $a_sent_mail_id): void
379 {
380 $storage = self::getStorage($a_sent_mail_id, $this->user_id);
381 $this->db->manipulateF(
382 '
383 INSERT INTO mail_attachment
384 ( mail_id, path) VALUES (%s, %s)',
385 ['integer', 'text'],
386 [$a_mail_id, $storage->getRelativePathExMailDirectory()]
387 );
388 }
389
390 public function deassignAttachmentFromDirectory(int $a_mail_id): bool
391 {
392 $res = $this->db->query(
393 'SELECT path FROM mail_attachment WHERE mail_id = ' . $this->db->quote($a_mail_id, 'integer')
394 );
395
396 $path = '';
397 while ($row = $this->db->fetchObject($res)) {
398 $path = (string) $row->path;
399 }
400
401 if ($path !== '') {
402 $res = $this->db->query(
403 'SELECT COUNT(mail_id) count_mail_id FROM mail_attachment WHERE path = ' .
404 $this->db->quote($path, 'text')
405 ) ;
406
407 $cnt_mail_id = 0;
408 while ($row = $this->db->fetchObject($res)) {
409 $cnt_mail_id = (int) $row->count_mail_id;
410 }
411
412 if ($cnt_mail_id === 1) {
414 }
415 }
416
417 $this->db->manipulateF(
418 'DELETE FROM mail_attachment WHERE mail_id = %s',
419 ['integer'],
420 [$a_mail_id]
421 );
422
423 return true;
424 }
425
426 private function deleteAttachmentDirectory(string $a_rel_path): void
427 {
428 ilFileUtils::delDir($this->mail_path . '/' . $a_rel_path);
429 }
430
431 protected function initAttachmentMaxUploadSize(): void
432 {
435 // Copy of ilFileInputGUI: begin
436 // get the value for the maximal uploadable filesize from the php.ini (if available)
437 $umf = ini_get('upload_max_filesize');
438 // get the value for the maximal post data from the php.ini (if available)
439 $pms = ini_get('post_max_size');
440
441 //convert from short-string representation to "real" bytes
442 $multiplier_a = ['K' => 1024, 'M' => 1024 * 1024, 'G' => 1024 * 1024 * 1024];
443
444 $umf_parts = preg_split(
445 "/(\d+)([K|G|M])/",
446 (string) $umf,
447 -1,
448 PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
449 );
450 $pms_parts = preg_split(
451 "/(\d+)([K|G|M])/",
452 (string) $pms,
453 -1,
454 PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
455 );
456
457 if ((is_countable($umf_parts) ? count($umf_parts) : 0) === 2) {
458 $umf = (float) $umf_parts[0] * $multiplier_a[$umf_parts[1]];
459 }
460 if ((is_countable($pms_parts) ? count($pms_parts) : 0) === 2) {
461 $pms = (float) $pms_parts[0] * $multiplier_a[$pms_parts[1]];
462 }
463
464 // use the smaller one as limit
465 $max_filesize = min($umf, $pms);
466
467 if (!$max_filesize) {
468 $max_filesize = max($umf, $pms);
469 }
470
471 $this->mail_max_upload_file_size = (int) $max_filesize;
472 }
473
474 public function onUserDelete(): void
475 {
476 // Delete uploaded mail files which are not attached to any message
477 try {
478 $iter = new RegexIterator(
479 new DirectoryIterator($this->getMailPath()),
480 '/^' . $this->user_id . '_/'
481 );
482 foreach ($iter as $file) {
484 if ($file->isFile()) {
485 @unlink($file->getPathname());
486 }
487 }
488 } catch (Exception) {
489 }
490
491 // Select all files attached to messages which are not shared (... = 1) with other messages anymore
492 $query = '
493 SELECT DISTINCT(ma1.path)
494 FROM mail_attachment ma1
495 INNER JOIN mail
496 ON mail.mail_id = ma1.mail_id
497 WHERE mail.user_id = %s
498 AND (SELECT COUNT(tmp.path) FROM mail_attachment tmp WHERE tmp.path = ma1.path) = 1
499 ';
500 $res = $this->db->queryF(
501 $query,
502 ['integer'],
503 [$this->user_id]
504 );
505 while ($row = $this->db->fetchAssoc($res)) {
506 try {
507 $path = $this->getMailPath() . DIRECTORY_SEPARATOR . $row['path'];
508 $iter = new RecursiveIteratorIterator(
509 new RecursiveDirectoryIterator($path),
510 RecursiveIteratorIterator::CHILD_FIRST
511 );
512 foreach ($iter as $file) {
514 if ($file->isDir()) {
515 @rmdir($file->getPathname());
516 } else {
517 @unlink($file->getPathname());
518 }
519 }
520 @rmdir($path);
521 } catch (Exception) {
522 }
523 }
524
525 // Delete each mail attachment rows assigned to a message of the deleted user.
526 $this->db->manipulateF(
527 '
528 DELETE
529 FROM mail_attachment
530 WHERE EXISTS(
531 SELECT mail.mail_id
532 FROM mail
533 WHERE mail.user_id = %s AND mail.mail_id = mail_attachment.mail_id
534 )
535 ',
536 ['integer'],
537 [$this->user_id]
538 );
539 }
540
544 public function deliverAttachmentsAsZip(
545 string $basename,
546 int $mail_id,
547 array $files = [],
548 bool $is_draft = false
549 ): void {
550 $path = '';
551 if (!$is_draft) {
552 $path = $this->getAttachmentPathByMailId($mail_id);
553 if ($path === '') {
554 throw new ilMailException('mail_download_zip_no_attachments');
555 }
556 }
557
558 $download_filename = ilFileUtils::getASCIIFilename($basename);
559 if ($download_filename === '') {
560 $download_filename = 'attachments';
561 }
562
563 $processing_directory = ilFileUtils::ilTempnam();
564 $relative_processing_directory = basename($processing_directory);
565
566 $absolute_zip_directory = $processing_directory . '/' . $download_filename;
567 $relative_zip_directory = $relative_processing_directory . '/' . $download_filename;
568
569 $this->tmp_directory->createDir($relative_zip_directory);
570
571 foreach ($files as $filename) {
572 if ($is_draft) {
573 $source = str_replace(
574 $this->mail_path,
575 MAILPATH,
577 );
578 } else {
579 $source = MAILPATH . '/' . $path . '/' . $filename;
580 }
581
582 $source = str_replace('//', '/', $source);
583 if (!$this->storage_directory->has($source)) {
584 continue;
585 }
586
587 $target = $relative_zip_directory . '/' . $filename;
588
589 $stream = $this->storage_directory->readStream($source);
590 $this->tmp_directory->writeStream($target, $stream);
591 }
592
593 $path_to_zip_file = $processing_directory . '/' . $download_filename . '.zip';
594 ilFileUtils::zip($absolute_zip_directory, $path_to_zip_file);
595
596 $this->tmp_directory->deleteDir($relative_zip_directory);
597
599 $processing_directory . '/' . $download_filename . '.zip',
600 ilFileUtils::getValidFilename($download_filename . '.zip')
601 );
602 }
603}
$filename
Definition: buildRTE.php:78
deleteAttachmentDirectory(string $a_rel_path)
__construct(public int $user_id=0)
unlinkFiles(array $a_filenames)
static getStorage(int $a_mail_id, int $a_usr_id)
getAttachmentPath(string $a_filename, int $a_mail_id)
Filesystem $storage_directory
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...
getAttachmentPathByMailId(int $mail_id)
storeUploadedFile(UploadResult $result)
deassignAttachmentFromDirectory(int $a_mail_id)
deliverAttachmentsAsZip(string $basename, int $mail_id, array $files=[], bool $is_draft=false)
copyAttachmentFile(string $a_abs_path, string $a_new_name)
adoptAttachments(array $a_attachments, int $a_mail_id)
getAbsoluteAttachmentPoolPathByFilename(string $filename)
Resolves a path for a passed filename in regards of a user's mail attachment pool,...
storeAsAttachment(string $a_filename, string $a_content)
getAttachmentPathAndFilenameByMd5Hash(string $md5FileHash, int $mail_id)
rotateFiles(string $a_path)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
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)
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
The filesystem interface provides the public interface for the Filesystem service API consumer.
Definition: Filesystem.php:37
Interface ilDBInterface.
$res
Definition: ltiservices.php:69
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
Interface Observer \BackgroundTasks Contains several chained tasks and infos about them.
Class ilObjForumAdministration.
global $DIC
Definition: shib_login.php:26
PREG_SPLIT_NO_EMPTY PREG_SPLIT_DELIM_CAPTURE