Test Failed
Push — master ( be0cd3...4fb269 )
by
unknown
22:35 queued 12:00
created

AttachmentState::clearAttachmentFiles()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 2
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
/**
4
 * AttachmentState.
5
 *
6
 * A wrapper around the State class for handling the Attachment State file.
7
 * This class supplies operators for correctly managing the State contents.
8
 */
9
class AttachmentState {
10
	/**
11
	 * The basedir in which the attachments for all sessions are found.
12
	 */
13
	private $basedir;
14
15
	/**
16
	 * The directory in which the attachments for the current session are found.
17
	 */
18
	private $sessiondir;
19
20
	/**
21
	 * The directory in which the session files are created.
22
	 */
23
	private $attachmentdir = 'attachments';
24
25
	/**
26
	 * The State object which refers to the Attachments state.
27
	 */
28
	private $state;
29
30
	/**
31
	 * List of attachment files from the $state.
32
	 */
33
	private $files;
34
35
	/**
36
	 * List of deleted attachment files from the $state.
37
	 */
38
	private $deleteattachment;
39
40
	/**
41
	 * List of aborted attachment files while uploading is going on.
42
	 */
43
	private $abortedattachment;
44
45
	/**
46
	 * Constructor.
47
	 */
48
	public function __construct() {
49
		$this->basedir = TMP_PATH . DIRECTORY_SEPARATOR . $this->attachmentdir;
50
		$this->sessiondir = $this->basedir . DIRECTORY_SEPARATOR . session_id();
51
52
		$this->state = new State('attachments');
53
	}
54
55
	/**
56
	 * Open the session file.
57
	 *
58
	 * The session file is opened and locked so that other processes can not access the state information
59
	 */
60
	public function open() {
61
		if (!is_dir($this->sessiondir)) {
62
			mkdir($this->sessiondir, 0755, true /* recursive */);
63
		}
64
65
		$this->state->open();
66
		$this->files = $this->state->read('files');
67
		$this->deleteattachment = $this->state->read('deleteattachment');
68
		$this->abortedattachment = $this->state->read('abortedattachment');
69
70
		// Check if any aborted attachments are pending to be removed
71
		if (!empty($this->abortedattachment)) {
72
			// Remove aborted attachments
73
			$this->removeAbortedAttachments();
74
		}
75
	}
76
77
	/**
78
	 * Checks if any information regarding attachments is stored in this attachment state.
79
	 *
80
	 * @param string $message_id The unique identifier for referencing the attachments for a single message
81
	 *
82
	 * @return bool True to indicate if any changes needed to save in message else false
83
	 */
84
	public function isChangesPending($message_id) {
85
		$files = $this->getAttachmentFiles($message_id);
86
		$deletedfiles = $this->getDeletedAttachments($message_id);
87
88
		if (empty($files) && empty($deletedfiles)) {
89
			return false;
90
		}
91
92
		return true;
93
	}
94
95
	/**
96
	 * Obtain the folder in which the attachments for the current session
97
	 * can be found. All handling of attachments will be isolated to this
98
	 * folder to prevent any other user to be able to access the attachment
99
	 * files of another user.
100
	 *
101
	 * @return string The foldername in which the attachments can be found
102
	 */
103
	private function getAttachmentFolder() {
104
		return $this->sessiondir;
105
	}
106
107
	/**
108
	 * Obtain the full path for a new filename on the harddisk
109
	 * This uses tempnam to generate a new filename in the default
110
	 * attachments folder.
111
	 *
112
	 * @param string $filename The file for which the temporary path is requested
113
	 *
114
	 * @return string The full path to the attachment file
115
	 */
116
	public function getAttachmentTmpPath($filename) {
117
		$attachmentPath = tempnam($this->getAttachmentFolder(), mb_basename($filename));
118
119
		// Convert in UTF-8 properly if any Malformed UTF-8 characters.
120
		return mb_convert_encoding($attachmentPath, 'UTF-8');
0 ignored issues
show
Bug Best Practice introduced by
The expression return mb_convert_encodi...ttachmentPath, 'UTF-8') also could return the type array which is incompatible with the documented return type string.
Loading history...
121
	}
