FilesListModule   F
last analyzed

Complexity

Total Complexity 88

Size/Duplication

Total Lines 741
Duplicated Lines 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 339
c 1
b 1
f 0
dl 0
loc 741
rs 2
wmc 88

20 Methods

Rating   Name   Duplication   Size   Complexity  
A accountFromNode() 0 2 1
C save() 0 141 15
A initializeBackend() 0 9 2
C hasSubFolder() 0 46 13
A getActionStore() 0 17 5
B getHierarchyList() 0 92 6
D getSubFolders() 0 112 21
A getParentNode() 0 15 4
A getVersionFromCache() 0 4 1
A notifySubFolders() 0 7 2
A makeCacheKey() 0 2 1
A setCache() 0 4 1
A accountIDFromNode() 0 2 1
A createId() 0 2 1
A getCache() 0 5 1
A setVersionInCache() 0 12 3
A __construct() 0 32 2
A updateCacheAfterRename() 0 26 6
A clearCache() 0 2 1
A deleteCache() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like FilesListModule 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 FilesListModule, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
require_once __DIR__ . "/../Files/Core/class.accountstore.php";
4
require_once __DIR__ . "/../Files/Backend/class.backendstore.php";
5
6
require_once __DIR__ . "/../Files/Core/class.exception.php";
7
require_once __DIR__ . "/../Files/Backend/class.exception.php";
8
9
require_once __DIR__ . "/../Files/Core/Util/class.logger.php";
10
require_once __DIR__ . "/../Files/Core/Util/class.stringutil.php";
11
12
require_once __DIR__ . "/../vendor/autoload.php";
13
14
use Files\Backend\BackendStore;
15
use Files\Core\Account;
16
use Files\Core\AccountStore;
17
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...
18
use Files\Core\Util\StringUtil;
19
use Phpfastcache\CacheManager;
20
use Phpfastcache\Drivers\Redis\Config as RedisConfig;
21
22
/**
23
 * This module handles all list and change requests for the files browser.
24
 *
25
 * @class FilesListModule
26
 *
27
 * @extends ListModule
28
 */
