Test Failed
Push — master ( cd42b5...841446 )
by
unknown
16:44 queued 06:09
created

Backend::log()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Files\Backend\Owncloud;
4
5
require_once __DIR__ . "/../../files/php/Files/Backend/Webdav/sabredav/FilesWebDavClient.php";
6
require_once __DIR__ . "/../../files/php/Files/Backend/class.abstract_backend.php";
7
require_once __DIR__ . "/../../files/php/Files/Backend/class.exception.php";
8
require_once __DIR__ . "/../../files/php/Files/Backend/interface.quota.php";
9
require_once __DIR__ . "/../../files/php/Files/Backend/interface.version.php";
10
require_once __DIR__ . "/../../files/php/Files/Backend/interface.sharing.php";
11
require_once __DIR__ . "/lib/ocsapi/class.ocsclient.php";
12
13
use Files\Backend\AbstractBackend;
14
use Files\Backend\Exception as BackendException;
15
use Files\Backend\iFeatureSharing;
16
use Files\Backend\Webdav\sabredav\FilesWebDavClient;
17
use OCSAPI\Exception\ConnectionException;
18
use OCSAPI\Exception\FileNotFoundException;
19
use OCSAPI\ocsclient;
20
use OCSAPI\ocsshare;
21
use Sabre\DAV\Exception;
22
use Sabre\HTTP\ClientException;
23
24
/**
25
 * This is a file backend for owncloud servers.
26
 * It requires the Webdav File Backend!
27
 *
28
 * @class   Backend
29
 * @extends AbstractBackend
30
 */
31
class Backend extends \Files\Backend\Webdav\Backend implements iFeatureSharing {
32
	/**
33
	 * @var ocsclient the OCS Api client
34
	 */
35
	public $ocs_client;
36
37
	/**
38
	 * @constructor
39
	 */
40
	public function __construct() {
41
		// initialization
42
		$this->debug = PLUGIN_FILESBROWSER_LOGLEVEL === "DEBUG" ? true : false;
0 ignored issues
show
introduced by
The condition Files\Backend\Owncloud\P...ER_LOGLEVEL === 'DEBUG' is always false.
Loading history...
43
44
		$this->init_form();
45
46
		// set backend description
47
		$this->backendDescription = _("With this backend, you can connect to any ownCloud server.");
48
49
		// set backend display name
50
		$this->backendDisplayName = "ownCloud";
51
52
		// set backend version
53
		// TODO: this should be changed on every release
54
		$this->backendVersion = "3.0";
55
56
		// Backend name used in translations
57
		$this->backendTransName = _('Files ownCloud Backend: ');
0 ignored issues
show
Bug Best Practice introduced by
The property backendTransName does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
58
	}
59
60
	/**
61
	 * Initialise form fields.
62
	 */
63
	private function init_form() {
64
		$this->formConfig = [
65
			"labelAlign" => "left",
66
			"columnCount" => 1,
67
			"labelWidth" => 80,
68
			"defaults" => [
69
				"width" => 292,
70
			],
71
		];
72
73
		$this->formFields = [
74
			[
75
				"name" => "server_address",
76
				"fieldLabel" => _('Server address'),
77
				"editor" => [
78
					"allowBlank" => false,
79
				],
80
			],
81
			[
82
				"name" => "server_port",
83
				"fieldLabel" => _('Server port'),
84
				"editor" => [
85
					"ref" => "../../portField",
86
					"allowBlank" => false,
87
				],
88
			],
89
			[
90
				"name" => "server_ssl",
91
				"fieldLabel" => _('Use SSL'),
92
				"editor" => [
93
					"xtype" => "checkbox",
94
					"listeners" => [
95
						"check" => "Zarafa.plugins.files.data.Actions.onCheckSSL", // this javascript function will be called!
96
					],
97
				],
98
			],
99
			[
100
				"name" => "server_path",
101
				"fieldLabel" => _('Webdav base path'),
102
				"editor" => [
103
					"allowBlank" => false,
104
				],
105
			],
106
			[
107
				"name" => "user",
108
				"fieldLabel" => _('Username'),
109
				"editor" => [
110
					"ref" => "../../usernameField",
111
				],
112
			],
113
			[
114
				"name" => "password",
115
				"fieldLabel" => _('Password'),
116
				"editor" => [
117
					"ref" => "../../passwordField",
118
					"inputType" => "password",
119
				],
120
			],
121
			[
122
				"name" => "use_grommunio_credentials",
123
				"fieldLabel" => _('Use grommunio credentials'),
124
				"editor" => [
125
					"xtype" => "checkbox",
126
					"listeners" => [
127
						"check" => "Zarafa.plugins.files.data.Actions.onCheckCredentials", // this javascript function will be called!
128
					],
129
				],
130
			],
131
		];
132
133
		$this->metaConfig = [
134
			"success" => true,
135
			"metaData" => [
136
				"fields" => $this->formFields,
137
				"formConfig" => $this->formConfig,
138
			],
139
			"data" => [ // here we can specify the default values.
140
				"server_address" => $_SERVER['HTTP_HOST'],
141
				"server_ssl" => true,
142
				"server_port" => "443",
143
				"server_path" => "/files/remote.php/webdav",
144
				"use_grommunio_credentials" => true,
145
			],
146
		];
147
	}
148
149
	/**
150
	 * Opens the connection to the webdav server.
151
	 *
152
	 * @throws BackendException if connection is not successful
153
	 *
154
	 * @return bool true if action succeeded
155
	 */
156
	public function open() {
157
		// check if curl is available
158
		$serverHasCurl = function_exists('curl_version');
159
		if (!$serverHasCurl) {
160
			$e = new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_NO_CURL), 500);
161
			$e->setTitle($this->backendTransName . _('php-curl is not available'));
162
163
			throw $e;
164
		}