122
123
	/**
124
	 * Obtain the full path of the given filename on the harddisk.
125
	 *
126
	 * @param string $filename The file for which the path is requested
127
	 *
128
	 * @return string The full path to the attachment file
129
	 */
130
	public function getAttachmentPath($filename) {
131
		return $this->getAttachmentFolder() . DIRECTORY_SEPARATOR . mb_basename($filename);
132
	}
133
134
	/**
135
	 * Check if attachment file which belongs to the given $message_id
136
	 * and is identified by the given $attachid is an embedded attachment.
137
	 *
138
	 * @param string $message_id The unique identifier for referencing the
139
	 *                           attachments for a single message
140
	 * @param string $attachid   The unique identifier for referencing the
141
	 *                           attachment
142
	 *
143
	 * @return bool True if attachment is an embedded else false
144
	 */
145
	public function isEmbeddedAttachment($message_id, $attachid) {
146
		$attach = $this->getAttachmentFile($message_id, $attachid);
147
148
		if ($attach !== false) {
0 ignored issues
show
introduced by
The condition $attach !== false is always true.
Loading history...
149
			if ($attach['sourcetype'] === 'embedded') {
150
				return true;
151
			}
152
		}
153
154
		return false;
155
	}
156
157
	/**
158
	 * Function which identifies whether an attachment is an inline or normal attachment.
159
	 *
160
	 * @param MAPIAttach $attachment MAPI attachment Object
0 ignored issues
show
Bug introduced by
The type MAPIAttach was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
161
	 *
162
	 * @return return true if attachment was inline attachment else return false
0 ignored issues
show
Bug introduced by
The type return was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
163
	 */
164
	public function isInlineAttachment($attachment) {
165
		$props = mapi_attach_getprops($attachment, [PR_ATTACH_CONTENT_ID, PR_ATTACHMENT_HIDDEN, PR_ATTACH_FLAGS]);
166
		$isInlineAttachmentFlag = (isset($props[PR_ATTACH_FLAGS]) && $props[PR_ATTACH_FLAGS] & 4) ? true : false;
167
168
		return isset($props[PR_ATTACH_CONTENT_ID]) && $isInlineAttachmentFlag;
0 ignored issues
show
Bug Best Practice introduced by
The expression return IssetNode && $isInlineAttachmentFlag returns the type boolean which is incompatible with the documented return type return.
Loading history...
169
	}
170
171
	/**
172
	 * Function which identifies whether an attachment is contact photo or normal attachment.
173
	 *
174
	 * @param MAPIAttach $attachment MAPI attachment Object
175
	 *
176
	 * @return return true if attachment is contact photo else return false
177
	 */
178
	public function isContactPhoto($attachment) {
179
		$attachmentProps = mapi_attach_getprops($attachment, [PR_ATTACHMENT_CONTACTPHOTO, PR_ATTACHMENT_HIDDEN]);
180
		$isHidden = $attachmentProps[PR_ATTACHMENT_HIDDEN] ?? false;
181
		$isAttachmentContactPhoto = $attachmentProps[PR_ATTACHMENT_CONTACTPHOTO] ?? false;
182
183
		return $isAttachmentContactPhoto && $isHidden;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $isAttachmentContactPhoto && $isHidden returns the type boolean which is incompatible with the documented return type return.
Loading history...
184
	}
185
186
	/**
187
	 * Add a file which was uploaded to the server by moving it to the attachments directory,
188
	 * and then register (addAttachmentFile) it.
189
	 *
190
	 * @param string $message_id   The unique identifier for referencing the
191
	 *                             attachments for a single message
192
	 * @param string $filename     The filename of the attachment
193
	 * @param string $uploadedfile The file which was uploaded and will be moved to the attachments directory
194
	 * @param array  $fileinfo     The attachment data
195
	 *
196
	 * @return The attachment identifier to be used for referencing the file in the tmp folder
197
	 */