29
class FilesListModule extends ListModule {
30
	public const LOG_CONTEXT = "FilesListModule"; // Context for the Logger
31
32
	// Unauthorized errors of different backends.
33
	public const SMB_ERR_UNAUTHORIZED = 13;
34
	public const SMB_ERR_FORBIDDEN = 1;
35
	public const FTP_WD_OWNCLOUD_ERR_UNAUTHORIZED = 401;
36
	public const FTP_WD_OWNCLOUD_ERR_FORBIDDEN = 403;
37
	public const ALL_BACKEND_ERR_NOTFOUND = 404;
38
39
	/**
40
	 * @var phpFastCache cache handler
41
	 */
42
	public $cache;
43
44
	/**
45
	 * @var string User id of the currently logged in user. Used to generate unique cache id's.
46
	 */
47
	public $uid;
48
49
	/**
50
	 * @var object The account store holding all available accounts
51
	 */
52
	public $accountStore;
53
54
	/**
55
	 * @var object The backend store holding all available backends
56
	 */
57
	public $backendStore;
58
59
	/**
60
	 * @constructor
61
	 */
62
	public function __construct($id, $data) {
63
		parent::__construct($id, $data);
64
65
		// Initialize the account and backendstore
66
		$this->accountStore = new AccountStore();
67
		$this->backendStore = BackendStore::getInstance();
68
69
		// Setup the cache
70
		$config = new RedisConfig();
71
		$config->setHost(PLUGIN_FILES_REDIS_HOST);
72
		$config->setPort(PLUGIN_FILES_REDIS_PORT);
0 ignored issues
show
Bug introduced by
PLUGIN_FILES_REDIS_PORT of type string is incompatible with the type integer expected by parameter $port of Phpfastcache\Drivers\Redis\Config::setPort(). ( Ignorable by Annotation )

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

72
		$config->setPort(/** @scrutinizer ignore-type */ PLUGIN_FILES_REDIS_PORT);
Loading history...
73
		$config->setPassword(PLUGIN_FILES_REDIS_AUTH);
74
75
		$this->cache = CacheManager::getInstance('Redis', $config);
0 ignored issues
show
Documentation Bug introduced by
It seems like Phpfastcache\CacheManage...tance('Redis', $config) of type Phpfastcache\Core\Pool\E...dCacheItemPoolInterface is incompatible with the declared type phpFastCache of property $cache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
76
77
		// For backward compatibility we will check if the Encryption store exists. If not,
78
		// we will fall back to the old way of retrieving the password from the session.
79
		if (class_exists('EncryptionStore')) {
80
			// Get the username from the Encryption store
81
			$encryptionStore = EncryptionStore::getInstance();
82
			$this->uid = $encryptionStore->get('username');
83
		}
84
		else {
85
			$this->uid = $_SESSION["username"];
86
		}
87
		// As of the V6, the following characters can not longer being a part of the key identifier: {}()/\@:
88
		// If you try to do so, an \phpFastCache\Exceptions\phpFastCacheInvalidArgumentException will be raised.
89
		// You must replace them with a safe delimiter such as .|-_
90
		// @see https://github.com/PHPSocialNetwork/phpfastcache/blob/8.1.2/docs/migration/MigratingFromV5ToV6.md
91
		$this->uid = str_replace(['{', '}', '(', ')', '/', '\\', '@'], '_', $this->uid);
92
93
		Logger::debug(self::LOG_CONTEXT, "[constructor]: executing the module as uid: " . $this->uid);
94
	}
95
96
	/**
97
	 * Function get the folder data from backend.
98
	 *
99
	 * @param mixed $isReload
100
	 *
101
	 * @return array return folders array
102
	 */
103
	public function getHierarchyList($isReload = false) {
104
		$data = [];
105
		$data["item"] = [];
106
		$versions = $GLOBALS['PluginManager']->getPluginsVersion();
107
		$filesVersion = $versions['files'];
108
109
		$filesVersionFromCache = $this->getVersionFromCache('files');
110
		if (!is_string($filesVersionFromCache)) {
0 ignored issues
show
introduced by
The condition is_string($filesVersionFromCache) is always true.
Loading history...
111
			$filesVersionFromCache = '0';
112
		}
113
		// Clear cache when version gets changed and update 'files' version in cache.
114
		if ($isReload || version_compare($filesVersionFromCache, $filesVersion) !== 0) {
115
			$this->clearCache();
116
			$this->setVersionInCache('files', $filesVersion);
117
		}
118
119
		$accounts = $this->accountStore->getAllAccounts();
120
		foreach ($accounts as $account) {
121
			// we have to load all accounts and their folders
122
			// skip accounts that are not valid
123
			if ($account->getStatus() !== Account::STATUS_OK) {
124
				continue;
125
			}
126
127
			// build the real node id for this folder
128
			$realNodeId = "#R#" . $account->getId() . "/";
129
			$accountName = $account->getName();
130
			$rootId = $this->createId($realNodeId);
131
			$nodes = [
132
				"store_entryid" => $rootId,
133
				"props" => [
134
					'entryid' => $rootId,
135
					'subtree_id' => $rootId,
136
					'display_name' => $accountName,
137
					"object_type" => FILES_STORE,
138
					"status" => $account->getStatus(),
139
					"status_description" => $account->getStatusDescription(),
140
					"backend" => $account->getBackend(),
141
					"backend_config" => $account->getBackendConfig(),
142
					'backend_features' => $account->getFeatures(),
143
					'filename' => $accountName,
144
					'account_sequence' => $account->getSequence(),
145
					'cannot_change' => $account->getCannotChangeFlag(),
146
				],
147
			];
148
149
			$initializedBackend = $this->initializeBackend($account, true);
150
151
			// Get sub folder of root folder.
152
			$subFolders = $this->getSubFolders($realNodeId, $initializedBackend);
153
154
			array_push($subFolders, [
155
				'id' => $realNodeId,
156
				'folder_id' => $realNodeId,
157
				'entryid' => $rootId,
158
				'parent_entryid' => $rootId,
159
				'store_entryid' => $rootId,
160
				'props' => [
161
					'path' => $realNodeId,
162
					'icon_index' => ICON_FOLDER,
163
					// Fixme : remove text property. we have to use display_name property.
164
					'text' => $accountName,
165
					'has_subfolder' => empty($subFolders) === false,
166
					'object_type' => FILES_FOLDER,
167
					'filename' => $accountName,
168
					'display_name' => $accountName,
169
				],
170
			]);
171
172
			// TODO: dummy folder which used client side to show the account view when user
173
			//  switch to home folder using navigation bar.
174
			array_push($subFolders, [
175
				'id' => "#R#",
176
				'folder_id' => "#R#",
177
				'entryid' => "#R#",
178
				'parent_entryid' => $rootId,
179
				'store_entryid' => $rootId,
180
				'props' => [
181
					'path' => $realNodeId,
182
					'icon_index' => ICON_HOME_FOLDER,
183
					'text' => "Files",
184
					'has_subfolder' => false,
185
					'object_type' => FILES_FOLDER,
186
					'filename' => "Files",
187
					'display_name' => "Files",
188
				],
189
			]);
190
			$nodes["folders"] = ["item" => $subFolders];
191
			array_push($data["item"], $nodes);
192
		}
193
194
		return $data;
195
	}
196
197
	/**
198
	 * Function used to get the sub folders of the given folder id.
199
	 *
200
	 * @param string $nodeId    the folder id which used to get sub folders
201
	 * @param object $backend   The backend which used to retrieve the folders
202
	 * @param bool   $recursive the recursive true which get the sub folder recursively
203
	 * @param array  $nodes     the nodes contains the array of nodes
204
	 *
205
	 * @return array return the array folders
206
	 */
207
	public function getSubFolders($nodeId, $backend, $recursive = false, $nodes = []) {
208
		// relative node ID. We need to trim off the #R# and account ID
209
		$relNodeId = substr($nodeId, strpos($nodeId, '/'));
210
		$nodeIdPrefix = substr($nodeId, 0, strpos($nodeId, '/'));
211
212
		$accountID = $backend->getAccountID();
213
214
		// remove the trailing slash for the cache key
215
		$cachePath = rtrim($relNodeId, '/');
216
		if ($cachePath === "") {
217
			$cachePath = "/";
218
		}
219
220
		$backendDisplayName = $backend->backendDisplayName;
221
		$backendVersion = $backend->backendVersion;
222
		$cacheVersion = $this->getVersionFromCache($backendDisplayName, $accountID);
223
		if (!is_string($cacheVersion)) {
0 ignored issues
show
introduced by
The condition is_string($cacheVersion) is always true.
Loading history...
224
			$cacheVersion = '0';
225
		}
226
		$dir = $this->getCache($accountID, $cachePath);
227
228
		// Get new data from backend when cache is empty or the version of backend got changed.
229
		if (is_null($dir) || version_compare($backendVersion, $cacheVersion) !== 0) {
230
			$this->setVersionInCache($backendDisplayName, $backendVersion, $accountID);
231
			$dir = $backend->ls($relNodeId);
232
		}
233
234
		if ($dir) {
235
			$updateCache = false;
236
			foreach ($dir as $id => $node) {
237
				$objectType = strcmp((string) $node['resourcetype'], "collection") !== 0 ? FILES_FILE : FILES_FOLDER;
238
239
				// Only get the Folder item.
240
				if ($objectType !== FILES_FOLDER) {
241
					continue;
242
				}
243
244
				// Check if foldernames have a trailing slash, if not, add one!
245
				if (!StringUtil::endsWith($id, "/")) {
246
					unset($dir[$id]);
247
					$id .= "/";
248
					$dir[$id] = $node;
249
				}
250
251
				$size = $node['getcontentlength'] === null ? -1 : intval($node['getcontentlength']);
252
				// folder's dont have a size
253
				$size = $objectType == FILES_FILE ? $size : -1;
254
255
				$realID = $nodeIdPrefix . $id;
256
				$filename = stringToUTF8Encode(basename((string) $id));
257
258
				if (!isset($node['entryid']) || !isset($node['parent_entryid']) || !isset($node['store_entryid'])) {
259
					$parentNode = $this->getParentNode($cachePath, $accountID);
260
261
					$entryid = $this->createId($realID);
262
					$parentEntryid = $parentNode !== false && isset($parentNode['entryid']) ? $parentNode['entryid'] : $this->createId($nodeId);
263
					$storeEntryid = $this->createId($nodeIdPrefix . '/');
264
265
					$dir[$id]['entryid'] = $entryid;
266
					$dir[$id]['parent_entryid'] = $parentEntryid;
267
					$dir[$id]['store_entryid'] = $storeEntryid;
268
269
					$updateCache = true;
270
				}
271
				else {
272
					$entryid = $node['entryid'];
273
					$parentEntryid = $node['parent_entryid'];
274
					$storeEntryid = $node['store_entryid'];
275
				}
276
277
				$nodeHasSubFolder = $this->hasSubFolder($id, $accountID, $backend);
278
				// Skip displaying folder whose data is unaccesable.
279
				// Also update the cache.
280
				if (is_null($nodeHasSubFolder)) {
281
					unset($dir[$id]);
282
					$updateCache = true;
283
				}
284
				else {
285
					array_push($nodes, [
286
						'id' => $realID,
287
						'folder_id' => $realID,
288
						'entryid' => $entryid,
289
						'parent_entryid' => $parentEntryid,
290
						'store_entryid' => $storeEntryid,
291
						'props' => [
292
							'path' => $nodeId,
293
							'message_size' => $size,
294
							'text' => $filename,
295
							'object_type' => $objectType,
296
							'icon_index' => ICON_FOLDER,
297
							'filename' => $filename,
298
							'display_name' => $filename,
299
							'lastmodified' => strtotime((string) $node['getlastmodified']) * 1000,
300
							'has_subfolder' => $nodeHasSubFolder,
301
						],
302
					]);
303
				}
304
305
				// We need to call this function recursively when user rename the folder.
306
				// we have to send all sub folder as server side notification so grommunio Web
307
				// can update the sub folder as per it's parent folder is renamed.
308
				if ($objectType === FILES_FOLDER && $recursive) {
309
					$nodes = $this->getSubFolders($realID, $backend, true, $nodes);
310
				}
311
			}
312
313
			if ($updateCache) {
314
				$this->setCache($accountID, $cachePath, $dir);
315
			}
316
		}
317
318
		return $nodes;
319
	}
320
321
	/**
322
	 * Function which used to get the parent folder of selected folder.
323
	 *
324
	 * @param string $cachePath the cache path of selected folder
325
	 * @param string $accountID the account ID in which folder is belongs
326
	 *
327
	 * @return array|bool return the parent folder data else false
328
	 */
329
	public function getParentNode($cachePath, $accountID) {
330
		$parentNode = dirname($cachePath, 1);
331
332
		// remove the trailing slash for the cache key
333
		$parentNode = rtrim($parentNode, '/');
334
		if ($parentNode === "") {
335
			$parentNode = "/";
336
		}
337
		$dir = $this->getCache($accountID, $parentNode);
338
339
		if (!is_null($dir) && isset($dir[$cachePath . '/'])) {
0 ignored issues
show
introduced by
The condition is_null($dir) is always false.
Loading history...
340
			return $dir[$cachePath . '/'];
341
		}
342
343
		return false;
344
	}
345
346
	/**
347
	 * Function create the unique id.
348
	 *
349
	 * @param string $id The folder id
350
	 *
351
	 * @return string generated a hash value
352
	 */
353
	public function createId($id) {
354
		return hash("tiger192,3", $id);
355
	}
356
357
	/**
358
	 * Function will check that given folder has sub folder or not.
359
	 * This will retrurn null when there's an exception retrieving folder data.
360
	 *
361
	 * @param string $id The $id is id of selected folder
362
	 *
363
	 * @return bool or null when unable to access folder data
364
	 */
365
	public function hasSubFolder($id, $accountID, $backend) {
366
		$cachePath = rtrim($id, '/');
367
		if ($cachePath === "") {
368
			$cachePath = "/";
369
		}
370
371
		$dir = $this->getCache($accountID, $cachePath);
372
		if (is_null($dir)) {
0 ignored issues
show
introduced by
The condition is_null($dir) is always false.
Loading history...
373
			try {
374
				$dir = $backend->ls($id);
375
				$this->setCache($accountID, $cachePath, $dir);
376
			}
377
			catch (Exception $e) {
378
				$errorCode = $e->getCode();
379
380
				// If folder not found or folder doesn't have enough access then don't display that folder.
381
				if ($errorCode === self::SMB_ERR_UNAUTHORIZED ||
382
				$errorCode === self::SMB_ERR_FORBIDDEN ||
383
				$errorCode === self::FTP_WD_OWNCLOUD_ERR_UNAUTHORIZED ||
384
				$errorCode === self::FTP_WD_OWNCLOUD_ERR_FORBIDDEN ||
385
				$errorCode === self::ALL_BACKEND_ERR_NOTFOUND) {
386
					if ($errorCode === self::ALL_BACKEND_ERR_NOTFOUND) {
387
						Logger::error(self::LOG_CONTEXT, '[hasSubFolder]: folder ' . $id . ' not found');
388
					}
389
					else {
390
						Logger::error(self::LOG_CONTEXT, '[hasSubFolder]: Access denied for folder ' . $id);
391
					}
392
393
					return null;
394
				}
395
396
				// rethrow exception if its not related to access permission.
397
				throw $e;
398
			}
399
		}
400
401
		if ($dir) {
0 ignored issues
show
introduced by
$dir is of type iterable, thus it always evaluated to false.
Loading history...
402
			foreach ($dir as $id => $node) {
0 ignored issues
show
introduced by
$id is overwriting one of the parameters of this function.
Loading history...
403
				if (strcmp((string) $node['resourcetype'], "collection") === 0) {
404
					// we have a folder
405
					return true;
406
				}
407
			}
408
		}
409
410
		return false;
411
	}
412
413
	/**
414
	 * @throws Files\Backend\Exception
415
	 */
416
	public function save($actionData) {
417
		$response = [];
418
		$props = $actionData["props"];
419
		$messageProps = [];
420
		if (isset($actionData["entryid"]) && empty($actionData["entryid"])) {
421
			$path = isset($props['path']) && !empty($props['path']) ? $props['path'] : "/";
422
423
			$relDirname = substr((string) $path, strpos((string) $path, '/'));
424
			$relDirname = $relDirname . $props["display_name"] . '/';
425
			$account = $this->accountFromNode($path);
426
427
			// initialize the backend
428
			$initializedBackend = $this->initializeBackend($account, true);
429
			$relDirname = stringToUTF8Encode($relDirname);
430
			$result = $initializedBackend->mkcol($relDirname); // create it !
431
432
			$filesPath = substr((string) $path, strpos((string) $path, '/'));
433
			$dir = $initializedBackend->ls($filesPath);
434
435
			$id = $path . $props["display_name"] . '/';
436
437
			$actionId = $account->getId();
438
439
			$entryid = $this->createId($id);
440
			$parentEntryid = $actionData["parent_entryid"];
441
			$storeEntryid = $this->createId('#R#' . $actionId . '/');
442
443
			$cachePath = rtrim($relDirname, '/');
444
			if ($cachePath === "") {
445
				$cachePath = "/";
446
			}
447
448
			if (isset($dir[$relDirname]) && !empty($dir[$relDirname])) {
449
				$newDir = $dir[$relDirname];
450
				$newDir['entryid'] = $entryid;
451
				$newDir['parent_entryid'] = $parentEntryid;
452
				$newDir['store_entryid'] = $storeEntryid;
453
454
				// Get old cached data.
455
				$cachedDir = $this->getCache($actionId, dirname($cachePath, 1));
456
457
				// Insert newly created folder info with entryid, parentEntryid and storeEntryid
458
				// in already cached data.
459
				$cachedDir[$relDirname] = $newDir;
460
				$dir = $cachedDir;
461
			}
462
463
			// Delete old cache.
464
			$this->deleteCache($actionId, dirname($relDirname));
465
466
			// Set data in cache.
467
			$this->setCache($actionId, dirname($relDirname), $dir);
468
469
			if ($result) {
470
				$folder = [
471
					'props' => [
472
						'path' => $path,
473
						'filename' => $props["display_name"],
474
						'display_name' => $props["display_name"],
475
						'text' => $props["display_name"],
476
						'object_type' => $props['object_type'],
477
						'has_subfolder' => false,
478
					],
479
					'id' => rawurldecode($id),
480
					'folder_id' => rawurldecode($id),
481
					'entryid' => $entryid,
482
					'parent_entryid' => $parentEntryid,
483
					'store_entryid' => $storeEntryid,
484
				];
485
				$response = $folder;
486
			}
487
		}
488
		else {
489
			// Rename/update the folder/file name
490
			$folderId = $actionData['message_action']["source_folder_id"];
491
			// rename/update the folder or files name.
492
			$parentEntryid = $actionData["parent_entryid"];
493
494
			$isfolder = "";
495
			if (str_ends_with((string) $folderId, '/')) {
496
				$isfolder = "/"; // we have a folder...
497
			}
498
499
			$src = rtrim((string) $folderId, '/');
500
			$dstdir = dirname($src) == "/" ? "" : dirname($src);
501
			$dst = $dstdir . "/" . rtrim((string) $props['filename'], '/');
502
503
			$relDst = substr($dst, strpos($dst, '/'));
504
			$relSrc = substr($src, strpos($src, '/'));
505
506
			$account = $this->accountFromNode($src);
507
508
			// initialize the backend
509
			$initializedBackend = $this->initializeBackend($account);
510
511
			$result = $initializedBackend->move($relSrc, $relDst, false);
512
513
			// get the cache data of parent directory.
514
			$dir = $this->getCache($account->getId(), dirname($relSrc));
515
			if (isset($dir[$relSrc . "/"]) && !empty($dir[$relSrc . "/"])) {
516
				$srcDir = $dir[$relSrc . "/"];
517
				unset($dir[$relSrc . "/"]);
518
				$dir[$relDst . "/"] = $srcDir;
519
520
				// Update only rename folder info in php cache.
521
				$this->setCache($account->getId(), dirname($relSrc), $dir);
0 ignored issues
show
Bug introduced by
$dir 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

521
				$this->setCache($account->getId(), dirname($relSrc), /** @scrutinizer ignore-type */ $dir);
Loading history...
522
523
				$this->updateCacheAfterRename($relSrc, $relDst, $account->getId());
524
			}
525
			else {
526
				// clear the cache
527
				$this->deleteCache($account->getId(), dirname($relSrc));
528
			}
529
530
			if ($result) {
531
				/* create the response object */
532
				$folder = [];
533
534
				// some requests might not contain a new filename... so dont update the store
535
				if (isset($props['filename'])) {
536
					$folder = [
537
						'props' => [
538
							'folder_id' => rawurldecode($dst . $isfolder),
539
							'path' => rawurldecode($dstdir),
540
							'filename' => $props['filename'],
541
							'display_name' => $props['filename'],
542
						],
543
						'entryid' => $actionData["entryid"],
544
						'parent_entryid' => $parentEntryid,
545
						'store_entryid' => $actionData["store_entryid"],
546
					];
547
				}
548
				$response['item'] = $folder;
549
				$messageProps = $folder;
550
			}
551
		}
552
553
		$this->addActionData("update", $response);
554
		$GLOBALS["bus"]->addData($this->getResponseData());
555
556
		return $messageProps;
557
	}
558
559
	/**
560
	 * Update the cache of renamed folder and it's sub folders.
561
	 *
562
	 * @param string $oldPath   The $oldPath is path of folder before rename
563
	 * @param string $newPath   The $newPath is path of folder after rename
564
	 * @param string $accountId The id of an account in which renamed folder is belongs
565
	 */
566
	public function updateCacheAfterRename($oldPath, $newPath, $accountId) {
567
		// remove the trailing slash for the cache key
568
		$cachePath = rtrim($oldPath, '/');
569
		if ($cachePath === "") {
570
			$cachePath = "/";
571
		}
572
573
		$dir = $this->getCache($accountId, $cachePath);
574
		if ($dir) {
0 ignored issues
show
introduced by
$dir is of type iterable, thus it always evaluated to false.
Loading history...
575
			foreach ($dir as $id => $node) {
576
				$newId = str_replace(dirname((string) $id), $newPath, $id);
577
				unset($dir[$id]);
578
				$dir[$newId] = $node;
579
580
				$type = FILES_FILE;
581
582
				if (strcmp((string) $node['resourcetype'], "collection") == 0) { // we have a folder
583
					$type = FILES_FOLDER;
584
				}
585
586
				if ($type === FILES_FOLDER) {
587
					$this->updateCacheAfterRename($id, rtrim($newId, '/'), $accountId);
588
				}
589
			}
590
			$this->deleteCache($accountId, $cachePath);
591
			$this->setCache($accountId, $newPath, $dir);
592
		}
593
	}
594
595
	/**
596
	 * Function used to notify the sub folder of selected/modified folder.
597
	 *
598
	 * @param string $folderID The $folderID of a folder which is modified
599
	 */
600
	public function notifySubFolders($folderID) {
601
		$account = $this->accountFromNode($folderID);
602
		$initializedBackend = $this->initializeBackend($account, true);
603
		$folderData = $this->getSubFolders($folderID, $initializedBackend, true);
604
605
		if (!empty($folderData)) {
606
			$GLOBALS["bus"]->notify(REQUEST_ENTRYID, OBJECT_SAVE, $folderData);
607
		}
608
	}
609
610
	/**
611
	 * Get the account id from a node id.
612
	 *
613
	 * @param string $nodeID Id of the file or folder to operate on
614
	 *
615
	 * @return string The account id extracted from $nodeId
616
	 */
617
	public function accountIDFromNode($nodeID) {
618
		return substr($nodeID, 3, strpos($nodeID, '/') - 3); // parse account id from node id
619
	}
620
621
	/**
622
	 * Get the account from a node id.
623
	 *
624
	 * @param mixed $nodeID
625
	 *
626
	 * @return object The account for $nodeId
627
	 */
628
	public function accountFromNode($nodeID) {
629
		return $this->accountStore->getAccount($this->accountIDFromNode($nodeID));
630
	}
631
632
	/**
633
	 * Create a key used to store data in the cache.
634
	 *
635
	 * @param string $accountID Id of the account of the data to cache
636
	 * @param string $path      Path of the file or folder to create the cache element for
637
	 *
638
	 * @return string The created key
639
	 */
640
	public function makeCacheKey($accountID, $path) {
641
		return $this->uid . md5($accountID . $path);
642
	}
643
644
	/**
645
	 * Get version data form the cache.
646
	 *
647
	 * @param string $displayName display name of the backend or file plugin
648
	 * @param string $accountID   Id of the account of the data to cache
649
	 *
650
	 * @return string version data or null if nothing was found
651
	 */
652
	public function getVersionFromCache($displayName, $accountID = '') {
653
		$key = $this->uid . $accountID . $displayName;
654
655
		return $this->cache->getItem($key)->get();
656
	}
657
658
	/**
659
	 * Set version data in the cache only when version data has been changed.
660
	 *
661
	 * @param string $displayName display name of the backend or file plugin
662
	 * @param string $version     version info of backend or file plugin which needs to be cached
663
	 * @param string $accountID   Id of the account of the data to cache
664
	 */
665
	public function setVersionInCache($displayName, $version, $accountID = '') {
666
		$olderVersionFromCache = $this->getVersionFromCache($displayName, $accountID);
667
		if (!is_string($olderVersionFromCache)) {
0 ignored issues
show
introduced by
The condition is_string($olderVersionFromCache) is always true.
Loading history...
668
			$olderVersionFromCache = '0';
669
		}
670
		// If version of files/backend is same then return.
671
		if (version_compare($olderVersionFromCache, $version) === 0) {
672
			return;
673
		}
674
675
		$key = $this->uid . $accountID . $displayName;
676
		$this->cache->save($this->cache->getItem($key)->set($version));
677
	}
678
679
	/**
680
	 * Initialize the backend for the given account.
681
	 *
682
	 * @param object $account The account object the backend should be initialized for
683
	 * @param bool   $setID   Should the accountID be set in the backend object, or not. Defaults to false.
684
	 *
685
	 * @return object The initialized backend
686
	 */
687
	public function initializeBackend($account, $setID = false) {
688
		$backend = $this->backendStore->getInstanceOfBackend($account->getBackend());
689
		$backend->init_backend($account->getBackendConfig());
690
		if ($setID) {
691
			$backend->setAccountID($account->getId());
692
		}
693
		$backend->open();
694
695
		return $backend;
696
	}
697
698
	/**
699
	 * Save directory data in the cache.
700
	 *
701
	 * @param string $accountID Id of the account of the data to cache
702
	 * @param string $path      Path of the file or folder to create the cache element for
703
	 * @param string $data      Data to be cached
704
	 */
705
	public function setCache($accountID, $path, $data) {
706
		$key = $this->makeCacheKey($accountID, $path);
707
		Logger::debug(self::LOG_CONTEXT, "Setting cache for node: " . $accountID . $path . " ## " . $key);
708
		$this->cache->save($this->cache->getItem($key)->set($data));
709
	}
710
711
	/**
712
	 * Get directotry data form the cache.
713
	 *
714
	 * @param string $accountID Id of the account of the data to get
715
	 * @param string $path      Path of the file or folder to retrieve the cache element for
716
	 *
717
	 * @return iterable The directory data or null if nothing was found
718
	 */
719
	public function getCache($accountID, $path) {
720
		$key = $this->makeCacheKey($accountID, $path);
721
		Logger::debug(self::LOG_CONTEXT, "Getting cache for node: " . $accountID . $path . " ## " . $key);
722
723
		return $this->cache->getItem($key)->get();
724
	}
725
726
	/**
727
	 * Remove data from the cache.
728
	 *
729
	 * @param string $accountID Id of the account to delete the cache for
730
	 * @param string $path      Path of the file or folder to delete the cache element
731
	 */
732
	public function deleteCache($accountID, $path) {
733
		$key = $this->makeCacheKey($accountID, $path);
734
		Logger::debug(self::LOG_CONTEXT, "Removing cache for node: " . $accountID . $path . " ## " . $key);
735
		$this->cache->deleteItem($key);
736
	}
737
738
	/**
739
	 * Function clear the cache.
740
	 */
741
	public function clearCache() {
742
		$this->cache->clear();
743
	}
744
745
	/**
746
	 * Function which returns MAPI Message Store Object. It
747
	 * searches in the variable $action for a storeid.
748
	 *
749
	 * @param array $action the XML data retrieved from the client
750
	 *
751
	 * @return object MAPI Message Store Object, false if storeid is not found in the $action variable
752
	 */
753
	#[Override]
754
	public function getActionStore($action) {
755
		$store = false;
756
757
		if (isset($action["store_entryid"]) && !empty($action["store_entryid"])) {
758
			if (is_array($action["store_entryid"])) {
759
				$store = [];
760
				foreach ($action["store_entryid"] as $store_id) {
761
					array_push($store, $store_id);
762
				}
763
			}
764
			else {
765
				$store = $action["store_entryid"];
766
			}
767
		}
768
769
		return $store;
770
	}
771
}
772