FilesBrowserModule   F
last analyzed

Complexity

Total Complexity 158

Size/Duplication

Total Lines 1304
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 679
c 0
b 0
f 0
dl 0
loc 1304
rs 1.921
wmc 158

19 Methods

Rating   Name   Duplication   Size   Complexity  
A createNotifiers() 0 2 1
A getSharingInformation() 0 50 5
A sortFolderContent() 0 21 3
F prepareAttachmentForUpload() 0 135 22
A downloadSelectedFilesToTmp() 0 48 2
B delete() 0 79 8
A updateDirCache() 0 11 2
A rename() 0 7 3
F execute() 0 149 27
D getFolderContent() 0 131 23
A deleteExistingShare() 0 39 3
A createNewShare() 0 52 5
B uploadToBackend() 0 55 7
A updateExistingShare() 0 46 4
B move() 0 93 9
A updateCache() 0 22 2
B prepareEmailForUpload() 0 65 10
C checkIfExists() 0 51 17
A loadFiles() 0 57 5

How to fix   Complexity   

Complex Class

Complex classes like FilesBrowserModule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FilesBrowserModule, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
require_once "plugins/files/php/modules/class.fileslistmodule.php";
4
5
require_once __DIR__ . "/Files/Core/class.exception.php";
6
require_once __DIR__ . "/Files/Backend/class.exception.php";
7
8
require_once __DIR__ . "/Files/Core/class.accountstore.php";
9
require_once __DIR__ . "/Files/Backend/class.backendstore.php";
10
11
require_once __DIR__ . "/Files/Core/Util/class.arrayutil.php";
12
require_once __DIR__ . "/Files/Core/Util/class.logger.php";
13
require_once __DIR__ . "/Files/Core/Util/class.stringutil.php";
14
require_once __DIR__ . "/Files/Core/Util/class.pathutil.php";
15
16
require_once __DIR__ . "/vendor/autoload.php";
17
18
use Files\Backend\AbstractBackend;
19
use Files\Backend\BackendStore;
20
use Files\Backend\Exception as BackendException;
21
use Files\Backend\iFeatureSharing;
22
use Files\Core\Account;
23
use Files\Core\Exception as AccountException;
24
use Files\Core\Util\ArrayUtil;
25
use Files\Core\Util\Logger;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Logger. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
26
use Files\Core\Util\PathUtil;
27
use Files\Core\Util\StringUtil;
28
29
/**
30
 * This module handles all list and change requests for the files browser.
31
 *
32
 * @class FilesBrowserModule
33
 *
34
 * @extends ListModule
35
 */
36
class FilesBrowserModule extends FilesListModule {
37
	public const LOG_CONTEXT = "FilesBrowserModule"; // Context for the Logger
38
39
	/**
40
	 * Creates the notifiers for this module,
41
	 * and register them to the Bus.
42
	 */
43
	public function createNotifiers() {
44
		$GLOBALS["bus"]->registerNotifier('fileshierarchynotifier', REQUEST_ENTRYID);
45
	}
46
47
	/**
48
	 * Executes all the actions in the $data variable.
49
	 * Exception part is used for authentication errors also.
50
	 *
51
	 * @return bool true on success or false on failure
52
	 */
53
	#[Override]
54
	public function execute() {
55
		$result = false;
56
57
		foreach ($this->data as $actionType => $actionData) {
58
			if (isset($actionType)) {
59
				try {
60
					switch ($actionType) {
61
						case "checkifexists":
62
							$records = $actionData["records"];
63
							$destination = $actionData["destination"] ?? false;
64
							$result = $this->checkIfExists($records, $destination);
0 ignored issues
show
Bug introduced by
It seems like $destination can also be of type false; however, parameter $destination of FilesBrowserModule::checkIfExists() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

64
							$result = $this->checkIfExists($records, /** @scrutinizer ignore-type */ $destination);
Loading history...
65
							$response = [];
66
							$response['status'] = true;
67
							$response['duplicate'] = $result;
68
							$this->addActionData($actionType, $response);
69
							$GLOBALS["bus"]->addData($this->getResponseData());
70
							break;
71
72
						case "downloadtotmp":
73
							$result = $this->downloadSelectedFilesToTmp($actionType, $actionData);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $result is correct as $this->downloadSelectedF...ctionType, $actionData) targeting FilesBrowserModule::downloadSelectedFilesToTmp() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
74
							break;
75
76
						case "createdir":
77
							$this->save($actionData);
78
							$result = true;
79
							break;
80
81
						case "rename":
82
							$result = $this->rename($actionType, $actionData);
83
							break;
84
85
						case "uploadtobackend":
86
							$result = $this->uploadToBackend($actionType, $actionData);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $result is correct as $this->uploadToBackend($actionType, $actionData) targeting FilesBrowserModule::uploadToBackend() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
87
							break;
88
89
						case "save":
90
							if ((isset($actionData["props"]["sharedid"]) || isset($actionData["props"]["isshared"])) && (!isset($actionData["props"]["deleted"]) || !isset($actionData["props"]["message_size"]))) {
91
								// JUST IGNORE THIS REQUEST - we don't need to interact with the backend if a share was changed
92
								$response['status'] = true;
93
								$folder = [];
94
								$folder[$actionData['entryid']] = [
95
									'props' => $actionData["props"],
96
									'entryid' => $actionData['entryid'],
97
									'store_entryid' => 'files',
98
									'parent_entryid' => $actionData['parent_entryid'],
99
								];
100
101
								$response['item'] = array_values($folder);
102
								$this->addActionData("update", $response);
103
								$GLOBALS["bus"]->addData($this->getResponseData());
104
105
								break;
106
							}
107
108
							/*
109
							 * The "message_action" object has been set, check the action_type field for
110
							 * the exact action which must be taken.
111
							 * Supported actions:
112
							 *   - move: move record to new folder
113
							 */
114
							if (isset($actionData["message_action"], $actionData["message_action"]["action_type"])) {
115
								switch ($actionData["message_action"]["action_type"]) {
116
									case "move" :
117
										$result = $this->move($actionType, $actionData);
118
										break;
119
120
									default:
121
										// check if we should create something new or edit an existing file/folder
122
										if (isset($actionData["entryid"])) {
123
											$result = $this->rename($actionType, $actionData);
124
										}
125
										else {
126
											$result = $this->save($actionData);
127
										}
128
										break;
129
								}
130
							}
131
							else {
132
								// check if we should create something new or edit an existing file/folder
133
								if (isset($actionData["entryid"])) {
134
									$result = $this->rename($actionType, $actionData);
135
								}
136
								else {
137
									$result = $this->save($actionData);
138
								}
139
							}
140
							break;
141
142
						case "delete":
143
							$result = $this->delete($actionType, $actionData);
144
							break;
145
146
						case "list":
147
							$result = $this->loadFiles($actionType, $actionData);
148
							break;
149
150
						case "loadsharingdetails":
151
							$result = $this->getSharingInformation($actionType, $actionData);
152
							break;
153
154
						case "createnewshare":
155
							$result = $this->createNewShare($actionType, $actionData);
156
							break;
157
158
						case "updateexistingshare":
159
							$result = $this->updateExistingShare($actionType, $actionData);
160
							break;
161
162
						case "deleteexistingshare":
163
							$result = $this->deleteExistingShare($actionType, $actionData);
164
							break;
165
166
						case "updatecache":
167
							$result = $this->updateCache($actionType, $actionData);
168
							break;
169
170
						default:
171
							$this->handleUnknownActionType($actionType);
172
					}
173
				}
174
				catch (MAPIException $e) {
175
					$this->sendFeedback(false, $this->errorDetailsFromException($e));
176
				}
177
				catch (AccountException $e) {
178
					$this->sendFeedback(false, [
179
						'type' => ERROR_GENERAL,
180
						'info' => [
181
							'title' => $e->getTitle(),
182
							'original_message' => $e->getMessage(),
183
							'display_message' => $e->getMessage(),
184
						],
185
					]);
186
				}
187
				catch (BackendException $e) {
188
					$this->sendFeedback(false, [
189
						'type' => ERROR_GENERAL,
190
						'info' => [
191
							'title' => $e->getTitle(),
192
							'original_message' => $e->getMessage(),
193
							'display_message' => $e->getMessage(),
194
							'code' => $e->getCode(),
195
						],
196
					]);
197
				}
198
			}
199
		}
200
201
		return $result;
202
	}