165
166
		$davsettings = [
167
			'baseUri' => $this->webdavUrl(),
168
			'userName' => $this->user,
169
			'password' => $this->pass,
170
			'authType' => \Sabre\DAV\Client::AUTH_BASIC,
171
		];
172
173
		try {
174
			$this->sabre_client = new FilesWebDavClient($davsettings);
175
			$this->sabre_client->addCurlSetting(CURLOPT_SSL_VERIFYPEER, !$this->allowselfsigned);
176
177
			$this->ocs_client = new ocsclient($this->getOwncloudBaseURL(), $this->user, $this->pass, $this->allowselfsigned);
178
179
			return true;
180
		}
181
		catch (\Exception $e) {
182
			$this->log('Failed to open: ' . $e->getMessage());
183
			if (intval($e->getHTTPCode()) == 401) {
0 ignored issues
show
Bug introduced by
The method getHTTPCode() does not exist on Exception. Did you maybe mean getCode()? ( Ignorable by Annotation )

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

183
			if (intval($e->/** @scrutinizer ignore-call */ getHTTPCode()) == 401) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
184
				$e = new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_UNAUTHORIZED), $e->getHTTPCode());
185
				$e->setTitle($this->backendTransName . _('Access denied'));
186
187
				throw $e;
188
			}
189
			$e = new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_UNREACHABLE), $e->getHTTPCode());
190
			$e->setTitle($this->backendTransName . _('Connection failed'));
191
192
			throw $e;
193
		}
194
	}
195
196
	/**
197
	 * /**
198
	 * Copy a collection on webdav server
199
	 * Duplicates a collection on the webdav server (serverside).
200
	 * All work is done on the webdav server. If you set param overwrite as true,
201
	 * the target will be overwritten.
202
	 *
203
	 * @param string $src_path  Source path
204
	 * @param string $dst_path  Destination path
205
	 * @param bool   $overwrite Overwrite if collection exists in $dst_path
206
	 * @param bool   $coll      set this to true if you want to copy a folder
207
	 *
208
	 * @throws BackendException if request is not successful
209
	 *
210
	 * @return bool true if action succeeded
211
	 */
212
	private function copy($src_path, $dst_path, $overwrite, $coll) {
0 ignored issues
show
Unused Code introduced by
The method copy() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
213
		$time_start = microtime(true);
214
		$src_path = $this->removeSlash($src_path);
215
		$dst_path = $this->webdavUrl() . $this->removeSlash($dst_path);
216
		$this->log("[COPY] start for dir: {$src_path} -> {$dst_path}");
217
		if ($overwrite) {
218
			$overwrite = 'T';
219
		}
220
		else {
221
			$overwrite = 'F';
222
		}
223
224
		$settings = ["Destination" => $dst_path, 'Overwrite' => $overwrite];
225
		if ($coll) {
226
			$settings = ["Destination" => $dst_path, 'Depth' => 'Infinity'];
227
		}
228
229
		try {
230
			$response = $this->sabre_client->request("COPY", $src_path, null, $settings);
231
			$time_end = microtime(true);
232
			$time = $time_end - $time_start;
233
			$this->log("[COPY] done in {$time} seconds: " . $response['statusCode']);
234
235
			return true;
236
		}
237
		catch (ClientException $e) {
238
			$e = new BackendException($this->parseErrorCodeToMessage($e->getCode()), $e->getCode());
239
			$e->setTitle($this->backendTransName . _('Sabre error'));
240
241
			throw $e;
242
		}
243
		catch (Exception $e) {
244
			$this->log('[COPY] fatal: ' . $e->getMessage());
245
			$e = new BackendException($this->parseErrorCodeToMessage($e->getHTTPCode()), $e->getHTTPCode());
246
			$e->setTitle($this->backendTransName . _('Copying failed'));
247
248
			throw $e;
249
		}
250
	}