198
	public function addUploadedAttachmentFile($message_id, $filename, $uploadedfile, $fileinfo) {
199
		// Create the destination path, the attachment must
200
		// be placed in the attachment folder with a unique name.
201
		$filepath = $this->getAttachmentTmpPath($filename);
202
203
		// Obtain the generated filename
204
		$tmpname = mb_basename($filepath);
205
206
		move_uploaded_file($uploadedfile, $filepath);
207
208
		$this->addAttachmentFile($message_id, $tmpname, $fileinfo);
209
210
		return $tmpname;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $tmpname returns the type string which is incompatible with the documented return type The.
Loading history...
211
	}
212
213
	/**
214
	 * Move a file from an alternative location to the attachments directory,
215
	 * this will call addAttachmentFile to register the attachment to the state.
216
	 *
217
	 * @param string $message_id The unique identifier for referencing the
218
	 *                           attachments for a single message
219
	 * @param string $filename   The filename of the attachment
220
	 * @param string $sourcefile The path of the file to move to the attachments directory
221
	 * @param array  $fileinfo   The attachment data
222
	 *
223
	 * @return The attachment identifier to be used for referencing the file in the tmp folder
224
	 */
225
	public function addProvidedAttachmentFile($message_id, $filename, $sourcefile, $fileinfo) {
226
		// Create the destination path, the attachment must
227
		// be placed in the attachment folder with a unique name.
228
		$filepath = $this->getAttachmentTmpPath($filename);
229
230
		// Obtain the generated filename
231
		$tmpname = mb_basename($filepath);
232
233
		// Move the uploaded file to tmpname location
234
		rename($sourcefile, $filepath);
235
236
		$this->addAttachmentFile($message_id, $tmpname, $fileinfo);
237
238
		return $tmpname;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $tmpname returns the type string which is incompatible with the documented return type The.
Loading history...
239
	}
240
241
	/**
242
	 * Add information regarding embedded attachment to attachment state.
243
	 *
244
	 * @param string $message_id The unique identifier for referencing the
245
	 *                           attachments for a single message
246
	 * @param array  $fileinfo   The attachment data
247
	 *
248
	 * @return The attachment identifier to be used for referencing the file in the tmp folder
249
	 */
250
	public function addEmbeddedAttachment($message_id, $fileinfo) {
251
		// generate a random number to be used as unique id of attachment
252
		$tmpname = md5(rand());
253
254
		$this->addAttachmentFile($message_id, $tmpname, $fileinfo);
255
256
		return $tmpname;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $tmpname returns the type string which is incompatible with the documented return type The.
Loading history...
257
	}
258
259
	/**
260
	 * Delete a file which was previously uploaded to the attachments directory,
261
	 * this will call removeAttachmentFile to unregister the attachment from the state.
262
	 *
263
	 * @param string $message_id The unique identifier for referencing the
264
	 *                           attachments for a single message
265
	 * @param string $filename   The filename of the attachment to delete
266
	 * @param string $attachID   The unique identifier for referencing attachment
267
	 */
268
	public function deleteUploadedAttachmentFile($message_id, $filename, $attachID) {
269
		// Create the destination path, the attachment has
270
		// previously been placed in the attachment folder
271
		$filepath = $this->getAttachmentPath($filename);
272
		if (is_file($filepath)) {
273
			unlink($filepath);
274
		}
275
276
		$this->removeAttachmentFile($message_id, mb_basename($filepath), $attachID);
277
	}
278
279
	/**
280
	 * Obtain all files which were registered for the given $message_id.
281
	 *
282
	 * @param string $message_id The unique identifier for referencing the
283
	 *                           attachments for a single message
284
	 *
285
	 * @return array The array of attachments
286
	 */
287
	public function getAttachmentFiles($message_id) {
288
		if ($this->files && isset($this->files[$message_id])) {
289
			return $this->files[$message_id];
290
		}
291
292
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
293
	}
294
295
	/**
296
	 * Obtain a single attachment file which belongs to the given $message_id
297
	 * and is identified by the given $attachid.
298
	 *
299
	 * @param string $message_id The unique identifier for referencing the
300
	 *                           attachments for a single message
301
	 * @param string $attachid   The unique identifier for referencing the
302
	 *                           attachment
303
	 *
304
	 * @return array The attachment description for the requested attachment
305
	 */