203
204
	/**
205
	 * loads content of current folder - list of folders and files from Files.
206
	 *
207
	 * @param string $actionType name of the current action
208
	 * @param array  $actionData all parameters contained in this request
209
	 *
210
	 * @return bool
211
	 *
212
	 * @throws BackendException if the backend request fails
213
	 */
214
	public function loadFiles($actionType, $actionData) {
215
		$nodeId = $actionData['id'];
216
		$onlyFiles = $actionData['only_files'] ?? false;
217
		$response = [];
218
		$nodes = [];
219
220
		$accountID = $this->accountIDFromNode($nodeId);
221
222
		// check if we are in the ROOT (#R#). If so, display some kind of device/account view.
223
		if (empty($accountID) || !$this->accountStore->getAccount($accountID)) {
224
			$accounts = $this->accountStore->getAllAccounts();
225
			foreach ($accounts as $account) { // we have to load all accounts and their folders
226
				// skip accounts that are not valid
227
				if ($account->getStatus() != Account::STATUS_OK) {
228
					continue;
229
				}
230
				// build the real node id for this folder
231
				$realNodeId = $nodeId . $account->getId() . "/";
232
233
				$nodes[$realNodeId] = ['props' => [
234
					'id' => rawurldecode($realNodeId),
235
					'folder_id' => rawurldecode($realNodeId),
236
					'path' => $realNodeId,
237
					'filename' => $account->getName(),
238
					'message_size' => -1,
239
					'lastmodified' => -1,
240
					'message_class' => "IPM.Files",
241
					'type' => 0,
242
				],
243
					'entryid' => $this->createId($realNodeId),
244
					'store_entryid' => $this->createId($realNodeId),
245
					'parent_entryid' => $this->createId($realNodeId),
246
				];
247
			}
248
		}
249
		else {
250
			$account = $this->accountStore->getAccount($accountID);
251
252
			// initialize the backend
253
			$initializedBackend = $this->initializeBackend($account, true);
254
255
			$starttime = microtime(true);
256
			$nodes = $this->getFolderContent($nodeId, $initializedBackend, $onlyFiles);
257
			Logger::debug(self::LOG_CONTEXT, "[loadfiles]: getFolderContent took: " . (microtime(true) - $starttime) . " seconds");
258
259
			$nodes = $this->sortFolderContent($nodes, $actionData, false);
260
		}
261
262
		$response["item"] = array_values($nodes);
263
264
		$response['page'] = ["start" => 0, "rowcount" => 50, "totalrowcount" => count($response["item"])];
265
		$response['folder'] = ["content_count" => count($response["item"]), "content_unread" => 0];
266
267
		$this->addActionData($actionType, $response);
268
		$GLOBALS["bus"]->addData($this->getResponseData());
269
270
		return true;
271
	}
272
273
	/**
274
	 * Forms the structure needed for frontend
275
	 * for the list of folders and files.
276
	 *
277
	 * @param string          $nodeId          the name of the current root directory
278
	 * @param AbstractBackend $backendInstance
279
	 * @param bool            $onlyFiles       if true, get only files
280
	 *
281
	 * @return array of nodes for current path folder
282
	 *
283
	 * @throws BackendException if the backend request fails
284
	 */
