ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
class.ilFileDataMail.php
Go to the documentation of this file.
1 <?php
2 
19 declare(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 
314  public function getAbsoluteAttachmentPoolPathByFilename(string $filename): string
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(
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 }
deliverAttachmentsAsZip(string $basename, int $mail_id, array $files=[], bool $is_draft=false)
getAttachmentPathAndFilenameByMd5Hash(string $md5FileHash, int $mail_id)
$res
Definition: ltiservices.php:66
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
storeAsAttachment(string $a_filename, string $a_content)
Filesystem $storage_directory
Interface Observer Contains several chained tasks and infos about them.
adoptAttachments(array $a_attachments, int $a_mail_id)
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.
static getValidFilename(string $a_filename)
const MAILPATH
Definition: constants.php:50
copyAttachmentFile(string $a_abs_path, string $a_new_name)
storeUploadedFile(UploadResult $result)
static getASCIIFilename(string $a_filename)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
static getStorage(int $a_mail_id, int $a_usr_id)
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)
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
global $DIC
Definition: shib_login.php:26
unlinkFiles(array $a_filenames)
Class ilObjForumAdministration.
getAttachmentPathByMailId(int $mail_id)
static getDir(string $a_dir, bool $a_rec=false, ?string $a_sub_dir="")
get directory
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)
$filename
Definition: buildRTE.php:78
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)
__construct(public int $user_id=0)
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)
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>...
string $path
deleteAttachmentDirectory(string $a_rel_path)