306
	public function getAttachmentFile($message_id, $attachid) {
307
		if ($this->files && isset($this->files[$message_id], $this->files[$message_id][$attachid])) {
308
			return $this->files[$message_id][$attachid];
309
		}
310
311
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
312
	}
313
314
	/**
315
	 * Add an attachment file to the message.
316
	 *
317
	 * @param string $message_id The unique identifier for referencing the
318
	 *                           attachments for a single message
319
	 * @param string $name       The name of the new attachment
320
	 * @param array  $attachment The attachment data
321
	 */
322
	public function addAttachmentFile($message_id, $name, $attachment) {
323
		if (!$this->files) {
324
			$this->files = [$message_id => []];
325
		}
326
		elseif (!isset($this->files[$message_id])) {
327
			$this->files[$message_id] = [];
328
		}
329
330
		$this->files[$message_id][$name] = $attachment;
331
	}
332
333
	/**
334
	 * Remove an attachment file from the message.
335
	 *
336
	 * @param string $message_id The unique identifier for referencing the
337
	 *                           attachments for a single message
338
	 * @param string $name       The name of the attachment
339
	 * @param string $attachID   The unique identifier for referencing attachment
340
	 */
341
	public function removeAttachmentFile($message_id, $name, $attachID) {
342
		if ($this->files && isset($this->files[$message_id])) {
343
			unset($this->files[$message_id][$name]);
344
		}
345
346
		// if attachID is supplied then user is trying to remove the attachment which is
347
		// still uploading
348
		if ($attachID) {
349
			$found = false;
350
			if (!empty($this->files)) {
351
				// Loop over and find the attachment from attachID and remove the same
352
				foreach ($this->files as $tmpDir => $attachment) {
353
					foreach ($this->files[$tmpDir] as $tmpName => $attachmentVal) {
354
						if ($this->files[$tmpDir][$tmpName]['attach_id'] === $attachID) {
355
							$found = true;
356
							$filepath = $this->getAttachmentPath($tmpName);
357
							if (is_file($filepath)) {
358
								unlink($filepath);
359
							}
360
							unset($this->files[$tmpDir][$tmpName]);
361
						}
362
					}
363
				}
364
			}
365
366
			if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
367
				// if files array is empty or attachment is not found then just remember
368
				// this particular attachment, which will be removed when attachment_state
369
				// will be opened again.
370
				$this->addAbortedAttachment($message_id, $attachID);
371
372
				return;
373
			}
374
		}
375
	}
376
377
	/**
378
	 * Remove all attachment files from the message.
379
	 *
380
	 * @param string $message_id The unique identifier for referencing the
381
	 *                           attachments for a single message
382
	 */
383
	public function clearAttachmentFiles($message_id) {
384
		if ($this->files) {
385
			unset($this->files[$message_id]);
386
		}
387
	}
388
389
	/**
390
	 * Obtain all files which were removed for the given $message_id.
391
	 *
392
	 * @param string $message_id The unique identifier for referencing the
393
	 *                           attachments for a single message
394
	 *
395
	 * @return array The array of attachments
396
	 */
397
	public function getDeletedAttachments($message_id) {
398
		if ($this->deleteattachment && isset($this->deleteattachment[$message_id])) {
399
			return $this->deleteattachment[$message_id];
400
		}
401
402
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
403
	}
404
405
	/**
406
	 * Add a deleted attachment file to the message.
407
	 *
408
	 * @param string $message_id The unique identifier for referencing the
409
	 *                           attachments for a single message
410
	 * @param string $name       The name of the attachment
411
	 */
412
	public function addDeletedAttachment($message_id, $name) {
413
		if (!$this->deleteattachment) {
414
			$this->deleteattachment = [$message_id => []];
415
		}
416
		elseif (!isset($this->deleteattachment[$message_id])) {
417
			$this->deleteattachment[$message_id] = [];
418
		}
419
420
		$this->deleteattachment[$message_id][] = $name;
421
	}
422
423
	/**
424
	 * Remove a deleted attachment from the message.
425
	 *
426
	 * @param string $message_id The unique identifier for referencing the
427
	 *                           attachments for a single message
428
	 * @param string $name       The name of the attachment
429
	 */