285
	public function getFolderContent($nodeId, $backendInstance, $onlyFiles = false) {
286
		$nodes = [];
287
288
		// relative node ID. We need to trim off the #R# and account ID
289
		$relNodeId = substr($nodeId, strpos($nodeId, '/'));
290
		$nodeIdPrefix = substr($nodeId, 0, strpos($nodeId, '/'));
291
292
		$accountID = $backendInstance->getAccountID();
293
294
		// remove the trailing slash for the cache key
295
		$cachePath = rtrim($relNodeId, '/');
296
		if ($cachePath === "") {
297
			$cachePath = "/";
298
		}
299
300
		$dir = $this->getCache($accountID, $cachePath);
301
		if (is_null($dir)) {
0 ignored issues
show
introduced by
The condition is_null($dir) is always false.
Loading history...
302
			$dir = $backendInstance->ls($relNodeId);
303
			$this->setCache($accountID, $cachePath, $dir);
304
		}
305
306
		// FIXME: There is something issue with getting sharing information from owncloud.
307
		// check if backend supports sharing and load the information
308
		if ($backendInstance->supports(BackendStore::FEATURE_SHARING)) {
309
			Logger::debug(self::LOG_CONTEXT, "Checking for shared folders! ({$relNodeId})");
310
311
			$time_start = microtime(true);
312
313
			/** @var iFeatureSharing $backendInstance */
314
			$sharingInfo = $backendInstance->getShares($relNodeId);
315
			$time_end = microtime(true);
316
			$time = $time_end - $time_start;
317
318
			Logger::debug(self::LOG_CONTEXT, "Checking for shared took {$time} s!");
319
		}
320
321
		if ($dir) {
0 ignored issues
show
introduced by
$dir is of type iterable, thus it always evaluated to false.
Loading history...
322
			$updateCache = false;
323
			foreach ($dir as $id => $node) {
324
				$type = FILES_FILE;
325
326
				if (strcmp((string) $node['resourcetype'], "collection") == 0) { // we have a folder
327
					$type = FILES_FOLDER;
328
				}
329
330
				if ($type === FILES_FOLDER && $onlyFiles) {
331
					continue;
332
				}
333
334
				// Check if foldernames have a trailing slash, if not, add one!
335
				if ($type === FILES_FOLDER && !StringUtil::endsWith($id, "/")) {
336
					$id .= "/";
337
				}
338
339
				$realID = $nodeIdPrefix . $id;
340
341
				Logger::debug(self::LOG_CONTEXT, "parsing: " . $id . " in base: " . $nodeId);
342
343
				$filename = stringToUTF8Encode(basename((string) $id));
344
345
				$size = $node['getcontentlength'] === null ? -1 : intval($node['getcontentlength']);
346
				$size = $type == FILES_FOLDER ? -1 : $size; // folder's dont have a size
347
348
				$fileid = $node['fileid'] === "-1" ? -1 : intval($node['fileid']);
349
350
				$shared = false;
351
				$sharedid = [];
352
				if (isset($sharingInfo) && count($sharingInfo[$relNodeId]) > 0) {
353
					foreach ($sharingInfo[$relNodeId] as $sid => $sdetails) {
354
						if ($sdetails["path"] == rtrim((string) $id, "/")) {
355
							$shared = true;
356
							$sharedid[] = $sid;
357
						}
358
					}
359
				}
360
361
				$nodeId = stringToUTF8Encode($id);
362
				$dirName = dirname($nodeId, 1);
363
				if ($dirName === '/') {
364
					$path = stringToUTF8Encode($nodeIdPrefix . $dirName);
365
				}
366
				else {
367
					$path = stringToUTF8Encode($nodeIdPrefix . $dirName . '/');
368
				}
369
370
				if (!isset($node['entryid']) || !isset($node['parent_entryid']) || !isset($node['store_entryid'])) {
371
					$entryid = $this->createId($realID);
372
					$parentEntryid = $this->createId($path);
373
					$storeEntryid = $this->createId($nodeIdPrefix . '/');
374
375
					$dir[$id]['entryid'] = $entryid;
376
					$dir[$id]['parent_entryid'] = $parentEntryid;
377
					$dir[$id]['store_entryid'] = $storeEntryid;
378
379
					$updateCache = true;
380
				}
381
				else {
382
					$entryid = $node['entryid'];
383
					$parentEntryid = $node['parent_entryid'];
384
					$storeEntryid = $node['store_entryid'];
385
				}
386
387
				$nodes[$nodeId] = ['props' => [
388
					'folder_id' => stringToUTF8Encode($realID),
389
					'fileid' => $fileid,
390
					'path' => $path,
391
					'filename' => $filename,
392
					'message_size' => $size,
393
					'lastmodified' => strtotime((string) $node['getlastmodified']) * 1000,
394
					'message_class' => "IPM.Files",
395
					'isshared' => $shared,
396
					'sharedid' => $sharedid,
397
					'object_type' => $type,
398
					'type' => $type,
399
				],
400
					'entryid' => $entryid,
401
					'parent_entryid' => $parentEntryid,
402
					'store_entryid' => $storeEntryid,
403
				];
404
			}
405
406
			// Update the cache.
407
			if ($updateCache) {
408
				$this->setCache($accountID, $cachePath, $dir);
409
			}
410
		}
411
		else {
412
			Logger::debug(self::LOG_CONTEXT, "dir was empty");
413
		}
414
415
		return $nodes;
416
	}
417
418
	/**
419
	 * This functions sorts an array of nodes.
420
	 *
421
	 * @param array $nodes   array of nodes to sort
422
	 * @param array $data    all parameters contained in the request
423
	 * @param bool  $navtree parse for navtree or browser
424
	 *
425
	 * @return array of sorted nodes
426
	 */
427
	public function sortFolderContent($nodes, $data, $navtree = false) {
428
		$sortednodes = [];
429
430
		$sortkey = "filename";
431
		$sortdir = "ASC";
432
433
		if (isset($data['sort'])) {
434
			$sortkey = $data['sort'][0]['field'];
435
			$sortdir = $data['sort'][0]['direction'];
436
		}
437
438
		Logger::debug(self::LOG_CONTEXT, "sorting by " . $sortkey . " in direction: " . $sortdir);
439
440
		if ($navtree) {
441
			$sortednodes = ArrayUtil::sort_by_key($nodes, $sortkey, $sortdir);
442
		}
443
		else {
444
			$sortednodes = ArrayUtil::sort_props_by_key($nodes, $sortkey, $sortdir);
445
		}
446
447
		return $sortednodes;
448
	}
449
450
	/**
451
	 * Deletes the selected files on the backend server.
452
	 *
453
	 * @param string $actionType name of the current action
454
	 * @param array  $actionData all parameters contained in this request
455
	 *
456
	 * @return bool
457
	 *
458
	 * @throws BackendException if the backend request fails
459
	 */
460
	private function delete($actionType, $actionData) {
461
		// TODO: function is duplicate of class.hierarchylistmodule.php of delete function.
462
		$result = false;
463
		if (isset($actionData['records']) && is_array($actionData['records'])) {
464
			foreach ($actionData['records'] as $record) {
465
				$nodeId = $record['folder_id'];
466
				$relNodeId = substr((string) $nodeId, strpos((string) $nodeId, '/'));
467
468
				$account = $this->accountFromNode($nodeId);
469
470
				// initialize the backend
471
				$initializedBackend = $this->initializeBackend($account);
472
473
				$result = $initializedBackend->delete($relNodeId);
474
				Logger::debug(self::LOG_CONTEXT, "deleted: " . $nodeId . ", worked: " . $result);
475
476
				// clear the cache
477
				$this->deleteCache($account->getId(), dirname($relNodeId));
478
				$GLOBALS["bus"]->notify(REQUEST_ENTRYID, OBJECT_DELETE, [
479
					"id" => $nodeId,
480
					"folder_id" => $nodeId,
481
					"entryid" => $record['entryid'],
482
					"parent_entryid" => $record["parent_entryid"],
483
					"store_entryid" => $record["store_entryid"],
484
				]);
485
			}
486
487
			$response['status'] = true;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$response was never initialized. Although not strictly required by PHP, it is generally a good practice to add $response = array(); before regardless.
Loading history...
488
			$this->addActionData($actionType, $response);
489
			$GLOBALS["bus"]->addData($this->getResponseData());
490
		}
491
		else {
492
			$nodeId = $actionData['folder_id'];
493
494
			$relNodeId = substr((string) $nodeId, strpos((string) $nodeId, '/'));
495
			$response = [];
496
497
			$account = $this->accountFromNode($nodeId);
498
			$accountId = $account->getId();
499
			// initialize the backend
500
			$initializedBackend = $this->initializeBackend($account);
501
502
			try {
503
				$result = $initializedBackend->delete($relNodeId);
504
			}
505
			catch (BackendException) {
506
				// TODO: this might fails because the file was already deleted.
507
				// fire error message if any other error occurred.
508
				Logger::debug(self::LOG_CONTEXT, "deleted a directory that was no longer available");
509
			}
510
			Logger::debug(self::LOG_CONTEXT, "deleted: " . $nodeId . ", worked: " . $result);
511
512
			// Get old cached data.
513
			$cachedDir = $this->getCache($accountId, dirname($relNodeId));
514
			if (isset($cachedDir[$relNodeId]) && !empty($cachedDir[$relNodeId])) {
515
				// Delete the folder from cached data.
516
				unset($cachedDir[$relNodeId]);
517
			}
518
519
			// clear the cache of parent directory.
520
			$this->deleteCache($accountId, dirname($relNodeId));
521
			// clear the cache of selected directory.
522
			$this->deleteCache($accountId, rtrim($relNodeId, '/'));
523
524
			// Set data in cache.
525
			$this->setCache($accountId, dirname($relNodeId), $cachedDir);
526
527
			$response['status'] = $result ? true : false;
528
			$this->addActionData($actionType, $response);
529
			$GLOBALS["bus"]->addData($this->getResponseData());
530
531
			$GLOBALS["bus"]->notify(REQUEST_ENTRYID, OBJECT_DELETE, [
532
				"entryid" => $actionData["entryid"],
533
				"parent_entryid" => $actionData["parent_entryid"],
534
				"store_entryid" => $actionData["store_entryid"],
535
			]);
536
		}
537
538
		return true;
539
	}
