ILIAS  release_8 Revision v8.19
All Data Structures Namespaces Files Functions Variables Modules Pages
MailDeletionHandler.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
23 use ilLoggerFactory;
24 use ilSetting;
25 use ilDBStatement;
26 use ilLogger;
27 use ilDBConstants;
28 use ilFileUtils;
30 use ilDBInterface;
31 use Throwable;
34 use SplFileInfo;
35 
37 {
38  private const PING_THRESHOLD = 250;
39 
42  private ilDBInterface $db;
44  private ilLogger $logger;
48 
49  public function __construct(
52  ?ilDBInterface $db = null,
53  ?ilSetting $setting = null,
54  ?ilLogger $logger = null,
55  ?callable $delete_directory_callback = null
56  ) {
57  global $DIC;
58 
59  $this->db = $db ?? $DIC->database();
60  $this->settings = $setting ?? $DIC->settings();
61  $this->logger = $logger ?? ilLoggerFactory::getLogger('mail');
62  $this->delete_directory_callback = $delete_directory_callback;
63 
64  $this->job = $job;
65  $this->collector = $collector;
66 
67  $this->mail_ids_for_path_stmt = $this->db->prepare(
68  'SELECT COUNT(*) cnt FROM mail_attachment WHERE path = ?',
70  );
71  }
72 
76  private function determineDeletableAttachmentPaths(): array
77  {
78  $attachment_paths = [];
79 
80  $res = $this->db->query(
81  '
82  SELECT path, COUNT(mail_id) cnt_mail_ids
83  FROM mail_attachment
84  WHERE ' . $this->db->in(
85  'mail_id',
86  $this->collector->mailIdsToDelete(),
87  false,
89  ) . ' GROUP BY path'
90  );
91 
92  $i = 0;
93  while ($row = $this->db->fetchAssoc($res)) {
94  if ($i > 0 && $i % self::PING_THRESHOLD) {
95  $this->job->ping();
96  }
97 
98  $num_usages_total = (int) $this->db->fetchAssoc(
99  $this->db->execute(
100  $this->mail_ids_for_path_stmt,
101  [$row['path']]
102  )
103  )['cnt'];
104  $num_usages_within_deleted_mails = (int) $row['cnt_mail_ids'];
105 
106  if ($num_usages_within_deleted_mails >= $num_usages_total) {
107  $attachment_paths[] = $row['path'];
108  }
109 
110  ++$i;
111  }
112 
113  return $attachment_paths;
114  }
115 
116  private function deleteDirectory(string $directory): void
117  {
118  if ($this->delete_directory_callback !== null) {
119  call_user_func($this->delete_directory_callback, $directory);
120  } else {
121  ilFileUtils::delDir($directory);
122  }
123  }
124 
125  private function deleteAttachments(): void
126  {
127  $attachment_paths = $this->determineDeletableAttachmentPaths();
128 
129  $i = 0;
130  foreach ($attachment_paths as $path) {
131  if ($i > 0 && $i % self::PING_THRESHOLD) {
132  $this->job->ping();
133  }
134 
135  try {
136  $path = CLIENT_DATA_DIR . '/mail/' . $path;
137  $iter = new RecursiveIteratorIterator(
138  new RecursiveDirectoryIterator($path),
139  RecursiveIteratorIterator::CHILD_FIRST
140  );
141 
142  foreach ($iter as $file) {
145  $path_name = $file->getPathname();
146  if ($file->isDir()) {
147  $this->deleteDirectory($path_name);
148  $this->logger->info(
149  sprintf(
150  "Attachment directory '%s' deleted",
151  $path_name
152  )
153  );
154  } elseif (is_file($path_name) && unlink($path_name)) {
155  $this->logger->info(
156  sprintf(
157  "Attachment file '%s' deleted",
158  $path_name
159  )
160  );
161  } else {
162  $this->logger->info(
163  sprintf(
164  "Attachment file '%s' for mail_id could not be deleted " .
165  "due to missing file system permissions",
166  $path_name
167  )
168  );
169  }
170  }
171 
172  $this->deleteDirectory($path);
173  $this->logger->info(
174  sprintf(
175  "Attachment directory '%s' deleted",
176  $path
177  )
178  );
179  } catch (Throwable $e) {
180  $this->logger->warning($e->getMessage());
181  $this->logger->warning($e->getTraceAsString());
182  } finally {
183  ++$i;
184  }
185  }
186 
187  $this->db->manipulate(
188  'DELETE FROM mail_attachment WHERE ' .
189  $this->db->in('mail_id', $this->collector->mailIdsToDelete(), false, ilDBConstants::T_INTEGER)
190  );
191  }
192 
193  private function deleteMails(): void
194  {
195  $this->db->manipulate(
196  'DELETE FROM mail WHERE ' .
197  $this->db->in('mail_id', $this->collector->mailIdsToDelete(), false, ilDBConstants::T_INTEGER)
198  );
199  }
200 
201  private function deleteMarkedAsNotified(): void
202  {
203  if ((int) $this->settings->get('mail_notify_orphaned', '0') >= 1) {
204  $this->db->manipulate(
205  'DELETE FROM mail_cron_orphaned WHERE ' .
206  $this->db->in('mail_id', $this->collector->mailIdsToDelete(), false, ilDBConstants::T_INTEGER)
207  );
208  } else {
209  $this->db->manipulate('DELETE FROM mail_cron_orphaned');
210  }
211  }
212 
213  public function delete(): void
214  {
215  if (count($this->collector->mailIdsToDelete()) > 0) {
216  $this->deleteAttachments();
217 
218  $this->deleteMails();
219 
220  $this->logger->info(
221  sprintf(
222  'Deleted mail_ids: %s',
223  implode(', ', $this->collector->mailIdsToDelete())
224  )
225  );
226 
227  $this->deleteMarkedAsNotified();
228  $this->logger->info(
229  sprintf(
230  'Deleted mail_cron_orphaned mail_ids: %s',
231  implode(', ', $this->collector->mailIdsToDelete())
232  )
233  );
234  }
235  }
236 }
$res
Definition: ltiservices.php:69
static getLogger(string $a_component_id)
Get component logger.
$path
Definition: ltiservices.php:32
global $DIC
Definition: feed.php:28
ilSetting $setting
Definition: class.ilias.php:54
const CLIENT_DATA_DIR
Definition: constants.php:46
static delDir(string $a_dir, bool $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
__construct(ilMailCronOrphanedMails $job, ExpiredOrOrphanedMailsCollector $collector, ?ilDBInterface $db=null, ?ilSetting $setting=null, ?ilLogger $logger=null, ?callable $delete_directory_callback=null)
$i
Definition: metadata.php:41