251
252
	/**
253
	 * This function will return a user friendly error string.
254
	 *
255
	 * @param number $error_code A error code
256
	 *
257
	 * @return string userfriendly error message
258
	 */
259
	private function parseErrorCodeToMessage($error_code) {
260
		$error = intval($error_code);
261
262
		$msg = _('Unknown error');
263
		$contactAdmin = _('Please contact your system administrator.');
264
265
		switch ($error) {
266
			case CURLE_BAD_PASSWORD_ENTERED:
267
			case self::WD_ERR_UNAUTHORIZED:
268
				$msg = _('Unauthorized. Wrong username or password.');
269
				break;
270
271
			case CURLE_SSL_CONNECT_ERROR:
272
			case CURLE_COULDNT_RESOLVE_HOST:
273
			case CURLE_COULDNT_CONNECT:
274
			case CURLE_OPERATION_TIMEOUTED:
275
			case self::WD_ERR_UNREACHABLE:
276
				$msg = _('File server is not reachable. Please verify the file server URL.');
277
				break;
278
279
			case self::WD_ERR_FORBIDDEN:
280
				$msg = _('You don\'t have enough permissions to view this file or folder.');
281
				break;
282
283
			case self::WD_ERR_NOTFOUND:
284
				$msg = _('The file or folder is not available anymore.');
285
				break;
286
287
			case self::WD_ERR_TIMEOUT:
288
				$msg = _('Connection to the file server timed out. Please check again later.');
289
				break;
290
291
			case self::WD_ERR_LOCKED:
292
				$msg = _('This file is locked by another user. Please try again later.');
293
				break;
294
295
			case self::WD_ERR_FAILED_DEPENDENCY:
296
				$msg = _('The request failed.') . ' ' . $contactAdmin;
297
				break;
298
299
			case self::WD_ERR_INTERNAL:
300
				// This is a general error, might be thrown due to a wrong IP, but we don't know.
301
				$msg = _('The file server encountered an internal problem.') . ' ' . $contactAdmin;
302
				break;
303
304
			case self::WD_ERR_TMP:
305
				$msg = _('We could not write to temporary directory.') . ' ' . $contactAdmin;
306
				break;
307
308
			case self::WD_ERR_FEATURES:
309
				$msg = _('We could not retrieve list of server features.') . ' ' . $contactAdmin;
310
				break;
311
312
			case self::WD_ERR_NO_CURL:
313
				$msg = _('PHP-Curl is not available.') . ' ' . $contactAdmin;
314
				break;
315
		}
316
317
		return $msg;
318
	}
319
320
	/**
321
	 * a simple php error_log wrapper.
322
	 *
323
	 * @param string $err_string error message
324
	 */
325
	private function log($err_string) {
326
		if ($this->debug) {
327
			error_log("[BACKEND_OWNCLOUD]: " . $err_string);
328
		}
329
	}
330
331
	/**
332
	 * Get the base URL of Owncloud.
333
	 * For example: http://demo.owncloud.com/owncloud.
334
	 *
335
	 * @return string
336
	 */
337
	private function getOwncloudBaseURL() {
338
		$webdavurl = $this->webdavUrl();
339
340
		return substr($webdavurl, 0, strlen($webdavurl) - strlen("/remote.php/webdav/"));
341
	}
342
343
	/**
344
	 * ============================ FEATURE FUNCTIONS ========================.
345
	 */
346
347
	/**
348
	 * Return the version string of the server backend.
349
	 *
350
	 * @return string
351
	 */
352
	public function getServerVersion() {
353
		// check if curl is available
354
		$serverHasCurl = function_exists('curl_version');
355
		if (!$serverHasCurl) {
356
			throw new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_NO_CURL), 500);
357
		}
358
359
		$url = $this->getOwncloudBaseURL() . "/status.php";
