ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
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 int PING_THRESHOLD = 250;
39 
40  private readonly ilDBInterface $db;
41  private readonly ilSetting $settings;
42  private readonly ilLogger $logger;
46 
50  public function __construct(
51  private readonly ilMailCronOrphanedMails $job,
52  private readonly ExpiredOrOrphanedMailsCollector $collector,
53  ?ilDBInterface $db = null,
55  ?ilLogger $logger = null,
57  ) {
58  global $DIC;
59 
60  $this->db = $db ?? $DIC->database();
61  $this->settings = $setting ?? $DIC->settings();
62  $this->logger = $logger ?? ilLoggerFactory::getLogger('mail');
63  $this->delete_directory_callback = $delete_directory_callback;
64 
65  $this->mail_ids_for_path_stmt = $this->db->prepare(
66  'SELECT COUNT(*) cnt FROM mail_attachment WHERE path = ?',
68  );
69  }
70 
74  private function determineDeletableAttachmentPaths(): array
75  {
76  $attachment_paths = [];
77 
78  $res = $this->db->query(
79  '
80  SELECT path, COUNT(mail_id) cnt_mail_ids
81  FROM mail_attachment
82  WHERE ' . $this->db->in(
83  'mail_id',
84  $this->collector->mailIdsToDelete(),
85  false,
87  ) . ' GROUP BY path'
88  );
89 
90  $i = 0;
91  while ($row = $this->db->fetchAssoc($res)) {
92  if ($i > 0 && $i % self::PING_THRESHOLD) {
93  $this->job->ping();
94  }
95 
96  $num_usages_total = (int) $this->db->fetchAssoc(
97  $this->db->execute(
98  $this->mail_ids_for_path_stmt,
99  [$row['path']]
100  )
101  )['cnt'];
102  $num_usages_within_deleted_mails = (int) $row['cnt_mail_ids'];
103 
104  if ($num_usages_within_deleted_mails >= $num_usages_total) {
105  $attachment_paths[] = $row['path'];
106  }
107 
108  ++$i;
109  }
110 
111  return $attachment_paths;
112  }
113 
114  private function deleteDirectory(string $directory): void
115  {
116  if ($this->delete_directory_callback !== null) {
117  \call_user_func($this->delete_directory_callback, $directory);
118  } else {
119  ilFileUtils::delDir($directory);
120  }
121  }
122 
123  private function deleteAttachments(): void
124  {
125  $attachment_paths = $this->determineDeletableAttachmentPaths();
126 
127  $i = 0;
128  foreach ($attachment_paths as $path) {
129  if ($i > 0 && $i % self::PING_THRESHOLD) {
130  $this->job->ping();
131  }
132 
133  try {
134  $path = CLIENT_DATA_DIR . '/mail/' . $path;
135  $iter = new RecursiveIteratorIterator(
136  new RecursiveDirectoryIterator($path),
137  RecursiveIteratorIterator::CHILD_FIRST
138  );
139 
140  foreach ($iter as $file) {
143  $path_name = $file->getPathname();
144  if ($file->isDir()) {
145  $this->deleteDirectory($path_name);
146  $this->logger->info(
147  \sprintf(
148  "Attachment directory '%s' deleted",
149  $path_name
150  )
151  );
152  } elseif (is_file($path_name) && unlink($path_name)) {
153  $this->logger->info(
154  \sprintf(
155  "Attachment file '%s' deleted",
156  $path_name
157  )
158  );
159  } else {
160  $this->logger->info(
161  \sprintf(
162  'Attachment file \'%s\' for mail_id could not be deleted due to missing file system permissions',
163  $path_name
164  )
165  );
166  }
167  }
168 
169  $this->deleteDirectory($path);
170  $this->logger->info(
171  \sprintf(
172  "Attachment directory '%s' deleted",
173  $path
174  )
175  );
176  } catch (Throwable $e) {
177  $this->logger->warning($e->getMessage());
178  $this->logger->warning($e->getTraceAsString());
179  } finally {
180  ++$i;
181  }
182  }
183 
184  $this->db->manipulate(
185  'DELETE FROM mail_attachment WHERE ' .
186  $this->db->in('mail_id', $this->collector->mailIdsToDelete(), false, ilDBConstants::T_INTEGER)
187  );
188  }
189 
190  private function deleteMails(): void
191  {
192  $this->db->manipulate(
193  'DELETE FROM mail WHERE ' .
194  $this->db->in('mail_id', $this->collector->mailIdsToDelete(), false, ilDBConstants::T_INTEGER)
195  );
196  }
197 
198  private function deleteMarkedAsNotified(): void
199  {
200  if ((int) $this->settings->get('mail_notify_orphaned', '0') >= 1) {
201  $this->db->manipulate(
202  'DELETE FROM mail_cron_orphaned WHERE ' .
203  $this->db->in('mail_id', $this->collector->mailIdsToDelete(), false, ilDBConstants::T_INTEGER)
204  );
205  } else {
206  $this->db->manipulate('DELETE FROM mail_cron_orphaned');
207  }
208  }
209 
210  public function delete(): void
211  {
212  if ($this->collector->mailIdsToDelete() !== []) {
213  $this->deleteAttachments();
214 
215  $this->deleteMails();
216 
217  $this->logger->info(
218  \sprintf(
219  'Deleted mail_ids: %s',
220  implode(', ', $this->collector->mailIdsToDelete())
221  )
222  );
223 
224  $this->deleteMarkedAsNotified();
225  $this->logger->info(
226  \sprintf(
227  'Deleted mail_cron_orphaned mail_ids: %s',
228  implode(', ', $this->collector->mailIdsToDelete())
229  )
230  );
231  }
232  }
233 }
$res
Definition: ltiservices.php:66
static getLogger(string $a_component_id)
Get component logger.
$path
Definition: ltiservices.php:29
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
ilSetting $setting
Definition: class.ilias.php:68
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
global $DIC
Definition: shib_login.php:26
__construct(private readonly ilMailCronOrphanedMails $job, private readonly ExpiredOrOrphanedMailsCollector $collector, ?ilDBInterface $db=null, ?ilSetting $setting=null, ?ilLogger $logger=null, ?callable $delete_directory_callback=null)