430
	public function removeDeletedAttachment($message_id, $name) {
431
		if ($this->deleteattachment && isset($this->deleteattachment[$message_id])) {
432
			$index = array_search($name, $this->deleteattachment[$message_id]);
433
			if ($index !== false) {
434
				unset($this->deleteattachment[$message_id][$index]);
435
				$this->deleteattachment[$message_id] = array_values($this->deleteattachment[$message_id]);
436
			}
437
		}
438
	}
439
440
	/**
441
	 * Add an aborted attachment file to the 'abortedattachment' array.
442
	 * So that it can be removed from message while it is available in 'files' array.
443
	 *
444
	 * @param string $message_id The unique identifier for referencing the
445
	 *                           attachments for a single message
446
	 * @param string $attachID   The unique identifier for referencing attachment
447
	 */
448
	public function addAbortedAttachment($message_id, $attachID) {
449
		if (!$this->abortedattachment) {
450
			$this->abortedattachment = [$message_id => []];
451
		}
452
		elseif (!isset($this->abortedattachment[$message_id])) {
453
			$this->abortedattachment[$message_id] = [];
454
		}
455
456
		$this->abortedattachment[$message_id][] = $attachID;
457
	}
458
459
	/**
460
	 * Remove an aborted attachment from the message and files list.
461
	 */
462
	public function removeAbortedAttachments() {
463
		if (empty($this->files)) {
464
			return;
465
		}
466
467
		// Loop over and find the attachment from attachID and remove the same
468
		foreach ($this->files as $tmpDir => $attachment) {
469
			foreach ($this->files[$tmpDir] as $tmpName => $attachmentVal) {
470
				if (!isset($this->abortedattachment[$tmpDir])) {
471
					continue;
472
				}
473
474
				// check if the aborted attachID is present in files array
475
				$index = array_search($this->files[$tmpDir][$tmpName]['attach_id'], $this->abortedattachment[$tmpDir]);
476
				if ($index !== false) {
477
					// Remove attachment from the list of aborted attachments.
478
					unset($this->abortedattachment[$tmpDir][$index]);
479
480
					// If respective file is still there in tmp directory then remove
481
					$filepath = $this->getAttachmentPath($tmpName);
482
					if (is_file($filepath)) {
483
						unlink($filepath);
484
					}
485
486
					// Remove attachment from state as well
487
					unset($this->files[$tmpDir][$tmpName]);
488
				}
489
			}
490
		}
491
	}
492
493
	/**
494
	 * Remove all deleted attachment files from the message.
495
	 *
496
	 * @param string $message_id The unique identifier for referencing the
497
	 *                           attachments for a single message
498
	 */
499
	public function clearDeletedAttachments($message_id) {
500
		if ($this->deleteattachment) {
501
			unset($this->deleteattachment[$message_id]);
502
		}
503
	}
504
505
	/**
506
	 * Close the state and flush all information back
507
	 * to the State file on the disk.
508
	 */
509
	public function close() {
510
		$this->state->write('files', $this->files, false);
511
		$this->state->write('deleteattachment', $this->deleteattachment, false);
512
		$this->state->write('abortedattachment', $this->abortedattachment, false);
513
		$this->state->flush();
514
		$this->state->close();
515
	}
516
517
	/**
518
	 * Cleans all old attachments in the attachment directory.
519
	 *
520
	 * @param int $maxLifeTime the maximum allowed age of files in seconds
521
	 */
522
	public function clean($maxLifeTime = UPLOADED_ATTACHMENT_MAX_LIFETIME) {
523
		cleanTemp($this->basedir, $maxLifeTime);
524
525
		// remove base directory also
526
		if (is_dir($this->sessiondir)) {
527
			/*
528
			 * FIXME: We should remove the folder when it is not needed,
529
			 * Because of WA-6523 this is creating an issue, so need to fix this properly
530
			 */
531
			// Directory should be empty to remove it, So first remove all files in the directory
532
			/*if(function_exists("glob")) {
533
				array_map('unlink', glob($this->sessiondir . "/*"));
534
			}*/
535
			rmdir($this->sessiondir);
536
		}
537
	}
538
}
539