Test Failed
Push — master ( 647c72...cd42b5 )
by
unknown
10:25
created

Backend::unshare()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

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

380
			$versions = json_decode(/** @scrutinizer ignore-type */ $versiondata);
Loading history...
381
			$version = $versions->versionstring;
382
		} else {
383
			$version = "Undetected (no ownCloud?)";
384
		}
385
386
		return $version;
387
	}
388
389
	/**
390
	 * Get all shares in the specified folder
391
	 *
392
	 * The response array will look like:
393
	 *
394
	 * array(
395
	 *  path1 => array(
396
	 *      id1 => details1,
397
	 *      id2 => details2
398
	 *  ),
399
	 *  path2 => array(
400
	 *      id1 => ....
401
	 *  )
402
	 * )
403
	 *
404
	 * @param $path
405
	 * @return array
406
	 */
407
	public function getShares($path)
408
	{
409
		$result = array();
410
411
		$this->log('[GETSHARES]: loading shares for folder: ' . $path);
412
		try {
413
			$this->ocs_client->loadShares($path);
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] = array();
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] = array(
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
		return $result;
439
	}
440
441
	/**
442
	 * Get details about the shared files/folders.
443
	 *
444
	 * The response array will look like:
445
	 *
446
	 * array(
447
	 *  path1 => array(
448
	 *      id1 => details1,
449
	 *      id2 => details2
450
	 *  ),
451
	 *  path2 => array(
452
	 *      id1 => ....
453
	 *  )
454
	 * )
455
	 *
456
	 * @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...
457
	 * @return array
458
	 */
459
	public function sharingDetails($patharray)
460
	{
461
		$result = array();
462
463
		// performance optimization
464
		// fetch all shares - so we only need one request
465
		if (count($patharray) > 1) {
466
			try {
467
				$this->ocs_client->loadShares();
468
			} catch(ConnectionException $e) {
469
				$this->log('[SHARINGDETAILS]: connection exception while loading shares: ' . $e->getMessage() . " " . $e->getCode());
470
			}
471
472
			/** @var ocsshare[] $shares */
473
			$shares = $this->ocs_client->getAllShares();
474
			foreach ($patharray as $path) {
475
				$result[$path] = array();
476
				foreach ($shares as $id => $details) {
477
					if ($details->getPath() == $path) {
478
						$result[$path][$id] = array(
479
							"shared" => true,
480
							"id" => $details->getId(),
481
							"shareType" => $details->getShareType(),
482
							"permissions" => $details->getPermissions(),
483
							"expiration" => $details->getExpiration(),
484
							"token" => $details->getToken(),
485
							"url" => $details->getUrl(),
486
							"shareWith" => $details->getShareWith(),
487
							"shareWithDisplayname" => $details->getShareWithDisplayname()
488
						);
489
					}
490
				}
491
			}
492
		} else {
493
			if (count($patharray) == 1) {
494
				try {
495
					$shares = $this->ocs_client->loadShareByPath($patharray[0]);
496
				} catch (FileNotFoundException $e) {
497
					$shares = false;
498
				}
499
500
				$result[$patharray[0]] = array();
501
502
				if ($shares !== false) {
503
					foreach ($shares as $id => $share) {
504
						$result[$patharray[0]][$id] = array(
505
							"shared" => true,
506
							"id" => $share->getId(),
507
							"shareType" => $share->getShareType(),
508
							"permissions" => $share->getPermissions(),
509
							"expiration" => $share->getExpiration(),
510
							"token" => $share->getToken(),
511
							"url" => $share->getUrl(),
512
							"shareWith" => $share->getShareWith(),
513
							"shareWithDisplayname" => $share->getShareWithDisplayName()
514
						);
515
					}
516
				}
517
			} else {
518
				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...
519
			}
520
		}
521
522
		return $result;
523
	}
524
525
	/**
526
	 * Share one or multiple files.
527
	 * As the sharing dialog might differ for different backends, it is implemented as
528
	 * MetaForm - meaning that the argumentnames/count might differ.
529
	 * That's the cause why this function uses an array as parameter.
530
	 *
531
	 * $shareparams should look somehow like this:
532
	 *
533
	 * array(
534
	 *      "path1" => options1,
535
	 *      "path2" => options2
536
	 *
537
	 *      or
538
	 *
539
	 *      "id1" => options1 (ONLY if $update = true)
540
	 * )
541
	 *
542
	 * @param $shareparams
543
	 * @param bool $update
544
	 * @return bool
545
	 */
546
	public function share($shareparams, $update = false)
547
	{
548
		$result = array();
549
		if (count($shareparams) > 0) {
550
551
			/** @var string $path */
552
			foreach ($shareparams as $path => $options) {
553
				$path = rtrim($path, "/");
554
				$this->log('path: ' . $path);
555
				if (!$update) {
556
					$share = $this->ocs_client->createShare($path, $options);
557
					$result[$path] = array(
558
						"shared" => true,
559
						"id" => $share->getId(),
560
						"token" => $share->getToken(),
561
						"url" => $share->getUrl()
562
					);
563
				} else {
564
					foreach ($options as $key => $value) {
565
						$this->ocs_client->updateShare($path, $key, $value);
566
					}
567
					$result[$path] = array(
568
						"shared" => true,
569
						"id" => $path
570
					);
571
				}
572
			}
573
		} else {
574
			$this->log('No share params given');
575
			return false; // no shareparams...
576
		}
577
		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...
578
	}
579
580
	/**
581
	 * Disable sharing for the given files/folders.
582
	 *
583
	 *
584
	 * @param $idarray
585
	 * @return bool
586
	 * @throws \OCSAPI\Exception\ConnectionException
587
	 */
588
	public function unshare($idarray)
589
	{
590
591
		foreach ($idarray as $id) {
592
			$this->ocs_client->deleteShare($id);
593
		}
594
		return true;
595
	}
596
597
	/*
598
	 * Get Recipients that could be shared with, matching the search string
599
	 *
600
	 * @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...
601
	 * @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...
602
	 */
603
	public function getRecipients($search) {
604
		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...
605
	}
606
}
607
608
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
609