FilesListModule::save()   C
last analyzed

Complexity

Conditions 15
Paths 56

Size

Total Lines 141
Code Lines 86

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 86
c 0
b 0
f 0
nc 56
nop 1
dl 0
loc 141
rs 5.0387

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
	 * @param mixed $id
63
	 * @param mixed $data
64
	 */
65
	public function __construct($id, $data) {
66
		parent::__construct($id, $data);
67
68
		// Initialize the account and backendstore
69
		$this->accountStore = new AccountStore();
70
		$this->backendStore = BackendStore::getInstance();
71
72
		// Setup the cache
73
		$config = new RedisConfig();
74
		$config->setHost(PLUGIN_FILES_REDIS_HOST);
75
		$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

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

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