540
541
	/**
542
	 * Moves the selected files on the backend server.
543
	 *
544
	 * @param string $actionType name of the current action
545
	 * @param array  $actionData all parameters contained in this request
546
	 *
547
	 * @return bool if the backend request failed
548
	 */
549
	private function move($actionType, $actionData) {
550
		$dst = rtrim((string) $actionData['message_action']["destination_folder_id"], '/');
551
552
		$overwrite = $actionData['message_action']["overwrite"] ?? true;
553
		$isFolder = $actionData['message_action']["isFolder"] ?? false;
554
555
		$pathPostfix = "";
556
		if (str_ends_with((string) $actionData['folder_id'], '/')) {
557
			$pathPostfix = "/"; // we have a folder...
558
		}
559
560
		$source = rtrim((string) $actionData['folder_id'], '/');
561
		$fileName = basename($source);
562
		$destination = $dst . '/' . basename($source);
563
564
		// get dst and source account ids
565
		// currently only moving within one account is supported
566
		$srcAccountID = substr((string) $actionData['folder_id'], 3, strpos((string) $actionData['folder_id'], '/') - 3); // parse account id from node id
567
		$dstAccountID = substr((string) $actionData['message_action']["destination_folder_id"], 3, strpos((string) $actionData['message_action']["destination_folder_id"], '/') - 3); // parse account id from node id
568
569
		if ($srcAccountID !== $dstAccountID) {
570
			$this->sendFeedback(false, [
571
				'type' => ERROR_GENERAL,
572
				'info' => [
573
					'title' => _("Files Plugin"),
574
					'original_message' => _("Moving between accounts is not implemented"),
575
					'display_message' => _("Moving between accounts is not implemented"),
576
				],
577
			]);
578
579
			return false;
580
		}
581
		$relDst = substr($destination, strpos($destination, '/'));
582
		$relSrc = substr($source, strpos($source, '/'));
583
584
		$account = $this->accountFromNode($source);
585
586
		// initialize the backend
587
		$initializedBackend = $this->initializeBackend($account);
588
589
		$result = $initializedBackend->move($relSrc, $relDst, $overwrite);
590
591
		$actionId = $account->getId();
592
		// clear the cache
593
		$this->deleteCache($actionId, dirname($relDst));
594
595
		$cachedFolderName = $relSrc . $pathPostfix;
596
		$this->deleteCache($actionId, $cachedFolderName);
597
598
		$cached = $this->getCache($actionId, dirname($relSrc));
599
		$this->deleteCache($actionId, dirname($relSrc));
600
601
		if (isset($cached[$cachedFolderName]) && !empty($cached[$cachedFolderName])) {
602
			unset($cached[$cachedFolderName]);
603
			$this->setCache($actionId, dirname($relSrc), $cached);
0 ignored issues
show
Bug introduced by
$cached of type iterable is incompatible with the type string expected by parameter $data of FilesListModule::setCache(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

603
			$this->setCache($actionId, dirname($relSrc), /** @scrutinizer ignore-type */ $cached);
Loading history...
604
		}
605
606
		$response['status'] = !$result ? false : true;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$response was never initialized. Although not strictly required by PHP, it is generally a good practice to add $response = array(); before regardless.
Loading history...
607
608
		/* create the response object */
609
		$folder = [
610
			'props' => [
611
				'folder_id' => ($destination . $pathPostfix),
612
				'path' => $actionData['message_action']["destination_folder_id"],
613
				'filename' => $fileName,
614
				'display_name' => $fileName,
615
				'object_type' => $isFolder ? FILES_FOLDER : FILES_FILE,
616
				'deleted' => !$result ? false : true,
617
			],
618
			'entryid' => $this->createId($destination . $pathPostfix),
619
			'store_entryid' => $actionData['store_entryid'],
620
			'parent_entryid' => $actionData['message_action']['parent_entryid'],
621
		];
622
623
		$response['item'] = $folder;
624
625
		$this->addActionData("update", $response);
626
		$GLOBALS["bus"]->addData($this->getResponseData());
627
628
		// Notify hierarchy only when folder was moved.
629
		if ($isFolder) {
630
			// Send notification to delete folder node in hierarchy.
631
			$GLOBALS["bus"]->notify(REQUEST_ENTRYID, OBJECT_DELETE, [
632
				"entryid" => $actionData["entryid"],
633
				"parent_entryid" => $actionData["parent_entryid"],
634
				"store_entryid" => $actionData["store_entryid"],
635
			]);
636
637
			// Send notification to create new folder node in hierarchy.
638
			$GLOBALS["bus"]->notify(REQUEST_ENTRYID, OBJECT_SAVE, $folder);
639
		}
640
641
		return true;
642
	}
643
644
	/**
645
	 * Renames the selected file on the backend server.
646
	 *
647
	 * @param string $actionType name of the current action
648
	 * @param array  $actionData all parameters contained in this request
649
	 *
650
	 * @return bool
651
	 *
652
	 * @throws BackendException if the backend request fails
653
	 */
654
	public function rename($actionType, $actionData) {
655
		$messageProps = $this->save($actionData);
656
		$notifySubFolders = $actionData['message_action']['isFolder'] ?? true;
657
		if (!empty($messageProps)) {
658
			$GLOBALS["bus"]->notify(REQUEST_ENTRYID, OBJECT_SAVE, $messageProps);
659
			if ($notifySubFolders) {
660
				$this->notifySubFolders($messageProps["props"]["folder_id"]);
661
			}
662
		}
663
	}
664
665
	/**
666
	 * Check if given filename or folder already exists on server.
667
	 *
668
	 * @param array $records     which needs to be check for existence
669
	 * @param array $destination where the given records needs to be moved, uploaded, or renamed
670
	 *
671
	 * @return bool True if duplicate found, false otherwise
672
	 *
673
	 * @throws BackendException if the backend request fails
674
	 */
675
	private function checkIfExists($records, $destination) {
676
		$duplicate = false;
677
678
		if (isset($records) && is_array($records)) {
679
			if (!isset($destination) || $destination == false) {
680
				$destination = reset($records);
681
				$destination = $destination["id"]; // we can only check files in the same folder, so one request will be enough
682
				Logger::debug(self::LOG_CONTEXT, "Resetting destination to check.");
683
			}
684
			Logger::debug(self::LOG_CONTEXT, "Checking: " . $destination);
685
			$account = $this->accountFromNode($destination);
686
687
			// initialize the backend
688
			$initializedBackend = $this->initializeBackend($account);
689
690
			$relDirname = substr((string) $destination, strpos((string) $destination, '/'));
691
			Logger::debug(self::LOG_CONTEXT, "Getting content for: " . $relDirname);
692
693
			try {
694
				$lsdata = $initializedBackend->ls($relDirname); // we can only check files in the same folder, so one request will be enough
695
			}
696
			catch (Exception) {
697
				// ignore - if file not found -> does not exist :)
698
			}
699
			if (isset($lsdata) && is_array($lsdata)) {
700
				foreach ($records as $record) {
701
					$relRecId = substr((string) $record["id"], strpos((string) $record["id"], '/'));
702
					Logger::debug(self::LOG_CONTEXT, "Checking rec: " . $relRecId);
703
					foreach ($lsdata as $argsid => $args) {
704
						if (strcmp((string) $args['resourcetype'], "collection") == 0 && $record["isFolder"] && strcmp(basename($argsid), basename($relRecId)) == 0) { // we have a folder
705
							Logger::debug(self::LOG_CONTEXT, "Duplicate folder found: " . $argsid);
706
							$duplicate = true;
707
							break;
708
						}
709
						if (strcmp((string) $args['resourcetype'], "collection") != 0 && !$record["isFolder"] && strcmp(basename($argsid), basename($relRecId)) == 0) {
710
							Logger::debug(self::LOG_CONTEXT, "Duplicate file found: " . $argsid);
711
							$duplicate = true;
712
							break;
713
						}
714
						$duplicate = false;
715
					}
716
717
					if ($duplicate) {
718
						Logger::debug(self::LOG_CONTEXT, "Duplicate entry: " . $relRecId);
719
						break;
720
					}
721
				}
722
			}
723
		}
724
725
		return $duplicate;
726
	}
727
728
	/**
729
	 * Downloads file from the Files service and saves it in tmp
730
	 * folder with unique name.
731
	 *
732
	 * @param array $actionData
733
	 * @param mixed $actionType
734
	 *
735
	 * @throws BackendException if the backend request fails
736
	 */
737
	private function downloadSelectedFilesToTmp($actionType, $actionData) {
738
		$ids = $actionData['ids'];
739
		$dialogAttachmentId = $actionData['dialog_attachments'];
740
		$response = [];
741
742
		$attachment_state = new AttachmentState();
743
		$attachment_state->open();
744
745
		$account = $this->accountFromNode($ids[0]);
746
747
		// initialize the backend
748
		$initializedBackend = $this->initializeBackend($account);
749
750
		foreach ($ids as $file) {
751
			$filename = basename((string) $file);
752
			$tmpname = $attachment_state->getAttachmentTmpPath($filename);
753
754
			// download file from the backend
755
			$relRecId = substr((string) $file, strpos((string) $file, '/'));
756
			$http_status = $initializedBackend->get_file($relRecId, $tmpname);
0 ignored issues
show
Unused Code introduced by
The assignment to $http_status is dead and can be removed.
Loading history...
757
758
			$filesize = filesize($tmpname);
759
760
			Logger::debug(self::LOG_CONTEXT, "Downloading: " . $filename . " to: " . $tmpname);
761
762
			$attach_id = uniqid();
763
			$response['items'][] = [
764
				'name' => $filename,
765
				'size' => $filesize,
766
				"attach_id" => $attach_id,
767
				'tmpname' => PathUtil::getFilenameFromPath($tmpname),
768
			];
769
770
			$attachment_state->addAttachmentFile($dialogAttachmentId, PathUtil::getFilenameFromPath($tmpname), [
771
				"name" => $filename,
772
				"size" => $filesize,
773
				"type" => PathUtil::get_mime($tmpname),
774
				"attach_id" => $attach_id,
775
				"sourcetype" => 'default',
776
			]);
777
778
			Logger::debug(self::LOG_CONTEXT, "filesize: " . $filesize);
779
		}
780
781
		$attachment_state->close();
782
		$response['status'] = true;
783
		$this->addActionData($actionType, $response);
784
		$GLOBALS["bus"]->addData($this->getResponseData());
785
	}
786
787
	/**
788
	 * upload the tempfile to files.
789
	 *
790
	 * @param array $actionData
791
	 * @param mixed $actionType
792
	 *
793
	 * @throws BackendException if the backend request fails
794
	 */
795
	private function uploadToBackend($actionType, $actionData) {
796
		Logger::debug(self::LOG_CONTEXT, "preparing attachment");
797
798
		$account = $this->accountFromNode($actionData["destdir"]);
799
800
		// initialize the backend
801
		$initializedBackend = $this->initializeBackend($account);
802
803
		$result = true;
804
805
		if ($actionData["type"] === "attachment") {
806
			foreach ($actionData["items"] as $item) {
807
				[$tmpname, $filename] = $this->prepareAttachmentForUpload($item);
808
809
				$dirName = substr((string) $actionData["destdir"], strpos((string) $actionData["destdir"], '/'));
810
				$filePath = $dirName . $filename;
811
812
				Logger::debug(self::LOG_CONTEXT, "Uploading to: " . $filePath . " tmpfile: " . $tmpname);
813
814
				$result = $result && $initializedBackend->put_file($filePath, $tmpname);
815
				unlink($tmpname);
816
817
				$this->updateDirCache($initializedBackend, $dirName, $filePath, $actionData);
818
			}
819
		}
820
		elseif ($actionData["type"] === "mail") {
821
			foreach ($actionData["items"] as $item) {
822
				[$tmpname, $filename] = $this->prepareEmailForUpload($item);
823
824
				$dirName = substr((string) $actionData["destdir"], strpos((string) $actionData["destdir"], '/'));
825
				$filePath = $dirName . $filename;
826
827
				Logger::debug(self::LOG_CONTEXT, "Uploading to: " . $filePath . " tmpfile: " . $tmpname);
828
829
				$result = $result && $initializedBackend->put_file($filePath, $tmpname);
830
				unlink($tmpname);
831
832
				$this->updateDirCache($initializedBackend, $dirName, $filePath, $actionData);
833
			}
834
		}
835
		else {
836
			$this->sendFeedback(false, [
837
				'type' => ERROR_GENERAL,
838
				'info' => [
839
					'title' => _("Files plugin"),
840
					'original_message' => _("Unknown type - cannot save this file to the Files backend!"),
841
					'display_message' => _("Unknown type - cannot save this file to the Files backend!"),
842
				],
843
			]);
844
		}
845
846
		$response = [];
847
		$response['status'] = $result;
848
		$this->addActionData($actionType, $response);
849
		$GLOBALS["bus"]->addData($this->getResponseData());
850
	}
851
852
	/**
853
	 * Update the cache of selected directory.
854
	 *
855
	 * @param AbstractBackend $backendInstance
856
	 * @param string          $dirName         The directory name
857
	 * @param                 $filePath        The file path
858
	 * @param                 $actionData      The action data
859
	 *
860
	 * @throws BackendException
861
	 */
862
	public function updateDirCache($backendInstance, $dirName, $filePath, $actionData) {
863
		$cachePath = rtrim($dirName, '/');
864
		if ($cachePath === "") {
865
			$cachePath = "/";
866
		}
867
868
		$dir = $backendInstance->ls($cachePath);
869
		$accountID = $this->accountIDFromNode($actionData["destdir"]);
870
		$cacheDir = $this->getCache($accountID, $cachePath);
871
		$cacheDir[$filePath] = $dir[$filePath];
872
		$this->setCache($accountID, $cachePath, $cacheDir);
0 ignored issues
show
Bug introduced by
$cacheDir of type iterable is incompatible with the type string expected by parameter $data of FilesListModule::setCache(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

872
		$this->setCache($accountID, $cachePath, /** @scrutinizer ignore-type */ $cacheDir);
Loading history...
873
	}
874
875
	/**
876
	 * This function will prepare an attachment for the upload to the backend.
877
	 * It will store the attachment to the TMP folder and return its temporary
878
	 * path and filename as array.
879
	 *
880
	 * @param mixed $item
881
	 *
882
	 * @return array (tmpname, filename) or false on error
883
	 */
884
	private function prepareAttachmentForUpload($item) {
885
		// Check which type isset
886
		$openType = "attachment";
0 ignored issues
show
Unused Code introduced by
The assignment to $openType is dead and can be removed.
Loading history...
887
888
		// Get store id
889
		$storeid = false;
890
		if (isset($item["store"])) {
891
			$storeid = $item["store"];
892
		}
893
894
		// Get message entryid
895
		$entryid = false;
896
		if (isset($item["entryid"])) {
897
			$entryid = $item["entryid"];
898
		}
899
900
		// Get number of attachment which should be opened.
901
		$attachNum = false;
902
		if (isset($item["attachNum"])) {
903
			$attachNum = $item["attachNum"];
904
		}
905
906
		$tmpname = "";
0 ignored issues
show
Unused Code introduced by
The assignment to $tmpname is dead and can be removed.
Loading history...
907
		$filename = "";
0 ignored issues
show
Unused Code introduced by
The assignment to $filename is dead and can be removed.
Loading history...
908
909
		// Check if storeid and entryid isset
910
		if ($storeid && $entryid) {
911
			// Open the store
912
			$store = $GLOBALS["mapisession"]->openMessageStore(hex2bin((string) $storeid));
913
914
			if ($store) {
915
				// Open the message
916
				$message = mapi_msgstore_openentry($store, hex2bin((string) $entryid));
917
918
				if ($message) {
919
					$attachment = false;
920
921
					// Check if attachNum isset
922
					if ($attachNum) {
923
						// Loop through the attachNums, message in message in message ...
924
						for ($i = 0; $i < (count($attachNum) - 1); ++$i) {
925
							// Open the attachment
926
							$tempattach = mapi_message_openattach($message, (int) $attachNum[$i]);
927
							if ($tempattach) {
928
								// Open the object in the attachment
929
								$message = mapi_attach_openobj($tempattach);
930
							}
931
						}
932
933
						// Open the attachment
934
						$attachment = mapi_message_openattach($message, (int) $attachNum[count($attachNum) - 1]);
935
					}
936
937
					// Check if the attachment is opened
938
					if ($attachment) {
939
						// Get the props of the attachment
940
						$props = mapi_attach_getprops($attachment, [PR_ATTACH_LONG_FILENAME, PR_ATTACH_MIME_TAG, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
941
						// Content Type
942
						$contentType = "application/octet-stream";
943
						// Filename
944
						$filename = "ERROR";
945
946
						// Set filename
947
						if (isset($props[PR_ATTACH_LONG_FILENAME])) {
948
							$filename = PathUtil::sanitizeFilename($props[PR_ATTACH_LONG_FILENAME]);
949
						}
950
						else {
951
							if (isset($props[PR_ATTACH_FILENAME])) {
952
								$filename = PathUtil::sanitizeFilename($props[PR_ATTACH_FILENAME]);
953
							}
954
							else {
955
								if (isset($props[PR_DISPLAY_NAME])) {
956
									$filename = PathUtil::sanitizeFilename($props[PR_DISPLAY_NAME]);
957
								}
958
							}
959
						}
960
961
						// Set content type
962
						if (isset($props[PR_ATTACH_MIME_TAG])) {
963
							$contentType = $props[PR_ATTACH_MIME_TAG];
0 ignored issues
show
Unused Code introduced by
The assignment to $contentType is dead and can be removed.
Loading history...
964
						}
965
						else {
966
							// Parse the extension of the filename to get the content type
967
							if (strrpos($filename, ".") !== false) {
968
								$extension = strtolower(substr($filename, strrpos($filename, ".")));
969
								$contentType = "application/octet-stream";
970
								if (is_readable("mimetypes.dat")) {
971
									$fh = fopen("mimetypes.dat", "r");
972
									$ext_found = false;
973
									while (!feof($fh) && !$ext_found) {
974
										$line = fgets($fh);
975
										preg_match("/(\\.[a-z0-9]+)[ \t]+([^ \t\n\r]*)/i", $line, $result);
976
										if ($extension == $result[1]) {
977
											$ext_found = true;
978
											$contentType = $result[2];
979
										}
980
									}
981
									fclose($fh);
982
								}
983
							}
984
						}
985
986
						$tmpname = tempnam(TMP_PATH, stripslashes($filename));
987
988
						// Open a stream to get the attachment data
989
						$stream = mapi_openproperty($attachment, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0);
990
						$stat = mapi_stream_stat($stream);
991
						// File length =  $stat["cb"]
992
993
						Logger::debug(self::LOG_CONTEXT, "filesize: " . $stat["cb"]);
994
995
						$fhandle = fopen($tmpname, 'w');
996
						$buffer = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $buffer is dead and can be removed.
Loading history...
997
						for ($i = 0; $i < $stat["cb"]; $i += BLOCK_SIZE) {
998
							// Write stream
999
							$buffer = mapi_stream_read($stream, BLOCK_SIZE);
1000
							fwrite($fhandle, $buffer, strlen($buffer));
1001
						}
1002
						fclose($fhandle);
1003
1004
						Logger::debug(self::LOG_CONTEXT, "temp attachment written to " . $tmpname);
1005
1006
						return [$tmpname, $filename];
1007
					}
1008
				}
1009
			}
1010
			else {
1011
				Logger::error(self::LOG_CONTEXT, "store could not be opened");
1012
			}
1013
		}
1014
		else {
1015
			Logger::error(self::LOG_CONTEXT, "wrong call, store and entryid have to be set");
1016
		}
1017
1018
		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...
1019
	}
1020
1021
	/**
1022
	 * Store the email as eml to a temporary directory and return its temporary filename.
1023
	 *
1024
	 * @param mixed $item
1025
	 *
1026
	 * @return array (tmpname, filename) or false on error
1027
	 */
1028
	private function prepareEmailForUpload($item) {
1029
		// Get store id
1030
		$storeid = false;
1031
		if (isset($item["store"])) {
1032
			$storeid = $item["store"];
1033
		}
1034
1035
		// Get message entryid
1036
		$entryid = false;
1037
		if (isset($item["entryid"])) {
1038
			$entryid = $item["entryid"];
1039
		}
1040
1041
		$tmpname = "";
0 ignored issues
show
Unused Code introduced by
The assignment to $tmpname is dead and can be removed.
Loading history...
1042
		$filename = "";
1043
1044
		$store = $GLOBALS['mapisession']->openMessageStore(hex2bin($storeid));
1045
		$message = mapi_msgstore_openentry($store, hex2bin($entryid));
1046
1047
		// Decode smime signed messages on this message
1048
		parse_smime($store, $message);
1049
1050
		if ($message && $store) {
1051
			// get message properties.
1052
			$messageProps = mapi_getprops($message, [PR_SUBJECT, PR_MESSAGE_CLASS]);
1053
1054
			$isSupportedMessage = (
1055
				(stripos((string) $messageProps[PR_MESSAGE_CLASS], 'IPM.Note') === 0) ||
1056
				(stripos((string) $messageProps[PR_MESSAGE_CLASS], 'Report.IPM.Note') === 0) ||
1057
				(stripos((string) $messageProps[PR_MESSAGE_CLASS], 'IPM.Schedule') === 0)
1058
			);
1059
1060
			if ($isSupportedMessage) {
1061
				// Get addressbook for current session
1062
				$addrBook = $GLOBALS['mapisession']->getAddressbook();
1063
1064
				// Read the message as RFC822-formatted e-mail stream.
1065
				$stream = mapi_inetmapi_imtoinet($GLOBALS['mapisession']->getSession(), $addrBook, $message, []);
1066
1067
				if (!empty($messageProps[PR_SUBJECT])) {
1068
					$filename = PathUtil::sanitizeFilename($messageProps[PR_SUBJECT]) . '.eml';
1069
				}
1070
				else {
1071
					$filename = _('Untitled') . '.eml';
1072
				}
1073
1074
				$tmpname = tempnam(TMP_PATH, "email2filez");
1075
1076
				// Set the file length
1077
				$stat = mapi_stream_stat($stream);
1078
1079
				$fhandle = fopen($tmpname, 'w');
1080
				$buffer = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $buffer is dead and can be removed.
Loading history...
1081
				for ($i = 0; $i < $stat["cb"]; $i += BLOCK_SIZE) {
1082
					// Write stream
1083
					$buffer = mapi_stream_read($stream, BLOCK_SIZE);
1084
					fwrite($fhandle, $buffer, strlen($buffer));
1085
				}
1086
				fclose($fhandle);
1087
1088
				return [$tmpname, $filename];
1089
			}
1090
		}
1091
1092
		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...
1093
	}
1094
1095
	/**
1096
	 * Get sharing information from the backend.
1097
	 *
1098
	 * @return bool
1099
	 */
1100
	private function getSharingInformation($actionType, $actionData) {
1101
		$response = [];
1102
		$records = $actionData["records"];
1103
1104
		if (count($records) < 1) {
1105
			$this->sendFeedback(false, [
1106
				'type' => ERROR_GENERAL,
1107
				'info' => [
1108
					'title' => _("Files Plugin"),
1109
					'original_message' => _("No record given!"),
1110
					'display_message' => _("No record given!"),
1111
				],
1112
			]);
1113
		}
1114
1115
		$account = $this->accountFromNode($records[0]);
1116
1117
		// initialize the backend
1118
		$initializedBackend = $this->initializeBackend($account);
1119
1120
		$relRecords = [];
1121
		foreach ($records as $record) {
1122
			$relRecords[] = substr((string) $record, strpos((string) $record, '/')); // remove account id
1123
		}
1124
1125
		try {
1126
			$sInfo = $initializedBackend->sharingDetails($relRecords);
1127
		}
1128
		catch (Exception $e) {
1129
			$response['status'] = false;
1130
			$response['header'] = _('Fetching sharing information failed');
1131
			$response['message'] = $e->getMessage();
1132
			$this->addActionData("error", $response);
1133
			$GLOBALS["bus"]->addData($this->getResponseData());
1134
1135
			return false;
1136
		}
1137
1138
		$sharingInfo = [];
1139
		foreach ($sInfo as $path => $details) {
1140
			$realPath = "#R#" . $account->getId() . $path;
1141
			$sharingInfo[$realPath] = $details; // add account id again
1142
		}
1143
1144
		$response['status'] = true;
1145
		$response['shares'] = $sharingInfo;
1146
		$this->addActionData($actionType, $response);
1147
		$GLOBALS["bus"]->addData($this->getResponseData());
1148
1149
		return true;
1150
	}
1151
1152
	/**
1153
	 * Create a new share.
1154
	 *
1155
	 * @return bool
1156
	 */
1157
	private function createNewShare($actionType, $actionData) {
1158
		$records = $actionData["records"];
1159
		$shareOptions = $actionData["options"];
1160
1161
		if (count($records) < 1) {
1162
			$this->sendFeedback(false, [
1163
				'type' => ERROR_GENERAL,
1164
				'info' => [
1165
					'title' => _("Files Plugin"),
1166
					'original_message' => _("No record given!"),
1167
					'display_message' => _("No record given!"),
1168
				],
1169
			]);
1170
		}
1171
1172
		$account = $this->accountFromNode($records[0]);
1173
1174
		// initialize the backend
1175
		$initializedBackend = $this->initializeBackend($account);
1176
1177
		$sharingRecords = [];
1178
		foreach ($records as $record) {
1179
			$path = substr((string) $record, strpos((string) $record, '/')); // remove account id
1180
			$sharingRecords[$path] = $shareOptions; // add options
1181
		}
1182
1183
		try {
1184
			$sInfo = $initializedBackend->share($sharingRecords);
1185
		}
1186
		catch (Exception $e) {
1187
			$response['status'] = false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$response was never initialized. Although not strictly required by PHP, it is generally a good practice to add $response = array(); before regardless.
Loading history...
1188
			$response['header'] = _('Sharing failed');
1189
			$response['message'] = $e->getMessage();
1190
			$this->addActionData("error", $response);
1191
			$GLOBALS["bus"]->addData($this->getResponseData());
1192
1193
			return false;
1194
		}
1195
1196
		$sharingInfo = [];
1197
		foreach ($sInfo as $path => $details) {
1198
			$realPath = "#R#" . $account->getId() . $path;
1199
			$sharingInfo[$realPath] = $details; // add account id again
1200
		}
1201
1202
		$response = [];
1203
		$response['status'] = true;
1204
		$response['shares'] = $sharingInfo;
1205
		$this->addActionData($actionType, $response);
1206
		$GLOBALS["bus"]->addData($this->getResponseData());
1207
1208
		return true;
1209
	}
1210
1211
	/**
1212
	 * Update a existing share.
1213
	 *
1214
	 * @return bool
1215
	 */
1216
	private function updateExistingShare($actionType, $actionData) {
1217
		$records = $actionData["records"];
1218
		$accountID = $actionData["accountid"];
1219
		$shareOptions = $actionData["options"];
1220
1221
		if (count($records) < 1) {
1222
			$this->sendFeedback(false, [
1223
				'type' => ERROR_GENERAL,
1224
				'info' => [
1225
					'title' => _("Files Plugin"),
1226
					'original_message' => _("No record given!"),
1227
					'display_message' => _("No record given!"),
1228
				],
1229
			]);
1230
		}
1231
1232
		$account = $this->accountStore->getAccount($accountID);
1233
1234
		// initialize the backend
1235
		$initializedBackend = $this->initializeBackend($account);
1236
1237
		$sharingRecords = [];
1238
		foreach ($records as $record) {
1239
			$sharingRecords[$record] = $shareOptions; // add options
1240
		}
1241
1242
		try {
1243
			$sInfo = $initializedBackend->share($sharingRecords, true);
1244
		}
1245
		catch (Exception $e) {
1246
			$response['status'] = false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$response was never initialized. Although not strictly required by PHP, it is generally a good practice to add $response = array(); before regardless.
Loading history...
1247
			$response['header'] = _('Updating share failed');
1248
			$response['message'] = $e->getMessage();
1249
			$this->addActionData("error", $response);
1250
			$GLOBALS["bus"]->addData($this->getResponseData());
1251
1252
			return false;
1253
		}
1254
1255
		$response = [];
1256
		$response['status'] = true;
1257
		$response['shares'] = $sInfo;
1258
		$this->addActionData($actionType, $response);
1259
		$GLOBALS["bus"]->addData($this->getResponseData());
1260
1261
		return true;
1262
	}
1263
1264
	/**
1265
	 * Delete one or more shares.
1266
	 *
1267
	 * @return bool
1268
	 */
1269
	private function deleteExistingShare($actionType, $actionData) {
1270
		$records = $actionData["records"];
1271
		$accountID = $actionData["accountid"];
1272
1273
		if (count($records) < 1) {
1274
			$this->sendFeedback(false, [
1275
				'type' => ERROR_GENERAL,
1276
				'info' => [
1277
					'title' => _("Files Plugin"),
1278
					'original_message' => _("No record given!"),
1279
					'display_message' => _("No record given!"),
1280
				],
1281
			]);
1282
		}
1283
1284
		$account = $this->accountStore->getAccount($accountID);
1285
1286
		// initialize the backend
1287
		$initializedBackend = $this->initializeBackend($account);
1288
1289
		try {
1290
			$sInfo = $initializedBackend->unshare($records);
0 ignored issues
show
Unused Code introduced by
The assignment to $sInfo is dead and can be removed.
Loading history...
1291
		}
1292
		catch (Exception $e) {
1293
			$response['status'] = false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$response was never initialized. Although not strictly required by PHP, it is generally a good practice to add $response = array(); before regardless.
Loading history...
1294
			$response['header'] = _('Deleting share failed');
1295
			$response['message'] = $e->getMessage();
1296
			$this->addActionData("error", $response);
1297
			$GLOBALS["bus"]->addData($this->getResponseData());
1298
1299
			return false;
1300
		}
1301
1302
		$response = [];
1303
		$response['status'] = true;
1304
		$this->addActionData($actionType, $response);
1305
		$GLOBALS["bus"]->addData($this->getResponseData());
1306
1307
		return true;
1308
	}
1309
1310
	/**
1311
	 * Function will use to update the cache.
1312
	 *
1313
	 * @param string $actionType name of the current action
1314
	 * @param array  $actionData all parameters contained in this request
1315
	 *
1316
	 * @return bool true on success or false on failure
1317
	 */
1318
	public function updateCache($actionType, $actionData) {
1319
		$nodeId = $actionData['id'];
1320
		$accountID = $this->accountIDFromNode($nodeId);
1321
		$account = $this->accountStore->getAccount($accountID);
1322
		// initialize the backend
1323
		$initializedBackend = $this->initializeBackend($account, true);
1324
		$relNodeId = substr((string) $nodeId, strpos((string) $nodeId, '/'));
1325
1326
		// remove the trailing slash for the cache key
1327
		$cachePath = rtrim($relNodeId, '/');
1328
		if ($cachePath === "") {
1329
			$cachePath = "/";
1330
		}
1331
		$dir = $initializedBackend->ls($relNodeId);
1332
		$this->setCache($accountID, $cachePath, $dir);
1333
1334
		$response = [];
1335
		$response['status'] = true;
1336
		$this->addActionData($actionType, $response);
1337
		$GLOBALS["bus"]->addData($this->getResponseData());
1338
1339
		return true;
1340
	}
1341
}
1342