360
361
		// try to get the contents of the owncloud status page
362
		$ch = curl_init();
363
		curl_setopt($ch, CURLOPT_AUTOREFERER, true);
364
		curl_setopt($ch, CURLOPT_TIMEOUT, 3); // timeout of 3 seconds
365
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
366
		curl_setopt($ch, CURLOPT_URL, $url);
367
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
368
		if ($this->allowselfsigned) {
369
			curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
370
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
371
		}
372
		$versiondata = curl_exec($ch);
373
		$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
374
		curl_close($ch);
375
376
		if ($httpcode && $httpcode == "200" && $versiondata) {
377
			$versions = json_decode($versiondata);
0 ignored issues
show
Bug introduced by
It seems like $versiondata can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

377
			$versions = json_decode(/** @scrutinizer ignore-type */ $versiondata);
Loading history...
378
			$version = $versions->versionstring;
379
		}
380
		else {
381
			$version = "Undetected (no ownCloud?)";
382
		}
383
384
		return $version;
385
	}
386
387
	/**
388
	 * Get all shares in the specified folder.
389
	 *
390
	 * The response array will look like:
391
	 *
392
	 * array(
393
	 *  path1 => array(
394
	 *      id1 => details1,
395
	 *      id2 => details2
396
	 *  ),
397
	 *  path2 => array(
398
	 *      id1 => ....
399
	 *  )
400
	 * )
401
	 *
402
	 * @param $path
403
	 *
404
	 * @return array
405
	 */
406
	public function getShares($path) {
407
		$result = [];
408
409
		$this->log('[GETSHARES]: loading shares for folder: ' . $path);
410
411
		try {
412
			$this->ocs_client->loadShares($path);
413
		}
414
		catch (ConnectionException $e) {
415
			$this->log('[GETSHARES]: connection exception while loading shares: ' . $e->getMessage() . " " . $e->getCode());
416
		}
417
		$shares = $this->ocs_client->getAllShares();
418
419
		$this->log('[GETSHARES]: found ' . count($shares) . ' shares for folder: ' . $path);
0 ignored issues
show
Bug introduced by
$shares of type OCSAPI\ocsshare is incompatible with the type Countable|array expected by parameter $value of count(). ( Ignorable by Annotation )

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

419
		$this->log('[GETSHARES]: found ' . count(/** @scrutinizer ignore-type */ $shares) . ' shares for folder: ' . $path);
Loading history...
420
421
		$result[$path] = [];
422
		if ($shares !== false) {
0 ignored issues
show
introduced by
The condition $shares !== false is always true.
Loading history...
423
			foreach ($shares as $id => $options) {
424
				$result[$path][$id] = [
425
					"shared" => true,
426
					"id" => $options->getId(),
427
					"path" => $options->getPath(),
428
					"shareType" => $options->getShareType(),
429
					"permissions" => $options->getPermissions(),
430
					"expiration" => $options->getExpiration(),
431
					"token" => $options->getToken(),
432
					"url" => $options->getUrl(),
433
					"shareWith" => $options->getShareWith(),
434
					"shareWithDisplayname" => $options->getShareWithDisplayname(),
435
				];
436
			}
437
		}
438
439
		return $result;
440
	}
441
442
	/**
443
	 * Get details about the shared files/folders.
444
	 *
445
	 * The response array will look like:
446
	 *
447
	 * array(
448
	 *  path1 => array(
449
	 *      id1 => details1,
450
	 *      id2 => details2
451
	 *  ),
452
	 *  path2 => array(
453
	 *      id1 => ....
454
	 *  )
455
	 * )
456
	 *
457
	 * @param $patharray Simple array with path's to files or folders
0 ignored issues
show
Bug introduced by
The type Files\Backend\Owncloud\Simple was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
458
	 *
459
	 * @return array
460
	 */
