Passed
Push — master ( 37cafd...a8b392 )
by
unknown
07:18
created
plugins/kendox/php/kendox-client/class.kendox-token-generator.php 1 patch
Indentation   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -42,11 +42,11 @@
 block discarded – undo
42 42
 	 * Issuer host name.
43 43
 	 */
44 44
 		private $Issuer, /**
45
-	 * Full filename of PFX-File (Certificate).
46
-	 */
45
+		 * Full filename of PFX-File (Certificate).
46
+		 */
47 47
 		private $PfxFile, /**
48
-	 * Password for PFX-File (Certificate).
49
-	 */
48
+		 * Password for PFX-File (Certificate).
49
+		 */
50 50
 		private $PfxPassword
51 51
 	) {
52 52
 		$this->loadCertificateFromPfx();
Please login to merge, or discard this patch.
server/includes/modules/class.appointmentlistmodule.php 1 patch
Indentation   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -393,7 +393,7 @@
 block discarded – undo
393 393
 		foreach ($calendaritems as $calendaritem) {
394 394
 			$item = null;
395 395
 			if (!isset($calendaritem[$this->properties["recurring"]]) ||
396
-			    !$calendaritem[$this->properties["recurring"]]) {
396
+				!$calendaritem[$this->properties["recurring"]]) {
397 397
 				$item = Conversion::mapMAPI2XML($this->properties, $calendaritem);
398 398
 				$this->addItems($store, $item, $openedMessages, $start, $end, $items);
399 399
 
Please login to merge, or discard this patch.
defaults.php 1 patch
Indentation   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -590,7 +590,7 @@
 block discarded – undo
590 590
 }
591 591
 
592 592
 if (!defined("PREFETCH_EMAIL_STRATEGY")) {
593
-    define("PREFETCH_EMAIL_STRATEGY", 'VIEWPORT');
593
+	define("PREFETCH_EMAIL_STRATEGY", 'VIEWPORT');
594 594
 }
595 595
 
596 596
 /*
Please login to merge, or discard this patch.
plugins/filesbackendDefault/php/class.plugindefault.php 1 patch
Indentation   +51 added lines, -51 removed lines patch added patch discarded remove patch
@@ -7,58 +7,58 @@
 block discarded – undo
7 7
  */
8 8
 class PluginFilesbackendDefault extends Plugin
9 9
 {
10
-    /**
11
-     * Constructor.
12
-     */
13
-    public function __construct()
14
-    {
15
-    }
10
+	/**
11
+	 * Constructor.
12
+	 */
13
+	public function __construct()
14
+	{
15
+	}
16 16
 
17
-    /**
18
-     * Called to initialize the plugin and register for hooks.
19
-     */
20
-    public function init()
21
-    {
22
-        $this->registerHook('server.core.settings.init.before');
23
-    }
17
+	/**
18
+	 * Called to initialize the plugin and register for hooks.
19
+	 */
20
+	public function init()
21
+	{
22
+		$this->registerHook('server.core.settings.init.before');
23
+	}
24 24
 
25
-    /**
26
-     * Function is executed when a hook is triggered by the PluginManager.
27
-     *
28
-     * @param string $eventID Identifier of the hook
29
-     * @param array  $data    Reference to the data of the triggered hook
30
-     */
31
-    public function execute($eventID, &$data)
32
-    {
33
-        switch ($eventID) {
34
-            case 'server.core.settings.init.before':
35
-                $this->onBeforeSettingsInit($data);
36
-                break;
37
-        }
38
-    }
25
+	/**
26
+	 * Function is executed when a hook is triggered by the PluginManager.
27
+	 *
28
+	 * @param string $eventID Identifier of the hook
29
+	 * @param array  $data    Reference to the data of the triggered hook
30
+	 */
31
+	public function execute($eventID, &$data)
32
+	{
33
+		switch ($eventID) {
34
+			case 'server.core.settings.init.before':
35
+				$this->onBeforeSettingsInit($data);
36
+				break;
37
+		}
38
+	}
39 39
 
40
-    /**
41
-     * Called when the core Settings class is initialized and ready to accept sysadmin default
42
-     * settings. Registers the sysadmin defaults for the files backend plugin.
43
-     *
44
-     * @param array $data Reference to the data of the triggered hook
45
-     */
46
-    public function onBeforeSettingsInit(&$data)
47
-    {
48
-        $data['settingsObj']->addSysAdminDefaults([
49
-            'zarafa' => [
50
-                'v1' => [
51
-                    'plugins' => [
52
-                        'filesbackendDefault' => [
53
-                            'enable' => true,
54
-                        ],
55
-                        // Compatibility alias for pre-existing configurations
56
-                        'filesbackendOwncloud' => [
57
-                            'enable' => true,
58
-                        ],
59
-                    ],
60
-                ],
61
-            ],
62
-        ]);
63
-    }
40
+	/**
41
+	 * Called when the core Settings class is initialized and ready to accept sysadmin default
42
+	 * settings. Registers the sysadmin defaults for the files backend plugin.
43
+	 *
44
+	 * @param array $data Reference to the data of the triggered hook
45
+	 */
46
+	public function onBeforeSettingsInit(&$data)
47
+	{
48
+		$data['settingsObj']->addSysAdminDefaults([
49
+			'zarafa' => [
50
+				'v1' => [
51
+					'plugins' => [
52
+						'filesbackendDefault' => [
53
+							'enable' => true,
54
+						],
55
+						// Compatibility alias for pre-existing configurations
56
+						'filesbackendOwncloud' => [
57
+							'enable' => true,
58
+						],
59
+					],
60
+				],
61
+			],
62
+		]);
63
+	}
64 64
 }
Please login to merge, or discard this patch.
plugins/filesbackendDefault/php/lib/ocsapi/class.ocsclient.php 1 patch
Indentation   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -84,14 +84,14 @@
 block discarded – undo
84 84
 	 * @var string Server base URL
85 85
 	 */
86 86
 		private $baseurl, /**
87
-	 * @var string Username
88
-	 */
87
+		 * @var string Username
88
+		 */
89 89
 		private $user, /**
90
-	 * @var string Password
91
-	 */
90
+		 * @var string Password
91
+		 */
92 92
 		private $pass, /**
93
-	 * @var bool Allow self signed certs
94
-	 */
93
+		 * @var bool Allow self signed certs
94
+		 */
95 95
 		private $allowSelfSignedCerts = false
96 96
 	) {
97 97
 		// check if curl is available
Please login to merge, or discard this patch.
plugins/filesbackendSeafile/php/class.backend.php 1 patch
Indentation   +1034 added lines, -1034 removed lines patch added patch discarded remove patch
@@ -37,1038 +37,1038 @@
 block discarded – undo
37 37
  */
38 38
 final class Backend extends AbstractBackend implements iFeatureVersionInfo
39 39
 {
40
-    public const LOG_CONTEXT = "SeafileBackend"; // Context for the Logger
41
-
42
-    /**
43
-     * @const string gettext domain
44
-     */
45
-    private const GT_DOMAIN = 'plugin_filesbackendSeafile';
46
-
47
-    /**
48
-     * Seafile "usage" number ("bytes") to Grommunio usage display number ("bytes") multiplier.
49
-     *
50
-     * 1 megabyte in bytes within Seafile represents 1 mebibyte in bytes for Grommunio
51
-     *
52
-     * (Seafile Usage "Bytes" U) / 1000 / 1000 * 1024 * 1024 (1.048576)
53
-     */
54
-    private const QUOTA_MULTIPLIER_SEAFILE_TO_GROMMUNIO = 1.048576;
55
-
56
-    /**
57
-     * Error codes
58
-     *
59
-     * @see parseErrorCodeToMessage for description
60
-     * @see Backend::backendException() for Seafile API handling
61
-     */
62
-    private const SFA_ERR_UNAUTHORIZED = 401;
63
-    private const SFA_ERR_FORBIDDEN = 403;
64
-    private const SFA_ERR_NOTFOUND = 404;
65
-    private const SFA_ERR_NOTALLOWED = 405;
66
-    private const SFA_ERR_TIMEOUT = 408;
67
-    private const SFA_ERR_LOCKED = 423;
68
-    private const SFA_ERR_FAILED_DEPENDENCY = 423;
69
-    private const SFA_ERR_INTERNAL = 500;
70
-    private const SFA_ERR_UNREACHABLE = 800;
71
-    private const SFA_ERR_TMP = 801;
72
-    private const SFA_ERR_FEATURES = 802;
73
-    private const SFA_ERR_NO_CURL = 803;
74
-    private const SFA_ERR_UNIMPLEMENTED = 804;
75
-
76
-    /**
77
-     * @var ?SeafileApi The Seafile API client.
78
-     */
79
-    private ?SeafileApi $seafapi = null;
80
-
81
-    /**
82
-     * Configuration data for the Ext JS metaform.
83
-     */
84
-    private array $metaConfig = [];
85
-
86
-    /**
87
-     * Debug flag that mirrors `PLUGIN_FILESBROWSER_LOGLEVEL`.
88
-     */
89
-    private bool $debug = false;
90
-
91
-    private Config $config;
92
-
93
-    private ?SsoBackend $sso = null;
94
-
95
-    /**
96
-     * Backend name used in translations.
97
-     */
98
-    private string $backendTransName = '';
99
-
100
-    /**
101
-     * Seafile backend constructor
102
-     */
103
-    public function __construct()
104
-    {
105
-        // initialization
106
-        $this->debug = PLUGIN_FILESBROWSER_LOGLEVEL === 'DEBUG';
107
-
108
-        $this->config = new Config();
109
-
110
-        $this->init_form();
111
-
112
-        // set backend description
113
-        $this->backendDescription = dgettext(self::GT_DOMAIN, "With this backend, you can connect to any Seafile server.");
114
-
115
-        // set backend display name
116
-        $this->backendDisplayName = "Seafile";
117
-
118
-        // set backend version
119
-        $this->backendVersion = "2.0.68";
120
-
121
-        // set backend name used in translations
122
-        $this->backendTransName = dgettext(self::GT_DOMAIN, 'Files ' . $this->backendDisplayName . ' Backend: ');
123
-    }
124
-
125
-    ////////////////////////////////////////////////////////////////////////////
126
-    /// seafapi backend methods                                              ///
127
-    ////////////////////////////////////////////////////////////////////////////
128
-
129
-    /**
130
-     * Opens the connection to the Seafile server.
131
-     *
132
-     * @throws BackendException if connection is not successful
133
-     * @return boolean true if action succeeded
134
-     */
135
-    public function open()
136
-    {
137
-        $url = $this->config->getApiUrl();
138
-
139
-        try {
140
-            $this->sso->open();
141
-        } catch (Throwable $throwable) {
142
-            $this->backendException($throwable);
143
-        }
144
-
145
-        try {
146
-            $this->seafapi = new SeafileApi($url, $this->config->user, $this->config->pass);
147
-        } catch (Throwable $throwable) {
148
-            $this->backendException($throwable);
149
-        }
150
-
151
-        return true;
152
-    }
153
-
154
-    /**
155
-     * This function will read a list of files and folders from the server.
156
-     *
157
-     * @param string $dir to get listing from
158
-     * @param bool $hidefirst skip the first entry (we ignore this for the Seafile backend)
159
-     * @throws BackendException
160
-     *
161
-     * @return array
162
-     */
163
-    public function ls($dir, $hidefirst = true)
164
-    {
165
-        $timer = new Timer();
166
-        $this->log("[LS] '$dir'");
167
-
168
-        if ('' === trim($dir, '/')) {
169
-            try {
170
-                $listing = $this->seafapi->listLibraries();
171
-            } catch (Throwable $throwable) {
172
-                $this->backendException($throwable);
173
-            }
174
-            goto result;
175
-        }
176
-
177
-        $lsDir = $this->splitGrommunioPath($dir);
178
-        if (null === $lsDir->lib) {
179
-            // the library does not exist, the listing is short.
180
-            $listing = [];
181
-            goto result;
182
-        }
183
-
184
-        try {
185
-            $listing = $this->seafapi->listItemsInDirectory($lsDir->lib, $lsDir->path ?? '');
186
-        } catch (Throwable $throwable) {
187
-            $this->backendException($throwable);
188
-        }
189
-
190
-        result:
191
-
192
-        $result = [];
193
-        $baseDir = rtrim($dir, '/') . '/';
194
-        foreach ($listing as $node) {
195
-            if (!isset($this->seafapi::TYPES[$node->type])) {
196
-                $this->backendException(
197
-                    new \UnexpectedValueException(sprintf('Unhandled Seafile node-type "%s" (for "%s")', $node->type, $node->name))
198
-                );
199
-            }
200
-            $isDir = isset($this->seafapi::TYPES_DIR_LIKE[$node->type]);
201
-            $name = rtrim($baseDir . $node->name, '/') . '/';
202
-            $isDir || $name = rtrim($name, '/');
203
-            $result[$name] = [
204
-                'resourcetype' => $isDir ? 'collection' : 'file',
205
-                'getcontentlength' => $isDir ? null : $node->size,
206
-                'getlastmodified' => date('r', $node->mtime),
207
-                'getcontenttype' => null,
208
-                'quota-used-bytes' => null,
209
-                'quota-available-bytes' => null,
210
-            ];
211
-        }
212
-
213
-        $this->log("[LS] done in $timer seconds.");
214
-        return $result;
215
-    }
216
-
217
-    /**
218
-     * Creates a new directory on the server.
219
-     *
220
-     * @param string $dir
221
-     * @throws BackendException
222
-     * @return bool
223
-     */
224
-    public function mkcol($dir)
225
-    {
226
-        $timer = new Timer();
227
-        $this->log("[MKCOL] '$dir'");
228
-
229
-        if ($this->isLibrary($dir)) {
230
-            // create library
231
-            try {
232
-                $result = $this->seafapi->createLibrary($dir);
233
-                unset($result);
234
-            } catch (Throwable $throwable) {
235
-                $this->backendException($throwable);
236
-            }
237
-            $success = true;
238
-        } else {
239
-            // create directory within library
240
-            $lib = $this->seafapi->getLibraryFromPath($dir)->id;
241
-            [, $path] = explode('/', trim($dir, '/'), 2);
242
-            try {
243
-                $result = $this->seafapi->createNewDirectory($lib, $path);
244
-            } catch (Throwable $throwable) {
245
-                $this->backendException($throwable);
246
-            }
247
-            $success = 'success' === $result;
248
-        }
249
-
250
-        $this->log("[MKCOL] done in $timer seconds.");
251
-        return $success;
252
-    }
253
-
254
-    /**
255
-     * Deletes a files or folder from the backend.
256
-     *
257
-     * @param string $path
258
-     * @throws BackendException
259
-     * @return bool
260
-     */
261
-    public function delete($path)
262
-    {
263
-        $timer = new Timer();
264
-        $this->log("[DELETE] '$path'");
265
-
266
-        if ($this->isLibrary($path)) {
267
-            // delete library
268
-            try {
269
-                $this->seafapi->deleteLibraryByName($path);
270
-                $result = 'success';
271
-            } catch (Throwable $throwable) {
272
-                $this->backendException($throwable);
273
-            }
274
-        } else {
275
-            // delete file or directory within library
276
-            $deletePath = $this->splitGrommunioPath($path);
277
-            try {
278
-                $result = $this->seafapi->deleteFile($deletePath->lib, $deletePath->path);
279
-            } catch (Throwable $throwable) {
280
-                $this->backendException($throwable);
281
-            }
282
-        }
283
-
284
-        $this->log("[DELETE] done in $timer seconds.");
285
-        return 'success' === $result;
286
-    }
287
-
288
-    /**
289
-     * Move a file or collection on the backend server (serverside).
290
-     *
291
-     * @param string $src_path Source path
292
-     * @param string $dst_path Destination path
293
-     * @param bool $overwrite Overwrite file if exists in $dest_path
294
-     * @throws BackendException
295
-     * @return bool
296
-     */
297
-    public function move($src_path, $dst_path, $overwrite = false)
298
-    {
299
-        $timer = new Timer();
300
-        $this->log("[MOVE] '$src_path' -> '$dst_path'");
301
-
302
-        // check if the move operation would move src into itself - error condition
303
-        if (0 === strpos($dst_path, $src_path . '/')) {
304
-            $this->backendError(self::SFA_ERR_FORBIDDEN, 'Moving failed');
305
-        }
306
-
307
-        // move library/file/directory is one of in the following order:
308
-        // 1/5: rename library
309
-        // 2/5: noop - source and destination are the same
310
-        // 3/5: rename file/directory
311
-        // 4/5: move file/directory
312
-        // 5/5: every other operation (e.g. move library into another library) is not implemented
313
-
314
-        $src = $this->splitGrommunioPath($src_path);
315
-        $dst = $this->splitGrommunioPath($dst_path);
316
-
317
-        // 1/5: rename library
318
-        if ($src->path === null && $dst->path === null) {
319
-            if ($dst->lib !== null) {
320
-                // rename to an existing library name (not allowed as not supported)
321
-                $this->backendError(self::SFA_ERR_NOTALLOWED, 'Moving failed');
322
-            }
323
-            try {
324
-                $this->seafapi->renameLibrary($src->libName, $dst->libName);
325
-                $result = true;
326
-            } catch (Throwable $throwable) {
327
-                $this->backendException($throwable);
328
-            }
329
-            goto done;
330
-        }
331
-
332
-        $isIntraLibTransaction = $src->libName === $dst->libName;
333
-
334
-        // 2/5: noop - src and dst are the same
335
-        if ($isIntraLibTransaction && $src->path === $dst->path) {
336
-            // source and destination are the same path, nothing to do
337
-            $result = 'success';
338
-            goto done;
339
-        }
340
-
341
-        $dirNames = array_map('dirname', [$src->path, $dst->path]);
342
-        $pathsHaveSameDirNames = $dirNames[0] === $dirNames[1];
343
-
344
-        // 3/5: rename file/directory
345
-        if ($isIntraLibTransaction && $pathsHaveSameDirNames) {
346
-            try {
347
-                $result = $this->seafapi->renameFile($src->lib, $src->path, basename($dst->path));
348
-            } catch (Throwable $throwable) {
349
-                $this->backendException($throwable);
350
-            }
351
-            goto done;
352
-        }
353
-
354
-        // 4/5: move file/directory
355
-        if (isset($src->path, $dst->lib)) {
356
-            try {
357
-                $result = $this->seafapi->moveFile($src->lib, $src->path, $dst->lib, $dirNames[1]);
358
-            } catch (Throwable $throwable) {
359
-                $this->backendException($throwable);
360
-            }
361
-        }
362
-
363
-        done:
364
-
365
-        // 5/5: every other operation (move library into another library, not implemented)
366
-        if (!isset($result)) {
367
-            $this->backendError(self::SFA_ERR_UNIMPLEMENTED, 'Not implemented.');
368
-        }
369
-
370
-        $this->log("[MOVE] done in $timer seconds.");
371
-        return 'success' === $result;
372
-    }
373
-
374
-    /**
375
-     * Download a remote file to a buffer variable.
376
-     *
377
-     * @param string $path The source path on the server
378
-     * @param mixed $buffer Buffer for the received data
379
-     *
380
-     * @throws BackendException if request is not successful
381
-     *
382
-     * @return boolean true if action succeeded
383
-     */
384
-    public function get($path, &$buffer)
385
-    {
386
-        $timer = new Timer();
387
-        $this->log("[GET] '$path'");
388
-
389
-        $src = $this->splitGrommunioPath($path);
390
-
391
-        try {
392
-            $result = $this->seafapi->downloadFileAsBuffer($src->lib, $src->path);
393
-        } catch (Throwable $throwable) {
394
-            $this->backendException($throwable);
395
-        }
396
-
397
-        $success = false !== $result;
398
-
399
-        if ($success) {
400
-            $buffer = $result;
401
-        }
402
-
403
-        $this->log("[GET] done in $timer seconds.");
404
-        return $success;
405
-    }
406
-
407
-    /**
408
-     * Download a remote file to a local file.
409
-     *
410
-     * @param string $srcpath Source path on server
411
-     * @param string $localpath Destination path on local filesystem
412
-     *
413
-     * @throws BackendException if request is not successful
414
-     *
415
-     * @return boolean true if action succeeded
416
-     */
417
-    public function get_file($srcpath, $localpath)
418
-    {
419
-        $timer = new Timer();
420
-        $this->log("[GET_FILE] '$srcpath' -> '$localpath'");
421
-
422
-        $src = $this->splitGrommunioPath($srcpath);
423
-
424
-        try {
425
-            $result = $this->seafapi->downloadFileToFile($src->lib, $src->path, $localpath);
426
-        } catch (Throwable $throwable) {
427
-            $this->backendException($throwable);
428
-        }
429
-
430
-        $this->log("[GET_FILE] done in $timer seconds.");
431
-        return $result;
432
-    }
433
-
434
-    /**
435
-     * Puts a file into a collection.
436
-     *
437
-     * @param string $path Destination path
438
-     *
439
-     * @string mixed $data Any kind of data
440
-     * @throws BackendException if request is not successful
441
-     *
442
-     * @return boolean true if action succeeded
443
-     */
444
-    public function put($path, $data)
445
-    {
446
-        $timer = new Timer();
447
-        $this->log(sprintf("[PUT] start: path: %s (%d)", $path, strlen($data)));
448
-
449
-        $target = $this->splitGrommunioPath($path);
450
-
451
-        try {
452
-            /** @noinspection PhpUnusedLocalVariableInspection */
453
-            $result = $this->seafapi->uploadBuffer($target->lib, $target->path, $data);
454
-        } catch (Throwable $throwable) {
455
-            $this->backendException($throwable);
456
-        }
457
-
458
-        $this->log("[PUT] done in $timer seconds.");
459
-        return true;
460
-    }
461
-
462
-    /**
463
-     * Upload a local file
464
-     *
465
-     * @param string $path Destination path on the server
466
-     * @param string $filename Local filename for the file that should be uploaded
467
-     *
468
-     * @throws BackendException if request is not successful
469
-     *
470
-     * @return boolean true if action succeeded
471
-     */
472
-    public function put_file($path, $filename)
473
-    {
474
-        $timer = new Timer();
475
-        $this->log(sprintf("[PUT_FILE] %s -> %s", $filename, $path));
476
-
477
-        // filename can be null if an attachment of draft-email that has not been saved
478
-        if (empty($filename)) {
479
-            return false;
480
-        }
481
-
482
-        $target = $this->splitGrommunioPath($path);
483
-
484
-        // put file into users default library if no library given
485
-        if ($target->path === null && $target->libName !== null) {
486
-            try {
487
-                $defaultLibrary = $this->seafapi->getDefaultLibrary();
488
-            } catch (Throwable $throwable) {
489
-                $this->backendException($throwable);
490
-            }
491
-            if (isset($defaultLibrary->repo_id, $defaultLibrary->exists) && true === $defaultLibrary->exists) {
492
-                $target->path = $target->libName;
493
-                $target->libName = null;
494
-                $target->lib = $defaultLibrary->repo_id;
495
-            }
496
-        }
497
-
498
-        try {
499
-            /** @noinspection PhpUnusedLocalVariableInspection */
500
-            $result = $this->seafapi->uploadFile($target->lib, $target->path, $filename);
501
-        } catch (Throwable $throwable) {
502
-            $this->backendException($throwable);
503
-        }
504
-
505
-        $this->log("[PUT_FILE] done in $timer seconds.");
506
-        return true;
507
-    }
508
-
509
-    ////////////////////////////////////////////////////////////////////////////
510
-    /// non-seafapi backend implementation                                   ///
511
-    ////////////////////////////////////////////////////////////////////////////
512
-
513
-    /**
514
-     * Initialize backend from $backend_config array
515
-     *
516
-     * @param $backend_config
517
-     * @return void
518
-     */
519
-    public function init_backend($backend_config)
520
-    {
521
-        $config = $backend_config;
522
-
523
-        if ($backend_config["use_zarafa_credentials"]) {
524
-            // For backward compatibility we will check if the Encryption store exists. If not,
525
-            // we will fall back to the old way of retrieving the password from the session.
526
-            if (class_exists('EncryptionStore')) {
527
-                // Get the username and password from the Encryption store
528
-                $encryptionStore = EncryptionStore::getInstance();
529
-                if ($encryptionStore instanceof EncryptionStore) {
530
-                    $config['user'] = $encryptionStore->get('username');
531
-                    $config['password'] = $encryptionStore->get('password');
532
-                }
533
-            } else {
534
-                $config['user'] = ConfigUtil::loadSmtpAddress();
535
-                $password = $_SESSION['password'];
536
-                if (function_exists('openssl_decrypt')) {
537
-                    /** @noinspection PhpUndefinedConstantInspection */
538
-                    $config['password'] = openssl_decrypt($password, "des-ede3-cbc", PASSWORD_KEY, 0, PASSWORD_IV);
539
-                }
540
-            }
541
-        }
542
-
543
-        $this->config->importConfigArray($config);
544
-
545
-        SsoBackend::bind($this->sso)->initBackend($this->config);
546
-
547
-        Logger::debug(self::LOG_CONTEXT, __FUNCTION__ . ' done.');
548
-    }
549
-
550
-    /**
551
-     * @return false|string
552
-     * @noinspection PhpMultipleClassDeclarationsInspection Grommunio has a \JsonException shim
553
-     */
554
-    public function getFormConfig()
555
-    {
556
-        try {
557
-            $json = json_encode($this->metaConfig, JSON_THROW_ON_ERROR);
558
-        } catch (JsonException $e) {
559
-            $this->log(sprintf('[%s]: %s', get_class($e), $e->getMessage()));
560
-            $json = false;
561
-        }
562
-
563
-        return $json;
564
-    }
565
-
566
-    public function getFormConfigWithData()
567
-    {
568
-        return $this->getFormConfig();
569
-    }
570
-
571
-    /**
572
-     * set debug on (1) or off (0).
573
-     * produces a lot of debug messages in webservers error log if set to on (1).
574
-     *
575
-     * @param boolean $debug enable or disable debugging
576
-     *
577
-     * @return void
578
-     */
579
-    public function set_debug($debug)
580
-    {
581
-        $this->debug = (bool)$debug;
582
-    }
583
-
584
-    ////////////////////////////////////////////////////////////////////////////
585
-    /// not_used_implemented()                                               ///
586
-    ////////////////////////////////////////////////////////////////////////////
587
-
588
-    /**
589
-     * Duplicates a folder on the backend server.
590
-     *
591
-     * @param string $src_path
592
-     * @param string $dst_path
593
-     * @param bool $overwrite
594
-     * @throws BackendException
595
-     * @return bool
596
-     * @noinspection PhpReturnDocTypeMismatchInspection Upstream Interface Issue
597
-     */
598
-    public function copy_coll($src_path, $dst_path, $overwrite = false)
599
-    {
600
-        $this->backendError(self::SFA_ERR_UNIMPLEMENTED, 'Not implemented');
601
-    }
602
-
603
-    /**
604
-     * Duplicates a file on the backend server.
605
-     *
606
-     * @param string $src_path
607
-     * @param string $dst_path
608
-     * @param bool $overwrite
609
-     * @throws BackendException
610
-     * @return bool
611
-     * @noinspection PhpReturnDocTypeMismatchInspection Upstream Interface Issue
612
-     */
613
-    public function copy_file($src_path, $dst_path, $overwrite = false)
614
-    {
615
-        $this->backendError(self::SFA_ERR_UNIMPLEMENTED, 'Not implemented');
616
-    }
617
-
618
-    /**
619
-     * Checks if the given $path exists on the remote server.
620
-     *
621
-     * @param string $path
622
-     * @throws BackendException
623
-     * @return bool
624
-     * @noinspection PhpReturnDocTypeMismatchInspection Upstream Interface Issue
625
-     */
626
-    public function exists($path)
627
-    {
628
-        $this->backendError(self::SFA_ERR_UNIMPLEMENTED, 'Not implemented');
629
-    }
630
-
631
-    /**
632
-     * Gets path information from Seafile server.
633
-     *
634
-     * @param string $path
635
-     * @throws BackendException if request is not successful
636
-     * @return array directory info
637
-     */
638
-    public function gpi($path)
639
-    {
640
-        $this->log("[GPI] '$path'");
641
-        $list = $this->ls(dirname($path), false); // get contents of the parent dir
642
-
643
-        if (isset($list[$path])) {
644
-            return $list[$path];
645
-        }
646
-
647
-        $this->log('[GPI] wrong response from ls');
648
-        $this->backendError(self::SFA_ERR_FAILED_DEPENDENCY, 'Connection failed');
649
-    }
650
-
651
-    /**
652
-     * Checks if the given $path is a folder.
653
-     *
654
-     * @param string $path
655
-     * @throws BackendException
656
-     * @return bool
657
-     * @noinspection PhpReturnDocTypeMismatchInspection Upstream Interface Issue
658
-     */
659
-    public function is_dir($path)
660
-    {
661
-        $this->backendError(self::SFA_ERR_UNIMPLEMENTED, 'Not implemented');
662
-    }
663
-
664
-    /**
665
-     * Checks if the given $path is a file.
666
-     *
667
-     * @param string $path
668
-     * @throws BackendException
669
-     * @return bool
670
-     * @noinspection PhpReturnDocTypeMismatchInspection Upstream Interface Issue
671
-     */
672
-    public function is_file($path)
673
-    {
674
-        $this->backendError(self::SFA_ERR_UNIMPLEMENTED, 'Not implemented');
675
-    }
676
-
677
-    /////////////////////////////////////////////////////////////
678
-    // @see iFeatureVersionInfo implementation                 //
679
-    /////////////////////////////////////////////////////////////
680
-
681
-    /**
682
-     * Return the version string of the server backend.
683
-     *
684
-     * @throws BackendException
685
-     * @return String
686
-     */
687
-    public function getServerVersion()
688
-    {
689
-        try {
690
-            return $this->seafapi->getServerVersion();
691
-        } catch (Throwable $throwable) {
692
-            $this->backendException($throwable);
693
-        }
694
-    }
695
-
696
-    /////////////////////////////////////////////////////////////
697
-    // @see iFeatureQuota implementation                       //
698
-    /////////////////////////////////////////////////////////////
699
-
700
-    /**
701
-     * @param string $dir
702
-     * @return float
703
-     * @noinspection PhpMissingParamTypeInspection
704
-     * @noinspection PhpUnusedParameterInspection
705
-     */
706
-    public function getQuotaBytesUsed($dir)
707
-    {
708
-        $return = $this->seafapi->checkAccountInfo();
709
-
710
-        return ($return->usage ?? 0) * self::QUOTA_MULTIPLIER_SEAFILE_TO_GROMMUNIO;
711
-    }
712
-
713
-    /**
714
-     * @param string $dir
715
-     * @return float|int
716
-     * @noinspection PhpUnusedParameterInspection
717
-     * @noinspection PhpMissingParamTypeInspection
718
-     */
719
-    public function getQuotaBytesAvailable($dir)
720
-    {
721
-        $return = $this->seafapi->checkAccountInfo();
722
-        $avail = $return->total - $return->usage;
723
-        if (-2 === (int)$return->total) {
724
-            return -1;
725
-        }
726
-
727
-        return $avail * self::QUOTA_MULTIPLIER_SEAFILE_TO_GROMMUNIO;
728
-    }
729
-
730
-    /////////////////////////////////////////////////////////////
731
-    // @internal private helper methods                        //
732
-    /////////////////////////////////////////////////////////////
733
-
734
-    /**
735
-     * Initialise form fields
736
-     */
737
-    private function init_form()
738
-    {
739
-        $this->metaConfig = [
740
-            "success" => true,
741
-            "metaData" => [
742
-                "fields" => [
743
-                    [
744
-                        "name" => "server_address",
745
-                        "fieldLabel" => dgettext(self::GT_DOMAIN, 'Server address'),
746
-                        "editor" => [
747
-                            "allowBlank" => false,
748
-                        ],
749
-                    ],
750
-                    [
751
-                        "name" => "server_port",
752
-                        "fieldLabel" => dgettext(self::GT_DOMAIN, 'Server port'),
753
-                        "editor" => [
754
-                            "ref" => "../../portField",
755
-                            "allowBlank" => false,
756
-                        ],
757
-                    ],
758
-                    [
759
-                        "name" => "server_ssl",
760
-                        "fieldLabel" => dgettext(self::GT_DOMAIN, 'Use SSL'),
761
-                        "editor" => [
762
-                            "xtype" => "checkbox",
763
-                            "listeners" => [
764
-                                "check" => "Zarafa.plugins.files.data.Actions.onCheckSSL" // this javascript function will be called!
765
-                            ],
766
-                        ],
767
-                    ],
768
-                    [
769
-                        "name" => "user",
770
-                        "fieldLabel" => dgettext(self::GT_DOMAIN, 'Username'),
771
-                        "editor" => [
772
-                            "ref" => "../../usernameField",
773
-                        ],
774
-                    ],
775
-                    [
776
-                        "name" => "password",
777
-                        "fieldLabel" => dgettext(self::GT_DOMAIN, 'Password'),
778
-                        "editor" => [
779
-                            "ref" => "../../passwordField",
780
-                            "inputType" => "password",
781
-                        ],
782
-                    ],
783
-                    [
784
-                        "name" => "use_zarafa_credentials",
785
-                        "fieldLabel" => dgettext(self::GT_DOMAIN, 'Use grommunio credentials'),
786
-                        "editor" => [
787
-                            "xtype" => "checkbox",
788
-                            "listeners" => [
789
-                                "check" => "Zarafa.plugins.files.data.Actions.onCheckCredentials" // this javascript function will be called!
790
-                            ],
791
-                        ],
792
-                    ],
793
-                ],
794
-                "formConfig" => [
795
-                    "labelAlign" => "left",
796
-                    "columnCount" => 1,
797
-                    "labelWidth" => 80,
798
-                    "defaults" => [
799
-                        "width" => 292,
800
-                    ],
801
-                ],
802
-            ],
803
-
804
-            // here we can specify the default values.
805
-            "data" => [
806
-                "server_address" => "seafile.example.com",
807
-                "server_port" => "443",
808
-                "server_ssl" => "1",
809
-                "server_path" => "",
810
-                "use_zarafa_credentials" => "0",
811
-                "user" => "",
812
-                "password" => "",
813
-            ],
814
-        ];
815
-    }
816
-
817
-    /**
818
-     * split grommunio path into library and library path
819
-     *
820
-     * obtains the seafile library ID (if available, otherwise NULL)
821
-     *
822
-     * return protocol: object{
823
-     *   lib: ?string     # library ID e.g. "ccc60923-8cdf-4cc8-8f71-df86aba3a085"
824
-     *   path: ?string    # path inside library, always prefixed with "/" if set
825
-     *   libName: ?string # name of the library
826
-     * }
827
-     *
828
-     * @param string $grommunioPath
829
-     * @throws Exception
830
-     * @return object
831
-     */
832
-    private function splitGrommunioPath(string $grommunioPath): object
833
-    {
834
-        static $libraries;
835
-        $libraries = $libraries ?? array_column($this->seafapi->listLibraries(), null, 'name');
836
-
837
-        [, $libName, $path] = explode('/', $grommunioPath, 3) + [null, null, null];
838
-        if (null !== $path) {
839
-            $path = "/$path";
840
-        }
841
-        $lib = $libraries[$libName]->id ?? null;
842
-        return (object)['lib' => $lib, 'path' => $path, 'libName' => $libName];
843
-    }
844
-
845
-    /**
846
-     * test if a grommunio path is a library only
847
-     *
848
-     * @param string $grommunioPath
849
-     * @return bool
850
-     */
851
-    private function isLibrary(string $grommunioPath): bool
852
-    {
853
-        return 0 === substr_count(trim($grommunioPath, '/'), '/');
854
-    }
855
-
856
-    /**
857
-     * Turn a Backend error code into a Backend exception
858
-     *
859
-     * @param int $errorCode one of the Backend::SFA_ERR_* codes, e.g. {@see Backend::SFA_ERR_INTERNAL}
860
-     * @param ?string $title msg-id from the plugin_files domain, e.g. 'PHP-CURL not installed'
861
-     * @throws BackendException
862
-     * @return no-return
863
-     */
864
-    private function backendError(int $errorCode, string $title = null)
865
-    {
866
-        $message = $this->parseErrorCodeToMessage($errorCode);
867
-        $title = $this->backendTransName;
868
-        $this->backendErrorThrow($title, $message, $errorCode);
869
-    }
870
-
871
-    /**
872
-     * Throw a BackendException w/ title, message and code
873
-     *
874
-     * @param string $title
875
-     * @param string $message
876
-     * @param int $code
877
-     * @throws BackendException
878
-     * @return no-return
879
-     */
880
-    private function backendErrorThrow(string $title, string $message, int $code = 0)
881
-    {
882
-        /** {@see \Files\Backend\Exception} */
883
-        $exception = new BackendException($message, $code);
884
-        $exception->setTitle($title);
885
-        throw $exception;
886
-    }
887
-
888
-    /**
889
-     * Turn a throwable/exception with the Seafile API into a Backend exception
890
-     *
891
-     * @param Throwable $t
892
-     * @throws BackendException
893
-     * @return no-return
894
-     */
895
-    private function backendException(Throwable $t)
896
-    {
897
-        // if it is already a backend exception, throw it.
898
-        if ($t instanceof BackendException) {
899
-            throw $t;
900
-        }
901
-
902
-        [$callSite, $inFunc] = debug_backtrace();
903
-        $logLabel = "$inFunc[function]:$callSite[line]";
904
-
905
-        $class = get_class($t);
906
-        $message = $t->getMessage();
907
-        $this->log(sprintf('%s: [%s] #%s: %s', $logLabel, $class, $t->getCode(), $message));
908
-
909
-        // All SeafileApi exceptions are handled by this
910
-        if ($t instanceof Exception) {
911
-            $this->backendExceptionSeafapi($t);
912
-        }
913
-
914
-        $this->backendErrorThrow('Error', "[SEAFILE $logLabel] $class: $message", 500);
915
-    }
916
-
917
-    /**
918
-     * Turn an Exception into a BackendException
919
-     *
920
-     * Enriches message information for grommunio with API error messages
921
-     * if a Seafile ConnectionException.
922
-     *
923
-     * helper for {@see Backend::backendException()}
924
-     *
925
-     * @param Exception $exception
926
-     * @throws BackendException
927
-     * @return void
928
-     */
929
-    private function backendExceptionSeafapi(Exception $exception)
930
-    {
931
-        $code = $exception->getCode();
932
-        $message = $exception->getMessage();
933
-
934
-        $apiErrorMessagesHtml = null;
935
-        if ($exception instanceof Exception\ConnectionException) {
936
-            $messages = $exception->tryApiErrorMessages();
937
-            null === $messages || $apiErrorMessagesHtml = implode(
938
-                    "<br/>\n",
939
-                    array_map(static function (string $subject) {
940
-                        return htmlspecialchars($subject, ENT_QUOTES | ENT_HTML5);
941
-                    }, $messages)
942
-                ) . "<br/>\n";
943
-        }
944
-
945
-        if (null !== $apiErrorMessagesHtml) {
946
-            $message .= " - $apiErrorMessagesHtml";
947
-        }
948
-
949
-        $this->backendErrorThrow($this->backendDisplayName . ' Error', $message, $code);
950
-    }
951
-
952
-    /**
953
-     * a simple php error_log wrapper.
954
-     *
955
-     * @param string $err_string error message
956
-     *
957
-     * @return void
958
-     */
959
-    private function log(string $err_string)
960
-    {
961
-        if ($this->debug) {
962
-            Logger::debug(self::LOG_CONTEXT, $err_string);
963
-            $this->debugLog($err_string, 2);
964
-        }
965
-    }
966
-
967
-    /**
968
-     * This function will return a user-friendly error string.
969
-     *
970
-     * Error codes were migrated from WebDav backend.
971
-     *
972
-     * @param int $error_code An error code
973
-     *
974
-     * @return string user friendly error message
975
-     */
976
-    private function parseErrorCodeToMessage(int $error_code)
977
-    {
978
-        $error = $error_code;
979
-
980
-        switch ($error) {
981
-            case CURLE_BAD_PASSWORD_ENTERED:
982
-            case self::SFA_ERR_UNAUTHORIZED:
983
-                $msg = dgettext(self::GT_DOMAIN, 'Unauthorized. Wrong username or password.');
984
-                break;
985
-            case CURLE_SSL_CONNECT_ERROR:
986
-            case CURLE_COULDNT_RESOLVE_HOST:
987
-            case CURLE_COULDNT_CONNECT:
988
-            case CURLE_OPERATION_TIMEOUTED:
989
-            case self::SFA_ERR_UNREACHABLE:
990
-                $msg = dgettext(self::GT_DOMAIN, 'Seafile is not reachable. Correct backend address entered?');
991
-                break;
992
-            case self::SFA_ERR_FORBIDDEN:
993
-                $msg = dgettext(self::GT_DOMAIN, 'You don\'t have enough permissions for this operation.');
994
-                break;
995
-            case self::SFA_ERR_NOTFOUND:
996
-                $msg = dgettext(self::GT_DOMAIN, 'File is not available any more.');
997
-                break;
998
-            case self::SFA_ERR_TIMEOUT:
999
-                $msg = dgettext(self::GT_DOMAIN, 'Connection to server timed out. Retry later.');
1000
-                break;
1001
-            case self::SFA_ERR_LOCKED:
1002
-                $msg = dgettext(self::GT_DOMAIN, 'This file is locked by another user.');
1003
-                break;
1004
-            case self::SFA_ERR_FAILED_DEPENDENCY:
1005
-                $msg = dgettext(self::GT_DOMAIN, 'The request failed due to failure of a previous request.');
1006
-                break;
1007
-            case self::SFA_ERR_INTERNAL:
1008
-                $msg = dgettext(self::GT_DOMAIN, 'Seafile-server encountered a problem.');
1009
-                break;
1010
-            case self::SFA_ERR_TMP:
1011
-                $msg = dgettext(self::GT_DOMAIN, 'Could not write to temporary directory. Contact the server administrator.');
1012
-                break;
1013
-            case self::SFA_ERR_FEATURES:
1014
-                $msg = dgettext(self::GT_DOMAIN, 'Could not retrieve list of server features. Contact the server administrator.');
1015
-                break;
1016
-            case self::SFA_ERR_NO_CURL:
1017
-                $msg = dgettext(self::GT_DOMAIN, 'PHP-Curl is not available. Contact your system administrator.');
1018
-                break;
1019
-            case self::SFA_ERR_UNIMPLEMENTED:
1020
-                $msg = dgettext(self::GT_DOMAIN, 'This function is not yet implemented.');
1021
-                break;
1022
-            default:
1023
-                $msg = dgettext(self::GT_DOMAIN, 'Unknown error');
1024
-        }
1025
-
1026
-        return $msg;
1027
-    }
1028
-
1029
-    /////////////////////////////////////////////////////////////
1030
-    // @debug development helper method                        //
1031
-    /////////////////////////////////////////////////////////////
1032
-
1033
-    /**
1034
-     * Log debug message while developing the plugin in dedicated DEBUG.log file
1035
-     *
1036
-     * TODO(tk): remove debugLog, we shall not use it in production.
1037
-     *
1038
-     * @param mixed $message
1039
-     * @param int $backSteps [optional] offset of call point in stacktrace
1040
-     * @return void
1041
-     * @see \Files\Backend\Seafile\Backend::log()
1042
-     */
1043
-    public function debugLog($message, int $backSteps = 0): void
1044
-    {
1045
-        $baseDir = dirname(__DIR__);
1046
-        $debugLogFile = $baseDir . '/DEBUG.log';
1047
-        $backtrace = debug_backtrace();
1048
-        $callPoint = $backtrace[$backSteps];
1049
-        $path = $callPoint['file'];
1050
-        $shortPath = $path;
1051
-        if (0 === strpos($path, $baseDir)) {
1052
-            $shortPath = substr($path, strlen($baseDir));
1053
-        }
1054
-        // TODO(tk): track if the parent function is log() or not, not only the number of back-steps (or check all call points)
1055
-        $callInfoExtra = '';
1056
-        if (1 !== $backSteps) { // this is not a log() call with debug switched on
1057
-            $callInfoExtra = " ($backSteps) " . $backtrace[$backSteps + 1]['type'] . $backtrace[$backSteps + 1]['function'] . '()';
1058
-        }
1059
-        $callInfo = sprintf(' [ %s:%s ]%s', $shortPath, $callPoint['line'], $callInfoExtra);
1060
-
1061
-        if (!is_string($message)) {
1062
-            /** @noinspection JsonEncodingApiUsageInspection */
1063
-            $type = gettype($message);
1064
-            if ('object' === $type && is_callable([$message, '__debugInfo'])) {
1065
-                $message = $message->__debugInfo();
1066
-            }
1067
-            $message = $type . ': ' . json_encode($message, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
1068
-        }
1069
-
1070
-        $message = substr(sprintf('%.3f', $_SERVER['REQUEST_TIME_FLOAT']), -7) . " $message";
1071
-
1072
-        error_log(str_pad($message, 48) . $callInfo . "\n", 3, $debugLogFile);
1073
-    }
40
+	public const LOG_CONTEXT = "SeafileBackend"; // Context for the Logger
41
+
42
+	/**
43
+	 * @const string gettext domain
44
+	 */
45
+	private const GT_DOMAIN = 'plugin_filesbackendSeafile';
46
+
47
+	/**
48
+	 * Seafile "usage" number ("bytes") to Grommunio usage display number ("bytes") multiplier.
49
+	 *
50
+	 * 1 megabyte in bytes within Seafile represents 1 mebibyte in bytes for Grommunio
51
+	 *
52
+	 * (Seafile Usage "Bytes" U) / 1000 / 1000 * 1024 * 1024 (1.048576)
53
+	 */
54
+	private const QUOTA_MULTIPLIER_SEAFILE_TO_GROMMUNIO = 1.048576;
55
+
56
+	/**
57
+	 * Error codes
58
+	 *
59
+	 * @see parseErrorCodeToMessage for description
60
+	 * @see Backend::backendException() for Seafile API handling
61
+	 */
62
+	private const SFA_ERR_UNAUTHORIZED = 401;
63
+	private const SFA_ERR_FORBIDDEN = 403;
64
+	private const SFA_ERR_NOTFOUND = 404;
65
+	private const SFA_ERR_NOTALLOWED = 405;
66
+	private const SFA_ERR_TIMEOUT = 408;
67
+	private const SFA_ERR_LOCKED = 423;
68
+	private const SFA_ERR_FAILED_DEPENDENCY = 423;
69
+	private const SFA_ERR_INTERNAL = 500;
70
+	private const SFA_ERR_UNREACHABLE = 800;
71
+	private const SFA_ERR_TMP = 801;
72
+	private const SFA_ERR_FEATURES = 802;
73
+	private const SFA_ERR_NO_CURL = 803;
74
+	private const SFA_ERR_UNIMPLEMENTED = 804;
75
+
76
+	/**
77
+	 * @var ?SeafileApi The Seafile API client.
78
+	 */
79
+	private ?SeafileApi $seafapi = null;
80
+
81
+	/**
82
+	 * Configuration data for the Ext JS metaform.
83
+	 */
84
+	private array $metaConfig = [];
85
+
86
+	/**
87
+	 * Debug flag that mirrors `PLUGIN_FILESBROWSER_LOGLEVEL`.
88
+	 */
89
+	private bool $debug = false;
90
+
91
+	private Config $config;
92
+
93
+	private ?SsoBackend $sso = null;
94
+
95
+	/**
96
+	 * Backend name used in translations.
97
+	 */
98
+	private string $backendTransName = '';
99
+
100
+	/**
101
+	 * Seafile backend constructor
102
+	 */
103
+	public function __construct()
104
+	{
105
+		// initialization
106
+		$this->debug = PLUGIN_FILESBROWSER_LOGLEVEL === 'DEBUG';
107
+
108
+		$this->config = new Config();
109
+
110
+		$this->init_form();
111
+
112
+		// set backend description
113
+		$this->backendDescription = dgettext(self::GT_DOMAIN, "With this backend, you can connect to any Seafile server.");
114
+
115
+		// set backend display name
116
+		$this->backendDisplayName = "Seafile";
117
+
118
+		// set backend version
119
+		$this->backendVersion = "2.0.68";
120
+
121
+		// set backend name used in translations
122
+		$this->backendTransName = dgettext(self::GT_DOMAIN, 'Files ' . $this->backendDisplayName . ' Backend: ');
123
+	}
124
+
125
+	////////////////////////////////////////////////////////////////////////////
126
+	/// seafapi backend methods                                              ///
127
+	////////////////////////////////////////////////////////////////////////////
128
+
129
+	/**
130
+	 * Opens the connection to the Seafile server.
131
+	 *
132
+	 * @throws BackendException if connection is not successful
133
+	 * @return boolean true if action succeeded
134
+	 */
135
+	public function open()
136
+	{
137
+		$url = $this->config->getApiUrl();
138
+
139
+		try {
140
+			$this->sso->open();
141
+		} catch (Throwable $throwable) {
142
+			$this->backendException($throwable);
143
+		}
144
+
145
+		try {
146
+			$this->seafapi = new SeafileApi($url, $this->config->user, $this->config->pass);
147
+		} catch (Throwable $throwable) {
148
+			$this->backendException($throwable);
149
+		}
150
+
151
+		return true;
152
+	}
153
+
154
+	/**
155
+	 * This function will read a list of files and folders from the server.
156
+	 *
157
+	 * @param string $dir to get listing from
158
+	 * @param bool $hidefirst skip the first entry (we ignore this for the Seafile backend)
159
+	 * @throws BackendException
160
+	 *
161
+	 * @return array
162
+	 */
163
+	public function ls($dir, $hidefirst = true)
164
+	{
165
+		$timer = new Timer();
166
+		$this->log("[LS] '$dir'");
167
+
168
+		if ('' === trim($dir, '/')) {
169
+			try {
170
+				$listing = $this->seafapi->listLibraries();
171
+			} catch (Throwable $throwable) {
172
+				$this->backendException($throwable);
173
+			}
174
+			goto result;
175
+		}
176
+
177
+		$lsDir = $this->splitGrommunioPath($dir);
178
+		if (null === $lsDir->lib) {
179
+			// the library does not exist, the listing is short.
180
+			$listing = [];
181
+			goto result;
182
+		}
183
+
184
+		try {
185
+			$listing = $this->seafapi->listItemsInDirectory($lsDir->lib, $lsDir->path ?? '');
186
+		} catch (Throwable $throwable) {
187
+			$this->backendException($throwable);
188
+		}
189
+
190
+		result:
191
+
192
+		$result = [];
193
+		$baseDir = rtrim($dir, '/') . '/';
194
+		foreach ($listing as $node) {
195
+			if (!isset($this->seafapi::TYPES[$node->type])) {
196
+				$this->backendException(
197
+					new \UnexpectedValueException(sprintf('Unhandled Seafile node-type "%s" (for "%s")', $node->type, $node->name))
198
+				);
199
+			}
200
+			$isDir = isset($this->seafapi::TYPES_DIR_LIKE[$node->type]);
201
+			$name = rtrim($baseDir . $node->name, '/') . '/';
202
+			$isDir || $name = rtrim($name, '/');
203
+			$result[$name] = [
204
+				'resourcetype' => $isDir ? 'collection' : 'file',
205
+				'getcontentlength' => $isDir ? null : $node->size,
206
+				'getlastmodified' => date('r', $node->mtime),
207
+				'getcontenttype' => null,
208
+				'quota-used-bytes' => null,
209
+				'quota-available-bytes' => null,
210
+			];
211
+		}
212
+
213
+		$this->log("[LS] done in $timer seconds.");
214
+		return $result;
215
+	}
216
+
217
+	/**
218
+	 * Creates a new directory on the server.
219
+	 *
220
+	 * @param string $dir
221
+	 * @throws BackendException
222
+	 * @return bool
223
+	 */
224
+	public function mkcol($dir)
225
+	{
226
+		$timer = new Timer();
227
+		$this->log("[MKCOL] '$dir'");
228
+
229
+		if ($this->isLibrary($dir)) {
230
+			// create library
231
+			try {
232
+				$result = $this->seafapi->createLibrary($dir);
233
+				unset($result);
234
+			} catch (Throwable $throwable) {
235
+				$this->backendException($throwable);
236
+			}
237
+			$success = true;
238
+		} else {
239
+			// create directory within library
240
+			$lib = $this->seafapi->getLibraryFromPath($dir)->id;
241
+			[, $path] = explode('/', trim($dir, '/'), 2);
242
+			try {
243
+				$result = $this->seafapi->createNewDirectory($lib, $path);
244
+			} catch (Throwable $throwable) {
245
+				$this->backendException($throwable);
246
+			}
247
+			$success = 'success' === $result;
248
+		}
249
+
250
+		$this->log("[MKCOL] done in $timer seconds.");
251
+		return $success;
252
+	}
253
+
254
+	/**
255
+	 * Deletes a files or folder from the backend.
256
+	 *
257
+	 * @param string $path
258
+	 * @throws BackendException
259
+	 * @return bool
260
+	 */
261
+	public function delete($path)
262
+	{
263
+		$timer = new Timer();
264
+		$this->log("[DELETE] '$path'");
265
+
266
+		if ($this->isLibrary($path)) {
267
+			// delete library
268
+			try {
269
+				$this->seafapi->deleteLibraryByName($path);
270
+				$result = 'success';
271
+			} catch (Throwable $throwable) {
272
+				$this->backendException($throwable);
273
+			}
274
+		} else {
275
+			// delete file or directory within library
276
+			$deletePath = $this->splitGrommunioPath($path);
277
+			try {
278
+				$result = $this->seafapi->deleteFile($deletePath->lib, $deletePath->path);
279
+			} catch (Throwable $throwable) {
280
+				$this->backendException($throwable);
281
+			}
282
+		}
283
+
284
+		$this->log("[DELETE] done in $timer seconds.");
285
+		return 'success' === $result;
286
+	}
287
+
288
+	/**
289
+	 * Move a file or collection on the backend server (serverside).
290
+	 *
291
+	 * @param string $src_path Source path
292
+	 * @param string $dst_path Destination path
293
+	 * @param bool $overwrite Overwrite file if exists in $dest_path
294
+	 * @throws BackendException
295
+	 * @return bool
296
+	 */
297
+	public function move($src_path, $dst_path, $overwrite = false)
298
+	{
299
+		$timer = new Timer();
300
+		$this->log("[MOVE] '$src_path' -> '$dst_path'");
301
+
302
+		// check if the move operation would move src into itself - error condition
303
+		if (0 === strpos($dst_path, $src_path . '/')) {
304
+			$this->backendError(self::SFA_ERR_FORBIDDEN, 'Moving failed');
305
+		}
306
+
307
+		// move library/file/directory is one of in the following order:
308
+		// 1/5: rename library
309
+		// 2/5: noop - source and destination are the same
310
+		// 3/5: rename file/directory
311
+		// 4/5: move file/directory
312
+		// 5/5: every other operation (e.g. move library into another library) is not implemented
313
+
314
+		$src = $this->splitGrommunioPath($src_path);
315
+		$dst = $this->splitGrommunioPath($dst_path);
316
+
317
+		// 1/5: rename library
318
+		if ($src->path === null && $dst->path === null) {
319
+			if ($dst->lib !== null) {
320
+				// rename to an existing library name (not allowed as not supported)
321
+				$this->backendError(self::SFA_ERR_NOTALLOWED, 'Moving failed');
322
+			}
323
+			try {
324
+				$this->seafapi->renameLibrary($src->libName, $dst->libName);
325
+				$result = true;
326
+			} catch (Throwable $throwable) {
327
+				$this->backendException($throwable);
328
+			}
329
+			goto done;
330
+		}
331
+
332
+		$isIntraLibTransaction = $src->libName === $dst->libName;
333
+
334
+		// 2/5: noop - src and dst are the same
335
+		if ($isIntraLibTransaction && $src->path === $dst->path) {
336
+			// source and destination are the same path, nothing to do
337
+			$result = 'success';
338
+			goto done;
339
+		}
340
+
341
+		$dirNames = array_map('dirname', [$src->path, $dst->path]);
342
+		$pathsHaveSameDirNames = $dirNames[0] === $dirNames[1];
343
+
344
+		// 3/5: rename file/directory
345
+		if ($isIntraLibTransaction && $pathsHaveSameDirNames) {
346
+			try {
347
+				$result = $this->seafapi->renameFile($src->lib, $src->path, basename($dst->path));
348
+			} catch (Throwable $throwable) {
349
+				$this->backendException($throwable);
350
+			}
351
+			goto done;
352
+		}
353
+
354
+		// 4/5: move file/directory
355
+		if (isset($src->path, $dst->lib)) {
356
+			try {
357
+				$result = $this->seafapi->moveFile($src->lib, $src->path, $dst->lib, $dirNames[1]);
358
+			} catch (Throwable $throwable) {
359
+				$this->backendException($throwable);
360
+			}
361
+		}
362
+
363
+		done:
364
+
365
+		// 5/5: every other operation (move library into another library, not implemented)
366
+		if (!isset($result)) {
367
+			$this->backendError(self::SFA_ERR_UNIMPLEMENTED, 'Not implemented.');
368
+		}
369
+
370
+		$this->log("[MOVE] done in $timer seconds.");
371
+		return 'success' === $result;
372
+	}
373
+
374
+	/**
375
+	 * Download a remote file to a buffer variable.
376
+	 *
377
+	 * @param string $path The source path on the server
378
+	 * @param mixed $buffer Buffer for the received data
379
+	 *
380
+	 * @throws BackendException if request is not successful
381
+	 *
382
+	 * @return boolean true if action succeeded
383
+	 */
384
+	public function get($path, &$buffer)
385
+	{
386
+		$timer = new Timer();
387
+		$this->log("[GET] '$path'");
388
+
389
+		$src = $this->splitGrommunioPath($path);
390
+
391
+		try {
392
+			$result = $this->seafapi->downloadFileAsBuffer($src->lib, $src->path);
393
+		} catch (Throwable $throwable) {
394
+			$this->backendException($throwable);
395
+		}
396
+
397
+		$success = false !== $result;
398
+
399
+		if ($success) {
400
+			$buffer = $result;
401
+		}
402
+
403
+		$this->log("[GET] done in $timer seconds.");
404
+		return $success;
405
+	}
406
+
407
+	/**
408
+	 * Download a remote file to a local file.
409
+	 *
410
+	 * @param string $srcpath Source path on server
411
+	 * @param string $localpath Destination path on local filesystem
412
+	 *
413
+	 * @throws BackendException if request is not successful
414
+	 *
415
+	 * @return boolean true if action succeeded
416
+	 */
417
+	public function get_file($srcpath, $localpath)
418
+	{
419
+		$timer = new Timer();
420
+		$this->log("[GET_FILE] '$srcpath' -> '$localpath'");
421
+
422
+		$src = $this->splitGrommunioPath($srcpath);
423
+
424
+		try {
425
+			$result = $this->seafapi->downloadFileToFile($src->lib, $src->path, $localpath);
426
+		} catch (Throwable $throwable) {
427
+			$this->backendException($throwable);
428
+		}
429
+
430
+		$this->log("[GET_FILE] done in $timer seconds.");
431
+		return $result;
432
+	}
433
+
434
+	/**
435
+	 * Puts a file into a collection.
436
+	 *
437
+	 * @param string $path Destination path
438
+	 *
439
+	 * @string mixed $data Any kind of data
440
+	 * @throws BackendException if request is not successful
441
+	 *
442
+	 * @return boolean true if action succeeded
443
+	 */
444
+	public function put($path, $data)
445
+	{
446
+		$timer = new Timer();
447
+		$this->log(sprintf("[PUT] start: path: %s (%d)", $path, strlen($data)));
448
+
449
+		$target = $this->splitGrommunioPath($path);
450
+
451
+		try {
452
+			/** @noinspection PhpUnusedLocalVariableInspection */
453
+			$result = $this->seafapi->uploadBuffer($target->lib, $target->path, $data);
454
+		} catch (Throwable $throwable) {
455
+			$this->backendException($throwable);
456
+		}
457
+
458
+		$this->log("[PUT] done in $timer seconds.");
459
+		return true;
460
+	}
461
+
462
+	/**
463
+	 * Upload a local file
464
+	 *
465
+	 * @param string $path Destination path on the server
466
+	 * @param string $filename Local filename for the file that should be uploaded
467
+	 *
468
+	 * @throws BackendException if request is not successful
469
+	 *
470
+	 * @return boolean true if action succeeded
471
+	 */
472
+	public function put_file($path, $filename)
473
+	{
474
+		$timer = new Timer();
475
+		$this->log(sprintf("[PUT_FILE] %s -> %s", $filename, $path));
476
+
477
+		// filename can be null if an attachment of draft-email that has not been saved
478
+		if (empty($filename)) {
479
+			return false;
480
+		}
481
+
482
+		$target = $this->splitGrommunioPath($path);
483
+
484
+		// put file into users default library if no library given
485
+		if ($target->path === null && $target->libName !== null) {
486
+			try {
487
+				$defaultLibrary = $this->seafapi->getDefaultLibrary();
488
+			} catch (Throwable $throwable) {
489
+				$this->backendException($throwable);
490
+			}
491
+			if (isset($defaultLibrary->repo_id, $defaultLibrary->exists) && true === $defaultLibrary->exists) {
492
+				$target->path = $target->libName;
493
+				$target->libName = null;
494
+				$target->lib = $defaultLibrary->repo_id;
495
+			}
496
+		}
497
+
498
+		try {
499
+			/** @noinspection PhpUnusedLocalVariableInspection */
500
+			$result = $this->seafapi->uploadFile($target->lib, $target->path, $filename);
501
+		} catch (Throwable $throwable) {
502
+			$this->backendException($throwable);
503
+		}
504
+
505
+		$this->log("[PUT_FILE] done in $timer seconds.");
506
+		return true;
507
+	}
508
+
509
+	////////////////////////////////////////////////////////////////////////////
510
+	/// non-seafapi backend implementation                                   ///
511
+	////////////////////////////////////////////////////////////////////////////
512
+
513
+	/**
514
+	 * Initialize backend from $backend_config array
515
+	 *
516
+	 * @param $backend_config
517
+	 * @return void
518
+	 */
519
+	public function init_backend($backend_config)
520
+	{
521
+		$config = $backend_config;
522
+
523
+		if ($backend_config["use_zarafa_credentials"]) {
524
+			// For backward compatibility we will check if the Encryption store exists. If not,
525
+			// we will fall back to the old way of retrieving the password from the session.
526
+			if (class_exists('EncryptionStore')) {
527
+				// Get the username and password from the Encryption store
528
+				$encryptionStore = EncryptionStore::getInstance();
529
+				if ($encryptionStore instanceof EncryptionStore) {
530
+					$config['user'] = $encryptionStore->get('username');
531
+					$config['password'] = $encryptionStore->get('password');
532
+				}
533
+			} else {
534
+				$config['user'] = ConfigUtil::loadSmtpAddress();
535
+				$password = $_SESSION['password'];
536
+				if (function_exists('openssl_decrypt')) {
537
+					/** @noinspection PhpUndefinedConstantInspection */
538
+					$config['password'] = openssl_decrypt($password, "des-ede3-cbc", PASSWORD_KEY, 0, PASSWORD_IV);
539
+				}
540
+			}
541
+		}
542
+
543
+		$this->config->importConfigArray($config);
544
+
545
+		SsoBackend::bind($this->sso)->initBackend($this->config);
546
+
547
+		Logger::debug(self::LOG_CONTEXT, __FUNCTION__ . ' done.');
548
+	}
549
+
550
+	/**
551
+	 * @return false|string
552
+	 * @noinspection PhpMultipleClassDeclarationsInspection Grommunio has a \JsonException shim
553
+	 */
554
+	public function getFormConfig()
555
+	{
556
+		try {
557
+			$json = json_encode($this->metaConfig, JSON_THROW_ON_ERROR);
558
+		} catch (JsonException $e) {
559
+			$this->log(sprintf('[%s]: %s', get_class($e), $e->getMessage()));
560
+			$json = false;
561
+		}
562
+
563
+		return $json;
564
+	}
565
+
566
+	public function getFormConfigWithData()
567
+	{
568
+		return $this->getFormConfig();
569
+	}
570
+
571
+	/**
572
+	 * set debug on (1) or off (0).
573
+	 * produces a lot of debug messages in webservers error log if set to on (1).
574
+	 *
575
+	 * @param boolean $debug enable or disable debugging
576
+	 *
577
+	 * @return void
578
+	 */
579
+	public function set_debug($debug)
580
+	{
581
+		$this->debug = (bool)$debug;
582
+	}
583
+
584
+	////////////////////////////////////////////////////////////////////////////
585
+	/// not_used_implemented()                                               ///
586
+	////////////////////////////////////////////////////////////////////////////
587
+
588
+	/**
589
+	 * Duplicates a folder on the backend server.
590
+	 *
591
+	 * @param string $src_path
592
+	 * @param string $dst_path
593
+	 * @param bool $overwrite
594
+	 * @throws BackendException
595
+	 * @return bool
596
+	 * @noinspection PhpReturnDocTypeMismatchInspection Upstream Interface Issue
597
+	 */
598
+	public function copy_coll($src_path, $dst_path, $overwrite = false)
599
+	{
600
+		$this->backendError(self::SFA_ERR_UNIMPLEMENTED, 'Not implemented');
601
+	}
602
+
603
+	/**
604
+	 * Duplicates a file on the backend server.
605
+	 *
606
+	 * @param string $src_path
607
+	 * @param string $dst_path
608
+	 * @param bool $overwrite
609
+	 * @throws BackendException
610
+	 * @return bool
611
+	 * @noinspection PhpReturnDocTypeMismatchInspection Upstream Interface Issue
612
+	 */
613
+	public function copy_file($src_path, $dst_path, $overwrite = false)
614
+	{
615
+		$this->backendError(self::SFA_ERR_UNIMPLEMENTED, 'Not implemented');
616
+	}
617
+
618
+	/**
619
+	 * Checks if the given $path exists on the remote server.
620
+	 *
621
+	 * @param string $path
622
+	 * @throws BackendException
623
+	 * @return bool
624
+	 * @noinspection PhpReturnDocTypeMismatchInspection Upstream Interface Issue
625
+	 */
626
+	public function exists($path)
627
+	{
628
+		$this->backendError(self::SFA_ERR_UNIMPLEMENTED, 'Not implemented');
629
+	}
630
+
631
+	/**
632
+	 * Gets path information from Seafile server.
633
+	 *
634
+	 * @param string $path
635
+	 * @throws BackendException if request is not successful
636
+	 * @return array directory info
637
+	 */
638
+	public function gpi($path)
639
+	{
640
+		$this->log("[GPI] '$path'");
641
+		$list = $this->ls(dirname($path), false); // get contents of the parent dir
642
+
643
+		if (isset($list[$path])) {
644
+			return $list[$path];
645
+		}
646
+
647
+		$this->log('[GPI] wrong response from ls');
648
+		$this->backendError(self::SFA_ERR_FAILED_DEPENDENCY, 'Connection failed');
649
+	}
650
+
651
+	/**
652
+	 * Checks if the given $path is a folder.
653
+	 *
654
+	 * @param string $path
655
+	 * @throws BackendException
656
+	 * @return bool
657
+	 * @noinspection PhpReturnDocTypeMismatchInspection Upstream Interface Issue
658
+	 */
659
+	public function is_dir($path)
660
+	{
661
+		$this->backendError(self::SFA_ERR_UNIMPLEMENTED, 'Not implemented');
662
+	}
663
+
664
+	/**
665
+	 * Checks if the given $path is a file.
666
+	 *
667
+	 * @param string $path
668
+	 * @throws BackendException
669
+	 * @return bool
670
+	 * @noinspection PhpReturnDocTypeMismatchInspection Upstream Interface Issue
671
+	 */
672
+	public function is_file($path)
673
+	{
674
+		$this->backendError(self::SFA_ERR_UNIMPLEMENTED, 'Not implemented');
675
+	}
676
+
677
+	/////////////////////////////////////////////////////////////
678
+	// @see iFeatureVersionInfo implementation                 //
679
+	/////////////////////////////////////////////////////////////
680
+
681
+	/**
682
+	 * Return the version string of the server backend.
683
+	 *
684
+	 * @throws BackendException
685
+	 * @return String
686
+	 */
687
+	public function getServerVersion()
688
+	{
689
+		try {
690
+			return $this->seafapi->getServerVersion();
691
+		} catch (Throwable $throwable) {
692
+			$this->backendException($throwable);
693
+		}
694
+	}
695
+
696
+	/////////////////////////////////////////////////////////////
697
+	// @see iFeatureQuota implementation                       //
698
+	/////////////////////////////////////////////////////////////
699
+
700
+	/**
701
+	 * @param string $dir
702
+	 * @return float
703
+	 * @noinspection PhpMissingParamTypeInspection
704
+	 * @noinspection PhpUnusedParameterInspection
705
+	 */
706
+	public function getQuotaBytesUsed($dir)
707
+	{
708
+		$return = $this->seafapi->checkAccountInfo();
709
+
710
+		return ($return->usage ?? 0) * self::QUOTA_MULTIPLIER_SEAFILE_TO_GROMMUNIO;
711
+	}
712
+
713
+	/**
714
+	 * @param string $dir
715
+	 * @return float|int
716
+	 * @noinspection PhpUnusedParameterInspection
717
+	 * @noinspection PhpMissingParamTypeInspection
718
+	 */
719
+	public function getQuotaBytesAvailable($dir)
720
+	{
721
+		$return = $this->seafapi->checkAccountInfo();
722
+		$avail = $return->total - $return->usage;
723
+		if (-2 === (int)$return->total) {
724
+			return -1;
725
+		}
726
+
727
+		return $avail * self::QUOTA_MULTIPLIER_SEAFILE_TO_GROMMUNIO;
728
+	}
729
+
730
+	/////////////////////////////////////////////////////////////
731
+	// @internal private helper methods                        //
732
+	/////////////////////////////////////////////////////////////
733
+
734
+	/**
735
+	 * Initialise form fields
736
+	 */
737
+	private function init_form()
738
+	{
739
+		$this->metaConfig = [
740
+			"success" => true,
741
+			"metaData" => [
742
+				"fields" => [
743
+					[
744
+						"name" => "server_address",
745
+						"fieldLabel" => dgettext(self::GT_DOMAIN, 'Server address'),
746
+						"editor" => [
747
+							"allowBlank" => false,
748
+						],
749
+					],
750
+					[
751
+						"name" => "server_port",
752
+						"fieldLabel" => dgettext(self::GT_DOMAIN, 'Server port'),
753
+						"editor" => [
754
+							"ref" => "../../portField",
755
+							"allowBlank" => false,
756
+						],
757
+					],
758
+					[
759
+						"name" => "server_ssl",
760
+						"fieldLabel" => dgettext(self::GT_DOMAIN, 'Use SSL'),
761
+						"editor" => [
762
+							"xtype" => "checkbox",
763
+							"listeners" => [
764
+								"check" => "Zarafa.plugins.files.data.Actions.onCheckSSL" // this javascript function will be called!
765
+							],
766
+						],
767
+					],
768
+					[
769
+						"name" => "user",
770
+						"fieldLabel" => dgettext(self::GT_DOMAIN, 'Username'),
771
+						"editor" => [
772
+							"ref" => "../../usernameField",
773
+						],
774
+					],
775
+					[
776
+						"name" => "password",
777
+						"fieldLabel" => dgettext(self::GT_DOMAIN, 'Password'),
778
+						"editor" => [
779
+							"ref" => "../../passwordField",
780
+							"inputType" => "password",
781
+						],
782
+					],
783
+					[
784
+						"name" => "use_zarafa_credentials",
785
+						"fieldLabel" => dgettext(self::GT_DOMAIN, 'Use grommunio credentials'),
786
+						"editor" => [
787
+							"xtype" => "checkbox",
788
+							"listeners" => [
789
+								"check" => "Zarafa.plugins.files.data.Actions.onCheckCredentials" // this javascript function will be called!
790
+							],
791
+						],
792
+					],
793
+				],
794
+				"formConfig" => [
795
+					"labelAlign" => "left",
796
+					"columnCount" => 1,
797
+					"labelWidth" => 80,
798
+					"defaults" => [
799
+						"width" => 292,
800
+					],
801
+				],
802
+			],
803
+
804
+			// here we can specify the default values.
805
+			"data" => [
806
+				"server_address" => "seafile.example.com",
807
+				"server_port" => "443",
808
+				"server_ssl" => "1",
809
+				"server_path" => "",
810
+				"use_zarafa_credentials" => "0",
811
+				"user" => "",
812
+				"password" => "",
813
+			],
814
+		];
815
+	}
816
+
817
+	/**
818
+	 * split grommunio path into library and library path
819
+	 *
820
+	 * obtains the seafile library ID (if available, otherwise NULL)
821
+	 *
822
+	 * return protocol: object{
823
+	 *   lib: ?string     # library ID e.g. "ccc60923-8cdf-4cc8-8f71-df86aba3a085"
824
+	 *   path: ?string    # path inside library, always prefixed with "/" if set
825
+	 *   libName: ?string # name of the library
826
+	 * }
827
+	 *
828
+	 * @param string $grommunioPath
829
+	 * @throws Exception
830
+	 * @return object
831
+	 */
832
+	private function splitGrommunioPath(string $grommunioPath): object
833
+	{
834
+		static $libraries;
835
+		$libraries = $libraries ?? array_column($this->seafapi->listLibraries(), null, 'name');
836
+
837
+		[, $libName, $path] = explode('/', $grommunioPath, 3) + [null, null, null];
838
+		if (null !== $path) {
839
+			$path = "/$path";
840
+		}
841
+		$lib = $libraries[$libName]->id ?? null;
842
+		return (object)['lib' => $lib, 'path' => $path, 'libName' => $libName];
843
+	}
844
+
845
+	/**
846
+	 * test if a grommunio path is a library only
847
+	 *
848
+	 * @param string $grommunioPath
849
+	 * @return bool
850
+	 */
851
+	private function isLibrary(string $grommunioPath): bool
852
+	{
853
+		return 0 === substr_count(trim($grommunioPath, '/'), '/');
854
+	}
855
+
856
+	/**
857
+	 * Turn a Backend error code into a Backend exception
858
+	 *
859
+	 * @param int $errorCode one of the Backend::SFA_ERR_* codes, e.g. {@see Backend::SFA_ERR_INTERNAL}
860
+	 * @param ?string $title msg-id from the plugin_files domain, e.g. 'PHP-CURL not installed'
861
+	 * @throws BackendException
862
+	 * @return no-return
863
+	 */
864
+	private function backendError(int $errorCode, string $title = null)
865
+	{
866
+		$message = $this->parseErrorCodeToMessage($errorCode);
867
+		$title = $this->backendTransName;
868
+		$this->backendErrorThrow($title, $message, $errorCode);
869
+	}
870
+
871
+	/**
872
+	 * Throw a BackendException w/ title, message and code
873
+	 *
874
+	 * @param string $title
875
+	 * @param string $message
876
+	 * @param int $code
877
+	 * @throws BackendException
878
+	 * @return no-return
879
+	 */
880
+	private function backendErrorThrow(string $title, string $message, int $code = 0)
881
+	{
882
+		/** {@see \Files\Backend\Exception} */
883
+		$exception = new BackendException($message, $code);
884
+		$exception->setTitle($title);
885
+		throw $exception;
886
+	}
887
+
888
+	/**
889
+	 * Turn a throwable/exception with the Seafile API into a Backend exception
890
+	 *
891
+	 * @param Throwable $t
892
+	 * @throws BackendException
893
+	 * @return no-return
894
+	 */
895
+	private function backendException(Throwable $t)
896
+	{
897
+		// if it is already a backend exception, throw it.
898
+		if ($t instanceof BackendException) {
899
+			throw $t;
900
+		}
901
+
902
+		[$callSite, $inFunc] = debug_backtrace();
903
+		$logLabel = "$inFunc[function]:$callSite[line]";
904
+
905
+		$class = get_class($t);
906
+		$message = $t->getMessage();
907
+		$this->log(sprintf('%s: [%s] #%s: %s', $logLabel, $class, $t->getCode(), $message));
908
+
909
+		// All SeafileApi exceptions are handled by this
910
+		if ($t instanceof Exception) {
911
+			$this->backendExceptionSeafapi($t);
912
+		}
913
+
914
+		$this->backendErrorThrow('Error', "[SEAFILE $logLabel] $class: $message", 500);
915
+	}
916
+
917
+	/**
918
+	 * Turn an Exception into a BackendException
919
+	 *
920
+	 * Enriches message information for grommunio with API error messages
921
+	 * if a Seafile ConnectionException.
922
+	 *
923
+	 * helper for {@see Backend::backendException()}
924
+	 *
925
+	 * @param Exception $exception
926
+	 * @throws BackendException
927
+	 * @return void
928
+	 */
929
+	private function backendExceptionSeafapi(Exception $exception)
930
+	{
931
+		$code = $exception->getCode();
932
+		$message = $exception->getMessage();
933
+
934
+		$apiErrorMessagesHtml = null;
935
+		if ($exception instanceof Exception\ConnectionException) {
936
+			$messages = $exception->tryApiErrorMessages();
937
+			null === $messages || $apiErrorMessagesHtml = implode(
938
+					"<br/>\n",
939
+					array_map(static function (string $subject) {
940
+						return htmlspecialchars($subject, ENT_QUOTES | ENT_HTML5);
941
+					}, $messages)
942
+				) . "<br/>\n";
943
+		}
944
+
945
+		if (null !== $apiErrorMessagesHtml) {
946
+			$message .= " - $apiErrorMessagesHtml";
947
+		}
948
+
949
+		$this->backendErrorThrow($this->backendDisplayName . ' Error', $message, $code);
950
+	}
951
+
952
+	/**
953
+	 * a simple php error_log wrapper.
954
+	 *
955
+	 * @param string $err_string error message
956
+	 *
957
+	 * @return void
958
+	 */
959
+	private function log(string $err_string)
960
+	{
961
+		if ($this->debug) {
962
+			Logger::debug(self::LOG_CONTEXT, $err_string);
963
+			$this->debugLog($err_string, 2);
964
+		}
965
+	}
966
+
967
+	/**
968
+	 * This function will return a user-friendly error string.
969
+	 *
970
+	 * Error codes were migrated from WebDav backend.
971
+	 *
972
+	 * @param int $error_code An error code
973
+	 *
974
+	 * @return string user friendly error message
975
+	 */
976
+	private function parseErrorCodeToMessage(int $error_code)
977
+	{
978
+		$error = $error_code;
979
+
980
+		switch ($error) {
981
+			case CURLE_BAD_PASSWORD_ENTERED:
982
+			case self::SFA_ERR_UNAUTHORIZED:
983
+				$msg = dgettext(self::GT_DOMAIN, 'Unauthorized. Wrong username or password.');
984
+				break;
985
+			case CURLE_SSL_CONNECT_ERROR:
986
+			case CURLE_COULDNT_RESOLVE_HOST:
987
+			case CURLE_COULDNT_CONNECT:
988
+			case CURLE_OPERATION_TIMEOUTED:
989
+			case self::SFA_ERR_UNREACHABLE:
990
+				$msg = dgettext(self::GT_DOMAIN, 'Seafile is not reachable. Correct backend address entered?');
991
+				break;
992
+			case self::SFA_ERR_FORBIDDEN:
993
+				$msg = dgettext(self::GT_DOMAIN, 'You don\'t have enough permissions for this operation.');
994
+				break;
995
+			case self::SFA_ERR_NOTFOUND:
996
+				$msg = dgettext(self::GT_DOMAIN, 'File is not available any more.');
997
+				break;
998
+			case self::SFA_ERR_TIMEOUT:
999
+				$msg = dgettext(self::GT_DOMAIN, 'Connection to server timed out. Retry later.');
1000
+				break;
1001
+			case self::SFA_ERR_LOCKED:
1002
+				$msg = dgettext(self::GT_DOMAIN, 'This file is locked by another user.');
1003
+				break;
1004
+			case self::SFA_ERR_FAILED_DEPENDENCY:
1005
+				$msg = dgettext(self::GT_DOMAIN, 'The request failed due to failure of a previous request.');
1006
+				break;
1007
+			case self::SFA_ERR_INTERNAL:
1008
+				$msg = dgettext(self::GT_DOMAIN, 'Seafile-server encountered a problem.');
1009
+				break;
1010
+			case self::SFA_ERR_TMP:
1011
+				$msg = dgettext(self::GT_DOMAIN, 'Could not write to temporary directory. Contact the server administrator.');
1012
+				break;
1013
+			case self::SFA_ERR_FEATURES:
1014
+				$msg = dgettext(self::GT_DOMAIN, 'Could not retrieve list of server features. Contact the server administrator.');
1015
+				break;
1016
+			case self::SFA_ERR_NO_CURL:
1017
+				$msg = dgettext(self::GT_DOMAIN, 'PHP-Curl is not available. Contact your system administrator.');
1018
+				break;
1019
+			case self::SFA_ERR_UNIMPLEMENTED:
1020
+				$msg = dgettext(self::GT_DOMAIN, 'This function is not yet implemented.');
1021
+				break;
1022
+			default:
1023
+				$msg = dgettext(self::GT_DOMAIN, 'Unknown error');
1024
+		}
1025
+
1026
+		return $msg;
1027
+	}
1028
+
1029
+	/////////////////////////////////////////////////////////////
1030
+	// @debug development helper method                        //
1031
+	/////////////////////////////////////////////////////////////
1032
+
1033
+	/**
1034
+	 * Log debug message while developing the plugin in dedicated DEBUG.log file
1035
+	 *
1036
+	 * TODO(tk): remove debugLog, we shall not use it in production.
1037
+	 *
1038
+	 * @param mixed $message
1039
+	 * @param int $backSteps [optional] offset of call point in stacktrace
1040
+	 * @return void
1041
+	 * @see \Files\Backend\Seafile\Backend::log()
1042
+	 */
1043
+	public function debugLog($message, int $backSteps = 0): void
1044
+	{
1045
+		$baseDir = dirname(__DIR__);
1046
+		$debugLogFile = $baseDir . '/DEBUG.log';
1047
+		$backtrace = debug_backtrace();
1048
+		$callPoint = $backtrace[$backSteps];
1049
+		$path = $callPoint['file'];
1050
+		$shortPath = $path;
1051
+		if (0 === strpos($path, $baseDir)) {
1052
+			$shortPath = substr($path, strlen($baseDir));
1053
+		}
1054
+		// TODO(tk): track if the parent function is log() or not, not only the number of back-steps (or check all call points)
1055
+		$callInfoExtra = '';
1056
+		if (1 !== $backSteps) { // this is not a log() call with debug switched on
1057
+			$callInfoExtra = " ($backSteps) " . $backtrace[$backSteps + 1]['type'] . $backtrace[$backSteps + 1]['function'] . '()';
1058
+		}
1059
+		$callInfo = sprintf(' [ %s:%s ]%s', $shortPath, $callPoint['line'], $callInfoExtra);
1060
+
1061
+		if (!is_string($message)) {
1062
+			/** @noinspection JsonEncodingApiUsageInspection */
1063
+			$type = gettype($message);
1064
+			if ('object' === $type && is_callable([$message, '__debugInfo'])) {
1065
+				$message = $message->__debugInfo();
1066
+			}
1067
+			$message = $type . ': ' . json_encode($message, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
1068
+		}
1069
+
1070
+		$message = substr(sprintf('%.3f', $_SERVER['REQUEST_TIME_FLOAT']), -7) . " $message";
1071
+
1072
+		error_log(str_pad($message, 48) . $callInfo . "\n", 3, $debugLogFile);
1073
+	}
1074 1074
 }
Please login to merge, or discard this patch.
plugins/filesbackendSeafile/php/Model/Config.php 1 patch
Indentation   +154 added lines, -154 removed lines patch added patch discarded remove patch
@@ -19,158 +19,158 @@
 block discarded – undo
19 19
  */
20 20
 class Config implements ArrayAccess
21 21
 {
22
-    private const DEF = [
23
-        'server' => 'server_address',
24
-        'port' => 'server_port',
25
-        'ssl' => 'server_ssl',
26
-        'path' => 'server_path',
27
-        'user' => 'user',
28
-        'pass' => 'password',
29
-        'sso_auth_user_token' => 'sso_auth_user_token',
30
-    ];
31
-
32
-    private const SCHEMA = [
33
-        self::DEF['server'] => [
34
-            'filter' => FILTER_VALIDATE_DOMAIN,
35
-            'flags' => FILTER_FLAG_HOSTNAME,
36
-            'options' => ['default' => 'seafile.example.com'],
37
-        ],
38
-        self::DEF['port'] => [
39
-            'filter' => FILTER_VALIDATE_INT,
40
-            'options' => ['min_range' => 0, 'max_range' => 65535, 'default' => 443],
41
-        ],
42
-        self::DEF['ssl'] => [
43
-            'filter' => FILTER_VALIDATE_BOOLEAN,
44
-            'options' => ['default' => true],
45
-        ],
46
-        self::DEF['path'] => [
47
-            'filter' => FILTER_UNSAFE_RAW,
48
-            'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH,
49
-            'options' => ['default' => ''],
50
-        ],
51
-        self::DEF['user'] => [
52
-            'filter' => FILTER_UNSAFE_RAW,
53
-            'flags' => FILTER_FLAG_STRIP_LOW,
54
-            'options' => ['default' => ''],
55
-        ],
56
-        self::DEF['pass'] => [
57
-            'filter' => FILTER_DEFAULT,
58
-            'options' => ['default' => ''],
59
-        ],
60
-        self::DEF['sso_auth_user_token'] => [
61
-            'filter' => FILTER_DEFAULT,
62
-            'flags' => FILTER_FLAG_EMPTY_STRING_NULL,
63
-            'options' => ['default' => null],
64
-        ],
65
-    ];
66
-
67
-    private array $config = [];
68
-
69
-    public function __construct(array $config = [])
70
-    {
71
-        $this->importConfigArray($config);
72
-    }
73
-
74
-    /**
75
-     * Get the URL of the Seafile servers REST API
76
-     *
77
-     * @return string
78
-     */
79
-    public function getApiUrl(): string
80
-    {
81
-        $config = $this;
82
-
83
-        $ssl = (bool)$config->ssl;
84
-        $defaultPort = $ssl ? 443 : 80;
85
-        $port = max(0, min(65535, (int)$config->port)) ?: $defaultPort;
86
-        $host = rtrim($config->server, '/');
87
-        $path = ltrim($config->path, '/');
88
-
89
-        $url = sprintf('http%s://%s:%d/%s', $ssl ? 's' : '', $host, $port, $path);
90
-
91
-        return rtrim($url, '/');
92
-    }
93
-
94
-    /**
95
-     * read-only properties
96
-     *
97
-     * @param string $name
98
-     * @return mixed
99
-     *
100
-     * @noinspection MagicMethodsValidityInspection
101
-     * @noinspection RedundantSuppression
102
-     */
103
-    public function __get(string $name): mixed
104
-    {
105
-        if (!isset(self::DEF[$name])) {
106
-            throw new OutOfBoundsException("Not a property: \"$name\"");
107
-        }
108
-        return $this->config[self::DEF[$name]];
109
-    }
110
-
111
-    /**
112
-     * init configuration data
113
-     *
114
-     * set all named properties from associative array, overwriting self with defaults
115
-     * from the schema.
116
-     *
117
-     * @param array $config
118
-     * @return void
119
-     */
120
-    public function importConfigArray(array $config): void
121
-    {
122
-        $result = [];
123
-        foreach (self::SCHEMA as $name => $definition) {
124
-            $result[$name] =
125
-                array_key_exists($name, $config)
126
-                    ? $config[$name]
127
-                    : $this->config[$name] ?? $definition['options']['default'] ?? null;
128
-        }
129
-        $filtered = filter_var_array($result, self::SCHEMA);
130
-
131
-        if (!is_array($filtered)) {
132
-            throw new UnexpectedValueException('Failed to filter Seafile configuration values.');
133
-        }
134
-
135
-        $this->config = $filtered;
136
-    }
137
-
138
-    /**
139
-     * Hide its internal state from var_dump()
140
-     *
141
-     * Note: The xdebug extension may break this behavior.
142
-     * You should not rely on it if you have debugging extensions installed.
143
-     *
144
-     * @return array
145
-     */
146
-    public function __debugInfo(): array
147
-    {
148
-        $info = $this->config;
149
-        $info['password'] = is_string($info['password']) ? '*' : null;
150
-        $info['sso_auth_user_token'] = is_string($info['sso_auth_user_token']) ? '*' : null;
151
-
152
-        return $info;
153
-    }
154
-
155
-    /* ArrayAccess implementation */
156
-
157
-    public function offsetExists(mixed $offset): bool
158
-    {
159
-        return isset($this->config[$offset]);
160
-    }
161
-
162
-    public function offsetGet(mixed $offset): mixed
163
-    {
164
-        return $this->config[$offset];
165
-    }
166
-
167
-    public function offsetSet(mixed $offset, mixed $value): void
168
-    {
169
-        trigger_error('modification by write is undefined behaviour', E_USER_WARNING);
170
-    }
171
-
172
-    public function offsetUnset(mixed $offset): void
173
-    {
174
-        trigger_error('modification by delete is undefined behaviour', E_USER_WARNING);
175
-    }
22
+	private const DEF = [
23
+		'server' => 'server_address',
24
+		'port' => 'server_port',
25
+		'ssl' => 'server_ssl',
26
+		'path' => 'server_path',
27
+		'user' => 'user',
28
+		'pass' => 'password',
29
+		'sso_auth_user_token' => 'sso_auth_user_token',
30
+	];
31
+
32
+	private const SCHEMA = [
33
+		self::DEF['server'] => [
34
+			'filter' => FILTER_VALIDATE_DOMAIN,
35
+			'flags' => FILTER_FLAG_HOSTNAME,
36
+			'options' => ['default' => 'seafile.example.com'],
37
+		],
38
+		self::DEF['port'] => [
39
+			'filter' => FILTER_VALIDATE_INT,
40
+			'options' => ['min_range' => 0, 'max_range' => 65535, 'default' => 443],
41
+		],
42
+		self::DEF['ssl'] => [
43
+			'filter' => FILTER_VALIDATE_BOOLEAN,
44
+			'options' => ['default' => true],
45
+		],
46
+		self::DEF['path'] => [
47
+			'filter' => FILTER_UNSAFE_RAW,
48
+			'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH,
49
+			'options' => ['default' => ''],
50
+		],
51
+		self::DEF['user'] => [
52
+			'filter' => FILTER_UNSAFE_RAW,
53
+			'flags' => FILTER_FLAG_STRIP_LOW,
54
+			'options' => ['default' => ''],
55
+		],
56
+		self::DEF['pass'] => [
57
+			'filter' => FILTER_DEFAULT,
58
+			'options' => ['default' => ''],
59
+		],
60
+		self::DEF['sso_auth_user_token'] => [
61
+			'filter' => FILTER_DEFAULT,
62
+			'flags' => FILTER_FLAG_EMPTY_STRING_NULL,
63
+			'options' => ['default' => null],
64
+		],
65
+	];
66
+
67
+	private array $config = [];
68
+
69
+	public function __construct(array $config = [])
70
+	{
71
+		$this->importConfigArray($config);
72
+	}
73
+
74
+	/**
75
+	 * Get the URL of the Seafile servers REST API
76
+	 *
77
+	 * @return string
78
+	 */
79
+	public function getApiUrl(): string
80
+	{
81
+		$config = $this;
82
+
83
+		$ssl = (bool)$config->ssl;
84
+		$defaultPort = $ssl ? 443 : 80;
85
+		$port = max(0, min(65535, (int)$config->port)) ?: $defaultPort;
86
+		$host = rtrim($config->server, '/');
87
+		$path = ltrim($config->path, '/');
88
+
89
+		$url = sprintf('http%s://%s:%d/%s', $ssl ? 's' : '', $host, $port, $path);
90
+
91
+		return rtrim($url, '/');
92
+	}
93
+
94
+	/**
95
+	 * read-only properties
96
+	 *
97
+	 * @param string $name
98
+	 * @return mixed
99
+	 *
100
+	 * @noinspection MagicMethodsValidityInspection
101
+	 * @noinspection RedundantSuppression
102
+	 */
103
+	public function __get(string $name): mixed
104
+	{
105
+		if (!isset(self::DEF[$name])) {
106
+			throw new OutOfBoundsException("Not a property: \"$name\"");
107
+		}
108
+		return $this->config[self::DEF[$name]];
109
+	}
110
+
111
+	/**
112
+	 * init configuration data
113
+	 *
114
+	 * set all named properties from associative array, overwriting self with defaults
115
+	 * from the schema.
116
+	 *
117
+	 * @param array $config
118
+	 * @return void
119
+	 */
120
+	public function importConfigArray(array $config): void
121
+	{
122
+		$result = [];
123
+		foreach (self::SCHEMA as $name => $definition) {
124
+			$result[$name] =
125
+				array_key_exists($name, $config)
126
+					? $config[$name]
127
+					: $this->config[$name] ?? $definition['options']['default'] ?? null;
128
+		}
129
+		$filtered = filter_var_array($result, self::SCHEMA);
130
+
131
+		if (!is_array($filtered)) {
132
+			throw new UnexpectedValueException('Failed to filter Seafile configuration values.');
133
+		}
134
+
135
+		$this->config = $filtered;
136
+	}
137
+
138
+	/**
139
+	 * Hide its internal state from var_dump()
140
+	 *
141
+	 * Note: The xdebug extension may break this behavior.
142
+	 * You should not rely on it if you have debugging extensions installed.
143
+	 *
144
+	 * @return array
145
+	 */
146
+	public function __debugInfo(): array
147
+	{
148
+		$info = $this->config;
149
+		$info['password'] = is_string($info['password']) ? '*' : null;
150
+		$info['sso_auth_user_token'] = is_string($info['sso_auth_user_token']) ? '*' : null;
151
+
152
+		return $info;
153
+	}
154
+
155
+	/* ArrayAccess implementation */
156
+
157
+	public function offsetExists(mixed $offset): bool
158
+	{
159
+		return isset($this->config[$offset]);
160
+	}
161
+
162
+	public function offsetGet(mixed $offset): mixed
163
+	{
164
+		return $this->config[$offset];
165
+	}
166
+
167
+	public function offsetSet(mixed $offset, mixed $value): void
168
+	{
169
+		trigger_error('modification by write is undefined behaviour', E_USER_WARNING);
170
+	}
171
+
172
+	public function offsetUnset(mixed $offset): void
173
+	{
174
+		trigger_error('modification by delete is undefined behaviour', E_USER_WARNING);
175
+	}
176 176
 }
Please login to merge, or discard this patch.
plugins/filesbackendSeafile/php/Model/Timer.php 1 patch
Indentation   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -9,17 +9,17 @@
 block discarded – undo
9 9
  */
10 10
 final class Timer
11 11
 {
12
-    private float $start;
12
+	private float $start;
13 13
 
14
-    public function __construct()
15
-    {
16
-        $this->start = microtime(true);
17
-    }
14
+	public function __construct()
15
+	{
16
+		$this->start = microtime(true);
17
+	}
18 18
 
19
-    public function __toString(): string
20
-    {
21
-        $time = microtime(true) - $this->start;
19
+	public function __toString(): string
20
+	{
21
+		$time = microtime(true) - $this->start;
22 22
 
23
-        return \sprintf('%.3F', $time);
24
-    }
23
+		return \sprintf('%.3F', $time);
24
+	}
25 25
 }
Please login to merge, or discard this patch.
plugins/filesbackendSeafile/php/Model/ConfigUtil.php 1 patch
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -4,13 +4,13 @@
 block discarded – undo
4 4
 
5 5
 class ConfigUtil
6 6
 {
7
-    /**
8
-     * Get the users email-address (in Grommunio MAPI-Session)
9
-     *
10
-     * @return string
11
-     */
12
-    public static function loadSmtpAddress(): string
13
-    {
14
-        return $GLOBALS['mapisession']->getSMTPAddress();
15
-    }
7
+	/**
8
+	 * Get the users email-address (in Grommunio MAPI-Session)
9
+	 *
10
+	 * @return string
11
+	 */
12
+	public static function loadSmtpAddress(): string
13
+	{
14
+		return $GLOBALS['mapisession']->getSMTPAddress();
15
+	}
16 16
 }
Please login to merge, or discard this patch.