ILIAS  release_8 Revision v8.19
All Data Structures Namespaces Files Functions Variables Modules Pages
class.ilFileDataMail.php
Go to the documentation of this file.
1 <?php
2 
19 declare(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 
234  $name = ilFileUtils::_sanitizeFilemame($a_filename);
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(
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 }
getAttachmentPathAndFilenameByMd5Hash(string $md5FileHash, int $mailId)
$res
Definition: ltiservices.php:69
$rest
Definition: goto.php:49
Filesystem $storageDirectory
storeAsAttachment(string $a_filename, string $a_content)
This class handles all operations on files (attachments) in directory ilias_data/mail.
Class ChatMainBarProvider .
adoptAttachments(array $a_attachments, int $a_mail_id)
Adopt attachments (in case of forwarding a mail)
static getValidFilename(string $a_filename)
const MAILPATH
Definition: constants.php:50
copyAttachmentFile(string $a_abs_path, string $a_new_name)
Copy files in mail directory.
__construct(int $a_user_id=0)
static getASCIIFilename(string $a_filename)
getAttachmentPathByMailId(int $mailId)
global $DIC
Definition: feed.php:28
static getStorage(int $a_mail_id, int $a_usr_id)
if($format !==null) $name
Definition: metadata.php:247
unlinkFile(string $a_filename)
static deliverFileAttached(string $path_to_file, ?string $download_file_name=null, ?string $mime_type=null, bool $delete_file=false)
assignAttachmentsToDirectory(int $a_mail_id, int $a_sent_mail_id)
rotateFiles(string $a_path)
getAbsoluteAttachmentPoolPathByFilename(string $fileName)
Resolves a path for a passed filename in regards of a user&#39;s mail attachment pool, meaning attachments not being sent.
deassignAttachmentFromDirectory(int $a_mail_id)
static delDir(string $a_dir, bool $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
deliverAttachmentsAsZip(string $basename, int $mailId, array $files=[], bool $isDraft=false)
unlinkFiles(array $a_filenames)
header include for all ilias files.
static getDir(string $a_dir, bool $a_rec=false, ?string $a_sub_dir="")
get directory
$query
static moveUploadedFile(string $a_file, string $a_name, string $a_target, bool $a_raise_errors=true, string $a_mode="move_uploaded")
move uploaded file
static _sanitizeFilemame(string $a_filename)
static ilTempnam(?string $a_temp_path=null)
Returns a unique and non existing Path for e temporary file or directory.
__construct(Container $dic, ilPlugin $plugin)
static zip(string $a_dir, string $a_file, bool $compress_content=false)
zips given directory/file into given zip.file
storeUploadedFile(array $file)
static rename(string $a_source, string $a_target)
saveFiles(int $a_mail_id, array $a_attachments)
Saves all attachment files in a specific mail directory .../mail/<calculated_path>/mail_<mail_id>_<us...
getAttachmentPath(string $a_filename, int $a_mail_id)
$source
Definition: metadata.php:93
checkFilesExist(array $a_files)
saveFile(int $a_mail_id, string $a_attachment)
Save attachment file in a specific mail directory .../mail/<calculated_path>/mail_<mail_id>_<user_id>...
Class FlySystemFileAccessTest disabled disabled disabled.
string $path
deleteAttachmentDirectory(string $a_rel_path)