461
	public function sharingDetails($patharray) {
462
		$result = [];
463
464
		// performance optimization
465
		// fetch all shares - so we only need one request
466
		if (count($patharray) > 1) {
467
			try {
468
				$this->ocs_client->loadShares();
469
			}
470
			catch (ConnectionException $e) {
471
				$this->log('[SHARINGDETAILS]: connection exception while loading shares: ' . $e->getMessage() . " " . $e->getCode());
472
			}
473
474
			/** @var ocsshare[] $shares */
475
			$shares = $this->ocs_client->getAllShares();
476
			foreach ($patharray as $path) {
477
				$result[$path] = [];
478
				foreach ($shares as $id => $details) {
479
					if ($details->getPath() == $path) {
480
						$result[$path][$id] = [
481
							"shared" => true,
482
							"id" => $details->getId(),
483
							"shareType" => $details->getShareType(),
484
							"permissions" => $details->getPermissions(),
485
							"expiration" => $details->getExpiration(),
486
							"token" => $details->getToken(),
487
							"url" => $details->getUrl(),
488
							"shareWith" => $details->getShareWith(),
489
							"shareWithDisplayname" => $details->getShareWithDisplayname(),
490
						];
491
					}
492
				}
493
			}
494
		}
495
		else {
496
			if (count($patharray) == 1) {
497
				try {
498
					$shares = $this->ocs_client->loadShareByPath($patharray[0]);
499
				}
500
				catch (FileNotFoundException $e) {
501
					$shares = false;
502
				}
503
504
				$result[$patharray[0]] = [];
505
506
				if ($shares !== false) {
507
					foreach ($shares as $id => $share) {
508
						$result[$patharray[0]][$id] = [
509
							"shared" => true,
510
							"id" => $share->getId(),
511
							"shareType" => $share->getShareType(),
512
							"permissions" => $share->getPermissions(),
513
							"expiration" => $share->getExpiration(),
514
							"token" => $share->getToken(),
515
							"url" => $share->getUrl(),
516
							"shareWith" => $share->getShareWith(),
517
							"shareWithDisplayname" => $share->getShareWithDisplayName(),
518
						];
519
					}
520
				}
521
			}
522
			else {
523
				return false; // $patharray was empty...
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
524
			}
525
		}
526
527
		return $result;
528
	}
529
530
	/**
531
	 * Share one or multiple files.
532
	 * As the sharing dialog might differ for different backends, it is implemented as
533
	 * MetaForm - meaning that the argumentnames/count might differ.
534
	 * That's the cause why this function uses an array as parameter.
535
	 *
536
	 * $shareparams should look somehow like this:
537
	 *
538
	 * array(
539
	 *      "path1" => options1,
540
	 *      "path2" => options2
541
	 *
542
	 *      or
543
	 *
544
	 *      "id1" => options1 (ONLY if $update = true)
545
	 * )
546
	 *
547
	 * @param $shareparams
548
	 * @param bool $update
549
	 *
550
	 * @return bool
551
	 */
552
	public function share($shareparams, $update = false) {
553
		$result = [];
554
		if (count($shareparams) > 0) {
555
			/** @var string $path */
556
			foreach ($shareparams as $path => $options) {
557
				$path = rtrim($path, "/");
558
				$this->log('path: ' . $path);
559
				if (!$update) {
560
					$share = $this->ocs_client->createShare($path, $options);
561
					$result[$path] = [
562
						"shared" => true,
563
						"id" => $share->getId(),
564
						"token" => $share->getToken(),
565
						"url" => $share->getUrl(),
566
					];
567
				}
568
				else {
569
					foreach ($options as $key => $value) {
570
						$this->ocs_client->updateShare($path, $key, $value);
571
					}
572
					$result[$path] = [
573
						"shared" => true,
574
						"id" => $path,
575
					];
576
				}
577
			}
578
		}
579
		else {
580
			$this->log('No share params given');
581
582
			return false; // no shareparams...
583
		}
584
585
		return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result returns the type array which is incompatible with the documented return type boolean.
Loading history...
586
	}
587
588
	/**
589
	 * Disable sharing for the given files/folders.
590
	 *
591
	 * @param $idarray
592
	 *
593
	 * @throws \OCSAPI\Exception\ConnectionException
594
	 *
595
	 * @return bool
596
	 */
597
	public function unshare($idarray) {
598
		foreach ($idarray as $id) {
599
			$this->ocs_client->deleteShare($id);
600
		}
601
602
		return true;
603
	}
604
605
	/*
606
	 * Get Recipients that could be shared with, matching the search string
607
	 *
608
	 * @param $search Searchstring to use
0 ignored issues
show
Bug introduced by
The type Files\Backend\Owncloud\Searchstring was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
609
	 * @return The response from the osc client API
0 ignored issues
show
Bug introduced by
The type Files\Backend\Owncloud\The was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
610
	 */
611
	public function getRecipients($search) {
612
		return $this->ocs_client->getRecipients($search);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->ocs_client->getRecipients($search) returns the type array|array<mixed,array<integer,string>>|false which is incompatible with the documented return type Files\Backend\Owncloud\The.
Loading history...
613
	}
614
}
615