Completed
Pull Request — master (#7260)
by Morris
14:39
created

OC_Util::runningOnMac()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Adam Williamson <[email protected]>
6
 * @author Andreas Fischer <[email protected]>
7
 * @author Arthur Schiwon <[email protected]>
8
 * @author Bart Visscher <[email protected]>
9
 * @author Bernhard Posselt <[email protected]>
10
 * @author Birk Borkason <[email protected]>
11
 * @author Björn Schießle <[email protected]>
12
 * @author Brice Maron <[email protected]>
13
 * @author Christoph Wurst <[email protected]>
14
 * @author Christopher Schäpers <[email protected]>
15
 * @author Clark Tomlinson <[email protected]>
16
 * @author cmeh <[email protected]>
17
 * @author Felix Epp <[email protected]>
18
 * @author Florin Peter <[email protected]>
19
 * @author Frank Karlitschek <[email protected]>
20
 * @author Georg Ehrke <[email protected]>
21
 * @author helix84 <[email protected]>
22
 * @author Ilja Neumann <[email protected]>
23
 * @author Individual IT Services <[email protected]>
24
 * @author Jakob Sack <[email protected]>
25
 * @author Joas Schilling <[email protected]>
26
 * @author Jörn Friedrich Dreyer <[email protected]>
27
 * @author Kawohl <[email protected]>
28
 * @author Lukas Reschke <[email protected]>
29
 * @author Markus Goetz <[email protected]>
30
 * @author Martin Mattel <[email protected]>
31
 * @author Marvin Thomas Rabe <[email protected]>
32
 * @author Michael Gapczynski <[email protected]>
33
 * @author Morris Jobke <[email protected]>
34
 * @author rakekniven <[email protected]>
35
 * @author Robin Appelman <[email protected]>
36
 * @author Robin McCorkell <[email protected]>
37
 * @author Roeland Jago Douma <[email protected]>
38
 * @author Sebastian Wessalowski <[email protected]>
39
 * @author Stefan Rado <[email protected]>
40
 * @author Stefan Weil <[email protected]>
41
 * @author Thomas Müller <[email protected]>
42
 * @author Thomas Tanghus <[email protected]>
43
 * @author Victor Dubiniuk <[email protected]>
44
 * @author Vincent Petry <[email protected]>
45
 * @author Volkan Gezer <[email protected]>
46
 *
47
 * @license AGPL-3.0
48
 *
49
 * This code is free software: you can redistribute it and/or modify
50
 * it under the terms of the GNU Affero General Public License, version 3,
51
 * as published by the Free Software Foundation.
52
 *
53
 * This program is distributed in the hope that it will be useful,
54
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
55
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
56
 * GNU Affero General Public License for more details.
57
 *
58
 * You should have received a copy of the GNU Affero General Public License, version 3,
59
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
60
 *
61
 */
62
63
use OCP\IConfig;
64
use OCP\IGroupManager;
65
use OCP\IUser;
66
67
class OC_Util {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
68
	public static $scripts = array();
69
	public static $styles = array();
70
	public static $headers = array();
71
	private static $rootMounted = false;
72
	private static $fsSetup = false;
73
74
	/** @var array Local cache of version.php */
75
	private static $versionCache = null;
76
77
	protected static function getAppManager() {
78
		return \OC::$server->getAppManager();
79
	}
80
81
	private static function initLocalStorageRootFS() {
82
		// mount local file backend as root
83
		$configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data");
84
		//first set up the local "root" storage
85
		\OC\Files\Filesystem::initMountManager();
86
		if (!self::$rootMounted) {
87
			\OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => $configDataDirectory), '/');
88
			self::$rootMounted = true;
89
		}
90
	}
91
92
	/**
93
	 * mounting an object storage as the root fs will in essence remove the
94
	 * necessity of a data folder being present.
95
	 * TODO make home storage aware of this and use the object storage instead of local disk access
96
	 *
97
	 * @param array $config containing 'class' and optional 'arguments'
98
	 * @suppress PhanDeprecatedFunction
99
	 */
100
	private static function initObjectStoreRootFS($config) {
101
		// check misconfiguration
102
		if (empty($config['class'])) {
103
			\OCP\Util::writeLog('files', 'No class given for objectstore', \OCP\Util::ERROR);
104
		}
105
		if (!isset($config['arguments'])) {
106
			$config['arguments'] = array();
107
		}
108
109
		// instantiate object store implementation
110
		$name = $config['class'];
111 View Code Duplication
		if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) {
112
			$segments = explode('\\', $name);
113
			OC_App::loadApp(strtolower($segments[1]));
114
		}
115
		$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
116
		// mount with plain / root object store implementation
117
		$config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage';
118
119
		// mount object storage as root
120
		\OC\Files\Filesystem::initMountManager();
121 View Code Duplication
		if (!self::$rootMounted) {
122
			\OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/');
123
			self::$rootMounted = true;
124
		}
125
	}
126
127
	/**
128
	 * mounting an object storage as the root fs will in essence remove the
129
	 * necessity of a data folder being present.
130
	 *
131
	 * @param array $config containing 'class' and optional 'arguments'
132
	 * @suppress PhanDeprecatedFunction
133
	 */
134
	private static function initObjectStoreMultibucketRootFS($config) {
135
		// check misconfiguration
136
		if (empty($config['class'])) {
137
			\OCP\Util::writeLog('files', 'No class given for objectstore', \OCP\Util::ERROR);
138
		}
139
		if (!isset($config['arguments'])) {
140
			$config['arguments'] = array();
141
		}
142
143
		// instantiate object store implementation
144
		$name = $config['class'];
145 View Code Duplication
		if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) {
146
			$segments = explode('\\', $name);
147
			OC_App::loadApp(strtolower($segments[1]));
148
		}
149
150
		if (!isset($config['arguments']['bucket'])) {
151
			$config['arguments']['bucket'] = '';
152
		}
153
		// put the root FS always in first bucket for multibucket configuration
154
		$config['arguments']['bucket'] .= '0';
155
156
		$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
157
		// mount with plain / root object store implementation
158
		$config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage';
159
160
		// mount object storage as root
161
		\OC\Files\Filesystem::initMountManager();
162 View Code Duplication
		if (!self::$rootMounted) {
163
			\OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/');
164
			self::$rootMounted = true;
165
		}
166
	}
167
168
	/**
169
	 * Can be set up
170
	 *
171
	 * @param string $user
172
	 * @return boolean
173
	 * @description configure the initial filesystem based on the configuration
174
	 * @suppress PhanDeprecatedFunction
175
	 * @suppress PhanAccessMethodInternal
176
	 */
177
	public static function setupFS($user = '') {
178
		//setting up the filesystem twice can only lead to trouble
179
		if (self::$fsSetup) {
180
			return false;
181
		}
182
183
		\OC::$server->getEventLogger()->start('setup_fs', 'Setup filesystem');
184
185
		// If we are not forced to load a specific user we load the one that is logged in
186
		if ($user === null) {
187
			$user = '';
188
		} else if ($user == "" && \OC::$server->getUserSession()->isLoggedIn()) {
189
			$user = OC_User::getUser();
190
		}
191
192
		// load all filesystem apps before, so no setup-hook gets lost
193
		OC_App::loadApps(array('filesystem'));
194
195
		// the filesystem will finish when $user is not empty,
196
		// mark fs setup here to avoid doing the setup from loading
197
		// OC_Filesystem
198
		if ($user != '') {
199
			self::$fsSetup = true;
200
		}
201
202
		\OC\Files\Filesystem::initMountManager();
203
204
		\OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(false);
205
		\OC\Files\Filesystem::addStorageWrapper('mount_options', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) {
206
			if ($storage->instanceOfStorage('\OC\Files\Storage\Common')) {
207
				/** @var \OC\Files\Storage\Common $storage */
208
				$storage->setMountOptions($mount->getOptions());
209
			}
210
			return $storage;
211
		});
212
213
		\OC\Files\Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, \OCP\Files\Storage\IStorage $storage, \OCP\Files\Mount\IMountPoint $mount) {
214
			if (!$mount->getOption('enable_sharing', true)) {
215
				return new \OC\Files\Storage\Wrapper\PermissionsMask([
216
					'storage' => $storage,
217
					'mask' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_SHARE
218
				]);
219
			}
220
			return $storage;
221
		});
222
223
		// install storage availability wrapper, before most other wrappers
224
		\OC\Files\Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, \OCP\Files\Storage\IStorage $storage) {
225
			if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
226
				return new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]);
227
			}
228
			return $storage;
229
		});
230
231
		\OC\Files\Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) {
232
			if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
233
				return new \OC\Files\Storage\Wrapper\Encoding(['storage' => $storage]);
234
			}
235
			return $storage;
236
		});
237
238
		\OC\Files\Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) {
239
			// set up quota for home storages, even for other users
240
			// which can happen when using sharing
241
242
			/**
243
			 * @var \OC\Files\Storage\Storage $storage
244
			 */
245
			if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
246
				|| $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
247
			) {
248
				/** @var \OC\Files\Storage\Home $storage */
249
				if (is_object($storage->getUser())) {
250
					$user = $storage->getUser()->getUID();
251
					$quota = OC_Util::getUserQuota($user);
252
					if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
253
						return new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => $quota, 'root' => 'files'));
254
					}
255
				}
256
			}
257
258
			return $storage;
259
		});
260
261
		OC_Hook::emit('OC_Filesystem', 'preSetup', array('user' => $user));
262
		\OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(true);
263
264
		//check if we are using an object storage
265
		$objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null);
266
		$objectStoreMultibucket = \OC::$server->getSystemConfig()->getValue('objectstore_multibucket', null);
267
268
		// use the same order as in ObjectHomeMountProvider
269
		if (isset($objectStoreMultibucket)) {
270
			self::initObjectStoreMultibucketRootFS($objectStoreMultibucket);
271
		} elseif (isset($objectStore)) {
272
			self::initObjectStoreRootFS($objectStore);
273
		} else {
274
			self::initLocalStorageRootFS();
275
		}
276
277
		if ($user != '' && !OCP\User::userExists($user)) {
278
			\OC::$server->getEventLogger()->end('setup_fs');
279
			return false;
280
		}
281
282
		//if we aren't logged in, there is no use to set up the filesystem
283
		if ($user != "") {
284
285
			$userDir = '/' . $user . '/files';
286
287
			//jail the user into his "home" directory
288
			\OC\Files\Filesystem::init($user, $userDir);
289
290
			OC_Hook::emit('OC_Filesystem', 'setup', array('user' => $user, 'user_dir' => $userDir));
291
		}
292
		\OC::$server->getEventLogger()->end('setup_fs');
293
		return true;
294
	}
295
296
	/**
297
	 * check if a password is required for each public link
298
	 *
299
	 * @return boolean
300
	 * @suppress PhanDeprecatedFunction
301
	 */
302
	public static function isPublicLinkPasswordRequired() {
303
		$appConfig = \OC::$server->getAppConfig();
304
		$enforcePassword = $appConfig->getValue('core', 'shareapi_enforce_links_password', 'no');
305
		return ($enforcePassword === 'yes') ? true : false;
306
	}
307
308
	/**
309
	 * check if sharing is disabled for the current user
310
	 * @param IConfig $config
311
	 * @param IGroupManager $groupManager
312
	 * @param IUser|null $user
313
	 * @return bool
314
	 */
315
	public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) {
316
		if ($config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
317
			$groupsList = $config->getAppValue('core', 'shareapi_exclude_groups_list', '');
318
			$excludedGroups = json_decode($groupsList);
319 View Code Duplication
			if (is_null($excludedGroups)) {
320
				$excludedGroups = explode(',', $groupsList);
321
				$newValue = json_encode($excludedGroups);
322
				$config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
323
			}
324
			$usersGroups = $groupManager->getUserGroupIds($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by parameter $user on line 315 can be null; however, OCP\IGroupManager::getUserGroupIds() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
325 View Code Duplication
			if (!empty($usersGroups)) {
326
				$remainingGroups = array_diff($usersGroups, $excludedGroups);
327
				// if the user is only in groups which are disabled for sharing then
328
				// sharing is also disabled for the user
329
				if (empty($remainingGroups)) {
330
					return true;
331
				}
332
			}
333
		}
334
		return false;
335
	}
336
337
	/**
338
	 * check if share API enforces a default expire date
339
	 *
340
	 * @return boolean
341
	 * @suppress PhanDeprecatedFunction
342
	 */
343
	public static function isDefaultExpireDateEnforced() {
344
		$isDefaultExpireDateEnabled = \OCP\Config::getAppValue('core', 'shareapi_default_expire_date', 'no');
345
		$enforceDefaultExpireDate = false;
346
		if ($isDefaultExpireDateEnabled === 'yes') {
347
			$value = \OCP\Config::getAppValue('core', 'shareapi_enforce_expire_date', 'no');
348
			$enforceDefaultExpireDate = ($value === 'yes') ? true : false;
349
		}
350
351
		return $enforceDefaultExpireDate;
352
	}
353
354
	/**
355
	 * Get the quota of a user
356
	 *
357
	 * @param string $userId
358
	 * @return float Quota bytes
359
	 */
360
	public static function getUserQuota($userId) {
361
		$user = \OC::$server->getUserManager()->get($userId);
362
		if (is_null($user)) {
363
			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
364
		}
365
		$userQuota = $user->getQuota();
366
		if($userQuota === 'none') {
367
			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
368
		}
369
		return OC_Helper::computerFileSize($userQuota);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression \OC_Helper::computerFileSize($userQuota); of type double|false adds false to the return on line 369 which is incompatible with the return type documented by OC_Util::getUserQuota of type double. It seems like you forgot to handle an error condition.
Loading history...
370
	}
371
372
	/**
373
	 * copies the skeleton to the users /files
374
	 *
375
	 * @param String $userId
376
	 * @param \OCP\Files\Folder $userDirectory
377
	 * @throws \RuntimeException
378
	 * @suppress PhanDeprecatedFunction
379
	 */
380
	public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
381
382
		$skeletonDirectory = \OC::$server->getConfig()->getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
383
		$instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
384
385
		if ($instanceId === null) {
386
			throw new \RuntimeException('no instance id!');
387
		}
388
		$appdata = 'appdata_' . $instanceId;
389
		if ($userId === $appdata) {
390
			throw new \RuntimeException('username is reserved name: ' . $appdata);
391
		}
392
393
		if (!empty($skeletonDirectory)) {
394
			\OCP\Util::writeLog(
395
				'files_skeleton',
396
				'copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'),
397
				\OCP\Util::DEBUG
398
			);
399
			self::copyr($skeletonDirectory, $userDirectory);
400
			// update the file cache
401
			$userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
402
		}
403
	}
404
405
	/**
406
	 * copies a directory recursively by using streams
407
	 *
408
	 * @param string $source
409
	 * @param \OCP\Files\Folder $target
410
	 * @return void
411
	 */
412
	public static function copyr($source, \OCP\Files\Folder $target) {
413
		$logger = \OC::$server->getLogger();
414
415
		// Verify if folder exists
416
		$dir = opendir($source);
417
		if($dir === false) {
418
			$logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
419
			return;
420
		}
421
422
		// Copy the files
423
		while (false !== ($file = readdir($dir))) {
424
			if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
425
				if (is_dir($source . '/' . $file)) {
426
					$child = $target->newFolder($file);
427
					self::copyr($source . '/' . $file, $child);
428
				} else {
429
					$child = $target->newFile($file);
430
					$sourceStream = fopen($source . '/' . $file, 'r');
431
					if($sourceStream === false) {
432
						$logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
433
						closedir($dir);
434
						return;
435
					}
436
					stream_copy_to_stream($sourceStream, $child->fopen('w'));
437
				}
438
			}
439
		}
440
		closedir($dir);
441
	}
442
443
	/**
444
	 * @return void
445
	 * @suppress PhanUndeclaredMethod
446
	 */
447
	public static function tearDownFS() {
448
		\OC\Files\Filesystem::tearDown();
449
		\OC::$server->getRootFolder()->clearCache();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\Files\IRootFolder as the method clearCache() does only exist in the following implementations of said interface: OC\Files\Node\Root.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
450
		self::$fsSetup = false;
451
		self::$rootMounted = false;
452
	}
453
454
	/**
455
	 * get the current installed version of ownCloud
456
	 *
457
	 * @return array
458
	 */
459
	public static function getVersion() {
460
		OC_Util::loadVersion();
461
		return self::$versionCache['OC_Version'];
462
	}
463
464
	/**
465
	 * get the current installed version string of ownCloud
466
	 *
467
	 * @return string
468
	 */
469
	public static function getVersionString() {
470
		OC_Util::loadVersion();
471
		return self::$versionCache['OC_VersionString'];
472
	}
473
474
	/**
475
	 * @deprecated the value is of no use anymore
476
	 * @return string
477
	 */
478
	public static function getEditionString() {
479
		return '';
480
	}
481
482
	/**
483
	 * @description get the update channel of the current installed of ownCloud.
484
	 * @return string
485
	 */
486
	public static function getChannel() {
487
		OC_Util::loadVersion();
488
		return \OC::$server->getConfig()->getSystemValue('updater.release.channel', self::$versionCache['OC_Channel']);
489
	}
490
491
	/**
492
	 * @description get the build number of the current installed of ownCloud.
493
	 * @return string
494
	 */
495
	public static function getBuild() {
496
		OC_Util::loadVersion();
497
		return self::$versionCache['OC_Build'];
498
	}
499
500
	/**
501
	 * @description load the version.php into the session as cache
502
	 * @suppress PhanUndeclaredVariable
503
	 */
504
	private static function loadVersion() {
505
		if (self::$versionCache !== null) {
506
			return;
507
		}
508
509
		$timestamp = filemtime(OC::$SERVERROOT . '/version.php');
510
		require OC::$SERVERROOT . '/version.php';
511
		/** @var $timestamp int */
512
		self::$versionCache['OC_Version_Timestamp'] = $timestamp;
513
		/** @var $OC_Version string */
514
		self::$versionCache['OC_Version'] = $OC_Version;
0 ignored issues
show
Bug introduced by
The variable $OC_Version does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
515
		/** @var $OC_VersionString string */
516
		self::$versionCache['OC_VersionString'] = $OC_VersionString;
0 ignored issues
show
Bug introduced by
The variable $OC_VersionString does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
517
		/** @var $OC_Build string */
518
		self::$versionCache['OC_Build'] = $OC_Build;
0 ignored issues
show
Bug introduced by
The variable $OC_Build does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
519
520
		/** @var $OC_Channel string */
521
		self::$versionCache['OC_Channel'] = $OC_Channel;
0 ignored issues
show
Bug introduced by
The variable $OC_Channel does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
522
	}
523
524
	/**
525
	 * generates a path for JS/CSS files. If no application is provided it will create the path for core.
526
	 *
527
	 * @param string $application application to get the files from
528
	 * @param string $directory directory within this application (css, js, vendor, etc)
529
	 * @param string $file the file inside of the above folder
530
	 * @return string the path
531
	 */
532
	private static function generatePath($application, $directory, $file) {
533
		if (is_null($file)) {
534
			$file = $application;
535
			$application = "";
536
		}
537
		if (!empty($application)) {
538
			return "$application/$directory/$file";
539
		} else {
540
			return "$directory/$file";
541
		}
542
	}
543
544
	/**
545
	 * add a javascript file
546
	 *
547
	 * @param string $application application id
548
	 * @param string|null $file filename
549
	 * @param bool $prepend prepend the Script to the beginning of the list
550
	 * @return void
551
	 */
552
	public static function addScript($application, $file = null, $prepend = false) {
553
		$path = OC_Util::generatePath($application, 'js', $file);
554
555
		// core js files need separate handling
556
		if ($application !== 'core' && $file !== null) {
557
			self::addTranslations ( $application );
558
		}
559
		self::addExternalResource($application, $prepend, $path, "script");
560
	}
561
562
	/**
563
	 * add a javascript file from the vendor sub folder
564
	 *
565
	 * @param string $application application id
566
	 * @param string|null $file filename
567
	 * @param bool $prepend prepend the Script to the beginning of the list
568
	 * @return void
569
	 */
570
	public static function addVendorScript($application, $file = null, $prepend = false) {
571
		$path = OC_Util::generatePath($application, 'vendor', $file);
572
		self::addExternalResource($application, $prepend, $path, "script");
573
	}
574
575
	/**
576
	 * add a translation JS file
577
	 *
578
	 * @param string $application application id
579
	 * @param string|null $languageCode language code, defaults to the current language
580
	 * @param bool|null $prepend prepend the Script to the beginning of the list
581
	 */
582
	public static function addTranslations($application, $languageCode = null, $prepend = false) {
583
		if (is_null($languageCode)) {
584
			$languageCode = \OC::$server->getL10NFactory()->findLanguage($application);
585
		}
586
		if (!empty($application)) {
587
			$path = "$application/l10n/$languageCode";
588
		} else {
589
			$path = "l10n/$languageCode";
590
		}
591
		self::addExternalResource($application, $prepend, $path, "script");
0 ignored issues
show
Bug introduced by
It seems like $prepend defined by parameter $prepend on line 582 can also be of type null; however, OC_Util::addExternalResource() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
592
	}
593
594
	/**
595
	 * add a css file
596
	 *
597
	 * @param string $application application id
598
	 * @param string|null $file filename
599
	 * @param bool $prepend prepend the Style to the beginning of the list
600
	 * @return void
601
	 */
602
	public static function addStyle($application, $file = null, $prepend = false) {
603
		$path = OC_Util::generatePath($application, 'css', $file);
604
		self::addExternalResource($application, $prepend, $path, "style");
605
	}
606
607
	/**
608
	 * add a css file from the vendor sub folder
609
	 *
610
	 * @param string $application application id
611
	 * @param string|null $file filename
612
	 * @param bool $prepend prepend the Style to the beginning of the list
613
	 * @return void
614
	 */
615
	public static function addVendorStyle($application, $file = null, $prepend = false) {
616
		$path = OC_Util::generatePath($application, 'vendor', $file);
617
		self::addExternalResource($application, $prepend, $path, "style");
618
	}
619
620
	/**
621
	 * add an external resource css/js file
622
	 *
623
	 * @param string $application application id
624
	 * @param bool $prepend prepend the file to the beginning of the list
625
	 * @param string $path
626
	 * @param string $type (script or style)
627
	 * @return void
628
	 */
629
	private static function addExternalResource($application, $prepend, $path, $type = "script") {
0 ignored issues
show
Unused Code introduced by
The parameter $application is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
630
631
		if ($type === "style") {
632 View Code Duplication
			if (!in_array($path, self::$styles)) {
633
				if ($prepend === true) {
634
					array_unshift ( self::$styles, $path );
635
				} else {
636
					self::$styles[] = $path;
637
				}
638
			}
639 View Code Duplication
		} elseif ($type === "script") {
640
			if (!in_array($path, self::$scripts)) {
641
				if ($prepend === true) {
642
					array_unshift ( self::$scripts, $path );
643
				} else {
644
					self::$scripts [] = $path;
645
				}
646
			}
647
		}
648
	}
649
650
	/**
651
	 * Add a custom element to the header
652
	 * If $text is null then the element will be written as empty element.
653
	 * So use "" to get a closing tag.
654
	 * @param string $tag tag name of the element
655
	 * @param array $attributes array of attributes for the element
656
	 * @param string $text the text content for the element
657
	 */
658 View Code Duplication
	public static function addHeader($tag, $attributes, $text=null) {
659
		self::$headers[] = array(
660
			'tag' => $tag,
661
			'attributes' => $attributes,
662
			'text' => $text
663
		);
664
	}
665
666
	/**
667
	 * formats a timestamp in the "right" way
668
	 *
669
	 * @param int $timestamp
670
	 * @param bool $dateOnly option to omit time from the result
671
	 * @param DateTimeZone|string $timeZone where the given timestamp shall be converted to
672
	 * @return string timestamp
673
	 *
674
	 * @deprecated Use \OC::$server->query('DateTimeFormatter') instead
675
	 */
676
	public static function formatDate($timestamp, $dateOnly = false, $timeZone = null) {
677
		if ($timeZone !== null && !$timeZone instanceof \DateTimeZone) {
678
			$timeZone = new \DateTimeZone($timeZone);
679
		}
680
681
		/** @var \OC\DateTimeFormatter $formatter */
682
		$formatter = \OC::$server->query('DateTimeFormatter');
683
		if ($dateOnly) {
684
			return $formatter->formatDate($timestamp, 'long', $timeZone);
685
		}
686
		return $formatter->formatDateTime($timestamp, 'long', 'long', $timeZone);
687
	}
688
689
	/**
690
	 * check if the current server configuration is suitable for ownCloud
691
	 *
692
	 * @param \OC\SystemConfig $config
693
	 * @return array arrays with error messages and hints
694
	 */
695
	public static function checkServer(\OC\SystemConfig $config) {
696
		$l = \OC::$server->getL10N('lib');
697
		$errors = array();
698
		$CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data');
699
700
		if (!self::needUpgrade($config) && $config->getValue('installed', false)) {
701
			// this check needs to be done every time
702
			$errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY);
703
		}
704
705
		// Assume that if checkServer() succeeded before in this session, then all is fine.
706
		if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) {
707
			return $errors;
708
		}
709
710
		$webServerRestart = false;
711
		$setup = new \OC\Setup($config, \OC::$server->getIniWrapper(), \OC::$server->getL10N('lib'),
712
			\OC::$server->query(\OCP\Defaults::class), \OC::$server->getLogger(), \OC::$server->getSecureRandom());
713
714
		$urlGenerator = \OC::$server->getURLGenerator();
715
716
		$availableDatabases = $setup->getSupportedDatabases();
717
		if (empty($availableDatabases)) {
718
			$errors[] = array(
719
				'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'),
720
				'hint' => '' //TODO: sane hint
721
			);
722
			$webServerRestart = true;
723
		}
724
725
		// Check if config folder is writable.
726
		if(!OC_Helper::isReadOnlyConfigEnabled()) {
727
			if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
728
				$errors[] = array(
729
					'error' => $l->t('Cannot write into "config" directory'),
730
					'hint' => $l->t('This can usually be fixed by giving the webserver write access to the config directory. See %s',
731
						[$urlGenerator->linkToDocs('admin-dir_permissions')])
732
				);
733
			}
734
		}
735
736
		// Check if there is a writable install folder.
737
		if ($config->getValue('appstoreenabled', true)) {
738
			if (OC_App::getInstallPath() === null
739
				|| !is_writable(OC_App::getInstallPath())
740
				|| !is_readable(OC_App::getInstallPath())
741
			) {
742
				$errors[] = array(
743
					'error' => $l->t('Cannot write into "apps" directory'),
744
					'hint' => $l->t('This can usually be fixed by giving the webserver write access to the apps directory'
745
						. ' or disabling the appstore in the config file. See %s',
746
						[$urlGenerator->linkToDocs('admin-dir_permissions')])
747
				);
748
			}
749
		}
750
		// Create root dir.
751
		if ($config->getValue('installed', false)) {
752
			if (!is_dir($CONFIG_DATADIRECTORY)) {
753
				$success = @mkdir($CONFIG_DATADIRECTORY);
754
				if ($success) {
755
					$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
756
				} else {
757
					$errors[] = [
758
						'error' => $l->t('Cannot create "data" directory'),
759
						'hint' => $l->t('This can usually be fixed by giving the webserver write access to the root directory. See %s',
760
							[$urlGenerator->linkToDocs('admin-dir_permissions')])
761
					];
762
				}
763
			} else if (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
764
				//common hint for all file permissions error messages
765
				$permissionsHint = $l->t('Permissions can usually be fixed by giving the webserver write access to the root directory. See %s.',
766
					[$urlGenerator->linkToDocs('admin-dir_permissions')]);
767
				$errors[] = [
768
					'error' => 'Your data directory is not writable',
769
					'hint' => $permissionsHint
770
				];
771
			} else {
772
				$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
773
			}
774
		}
775
776
		if (!OC_Util::isSetLocaleWorking()) {
777
			$errors[] = array(
778
				'error' => $l->t('Setting locale to %s failed',
779
					array('en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/'
780
						. 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8')),
781
				'hint' => $l->t('Please install one of these locales on your system and restart your webserver.')
782
			);
783
		}
784
785
		// Contains the dependencies that should be checked against
786
		// classes = class_exists
787
		// functions = function_exists
788
		// defined = defined
789
		// ini = ini_get
790
		// If the dependency is not found the missing module name is shown to the EndUser
791
		// When adding new checks always verify that they pass on Travis as well
792
		// for ini settings, see https://github.com/owncloud/administration/blob/master/travis-ci/custom.ini
793
		$dependencies = array(
794
			'classes' => array(
795
				'ZipArchive' => 'zip',
796
				'DOMDocument' => 'dom',
797
				'XMLWriter' => 'XMLWriter',
798
				'XMLReader' => 'XMLReader',
799
			),
800
			'functions' => [
801
				'xml_parser_create' => 'libxml',
802
				'mb_strcut' => 'mb multibyte',
803
				'ctype_digit' => 'ctype',
804
				'json_encode' => 'JSON',
805
				'gd_info' => 'GD',
806
				'gzencode' => 'zlib',
807
				'iconv' => 'iconv',
808
				'simplexml_load_string' => 'SimpleXML',
809
				'hash' => 'HASH Message Digest Framework',
810
				'curl_init' => 'cURL',
811
				'openssl_verify' => 'OpenSSL',
812
			],
813
			'defined' => array(
814
				'PDO::ATTR_DRIVER_NAME' => 'PDO'
815
			),
816
			'ini' => [
817
				'default_charset' => 'UTF-8',
818
			],
819
		);
820
		$missingDependencies = array();
821
		$invalidIniSettings = [];
822
		$moduleHint = $l->t('Please ask your server administrator to install the module.');
823
824
		/**
825
		 * FIXME: The dependency check does not work properly on HHVM on the moment
826
		 *        and prevents installation. Once HHVM is more compatible with our
827
		 *        approach to check for these values we should re-enable those
828
		 *        checks.
829
		 */
830
		$iniWrapper = \OC::$server->getIniWrapper();
831
		if (!self::runningOnHhvm()) {
832
			foreach ($dependencies['classes'] as $class => $module) {
833
				if (!class_exists($class)) {
834
					$missingDependencies[] = $module;
835
				}
836
			}
837
			foreach ($dependencies['functions'] as $function => $module) {
838
				if (!function_exists($function)) {
839
					$missingDependencies[] = $module;
840
				}
841
			}
842
			foreach ($dependencies['defined'] as $defined => $module) {
843
				if (!defined($defined)) {
844
					$missingDependencies[] = $module;
845
				}
846
			}
847
			foreach ($dependencies['ini'] as $setting => $expected) {
848
				if (is_bool($expected)) {
849
					if ($iniWrapper->getBool($setting) !== $expected) {
850
						$invalidIniSettings[] = [$setting, $expected];
851
					}
852
				}
853
				if (is_int($expected)) {
854
					if ($iniWrapper->getNumeric($setting) !== $expected) {
855
						$invalidIniSettings[] = [$setting, $expected];
856
					}
857
				}
858
				if (is_string($expected)) {
859
					if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) {
860
						$invalidIniSettings[] = [$setting, $expected];
861
					}
862
				}
863
			}
864
		}
865
866
		foreach($missingDependencies as $missingDependency) {
867
			$errors[] = array(
868
				'error' => $l->t('PHP module %s not installed.', array($missingDependency)),
869
				'hint' => $moduleHint
870
			);
871
			$webServerRestart = true;
872
		}
873
		foreach($invalidIniSettings as $setting) {
874
			if(is_bool($setting[1])) {
875
				$setting[1] = ($setting[1]) ? 'on' : 'off';
876
			}
877
			$errors[] = [
878
				'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]),
879
				'hint' =>  $l->t('Adjusting this setting in php.ini will make Nextcloud run again')
880
			];
881
			$webServerRestart = true;
882
		}
883
884
		/**
885
		 * The mbstring.func_overload check can only be performed if the mbstring
886
		 * module is installed as it will return null if the checking setting is
887
		 * not available and thus a check on the boolean value fails.
888
		 *
889
		 * TODO: Should probably be implemented in the above generic dependency
890
		 *       check somehow in the long-term.
891
		 */
892
		if($iniWrapper->getBool('mbstring.func_overload') !== null &&
893
			$iniWrapper->getBool('mbstring.func_overload') === true) {
894
			$errors[] = array(
895
				'error' => $l->t('mbstring.func_overload is set to "%s" instead of the expected value "0"', [$iniWrapper->getString('mbstring.func_overload')]),
896
				'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini')
897
			);
898
		}
899
900
		if(function_exists('xml_parser_create') &&
901
			LIBXML_LOADED_VERSION < 20700 ) {
902
			$version = LIBXML_LOADED_VERSION;
903
			$major = floor($version/10000);
904
			$version -= ($major * 10000);
905
			$minor = floor($version/100);
906
			$version -= ($minor * 100);
907
			$patch = $version;
908
			$errors[] = array(
909
				'error' => $l->t('libxml2 2.7.0 is at least required. Currently %s is installed.', [$major . '.' . $minor . '.' . $patch]),
910
				'hint' => $l->t('To fix this issue update your libxml2 version and restart your web server.')
911
			);
912
		}
913
914
		if (!self::isAnnotationsWorking()) {
915
			$errors[] = array(
916
				'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'),
917
				'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')
918
			);
919
		}
920
921
		if (!\OC::$CLI && $webServerRestart) {
922
			$errors[] = array(
923
				'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'),
924
				'hint' => $l->t('Please ask your server administrator to restart the web server.')
925
			);
926
		}
927
928
		$errors = array_merge($errors, self::checkDatabaseVersion());
929
930
		// Cache the result of this function
931
		\OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0);
932
933
		return $errors;
934
	}
935
936
	/**
937
	 * Check the database version
938
	 *
939
	 * @return array errors array
940
	 */
941
	public static function checkDatabaseVersion() {
942
		$l = \OC::$server->getL10N('lib');
943
		$errors = array();
944
		$dbType = \OC::$server->getSystemConfig()->getValue('dbtype', 'sqlite');
945
		if ($dbType === 'pgsql') {
946
			// check PostgreSQL version
947
			try {
948
				$result = \OC_DB::executeAudited('SHOW SERVER_VERSION');
949
				$data = $result->fetchRow();
950
				if (isset($data['server_version'])) {
951
					$version = $data['server_version'];
952
					if (version_compare($version, '9.0.0', '<')) {
953
						$errors[] = array(
954
							'error' => $l->t('PostgreSQL >= 9 required'),
955
							'hint' => $l->t('Please upgrade your database version')
956
						);
957
					}
958
				}
959
			} catch (\Doctrine\DBAL\DBALException $e) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\DBALException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
960
				$logger = \OC::$server->getLogger();
961
				$logger->warning('Error occurred while checking PostgreSQL version, assuming >= 9');
962
				$logger->logException($e);
963
			}
964
		}
965
		return $errors;
966
	}
967
968
	/**
969
	 * Check for correct file permissions of data directory
970
	 *
971
	 * @param string $dataDirectory
972
	 * @return array arrays with error messages and hints
973
	 */
974
	public static function checkDataDirectoryPermissions($dataDirectory) {
975
		$l = \OC::$server->getL10N('lib');
976
		$errors = array();
977
		$permissionsModHint = $l->t('Please change the permissions to 0770 so that the directory'
978
			. ' cannot be listed by other users.');
979
		$perms = substr(decoct(@fileperms($dataDirectory)), -3);
980
		if (substr($perms, -1) !== '0') {
981
			chmod($dataDirectory, 0770);
982
			clearstatcache();
983
			$perms = substr(decoct(@fileperms($dataDirectory)), -3);
984
			if ($perms[2] !== '0') {
985
				$errors[] = [
986
					'error' => $l->t('Your data directory is readable by other users'),
987
					'hint' => $permissionsModHint
988
				];
989
			}
990
		}
991
		return $errors;
992
	}
993
994
	/**
995
	 * Check that the data directory exists and is valid by
996
	 * checking the existence of the ".ocdata" file.
997
	 *
998
	 * @param string $dataDirectory data directory path
999
	 * @return array errors found
1000
	 */
1001
	public static function checkDataDirectoryValidity($dataDirectory) {
1002
		$l = \OC::$server->getL10N('lib');
1003
		$errors = [];
1004 View Code Duplication
		if ($dataDirectory[0] !== '/') {
1005
			$errors[] = [
1006
				'error' => $l->t('Your data directory must be an absolute path'),
1007
				'hint' => $l->t('Check the value of "datadirectory" in your configuration')
1008
			];
1009
		}
1010 View Code Duplication
		if (!file_exists($dataDirectory . '/.ocdata')) {
1011
			$errors[] = [
1012
				'error' => $l->t('Your data directory is invalid'),
1013
				'hint' => $l->t('Ensure there is a file called ".ocdata"' .
1014
					' in the root of the data directory.')
1015
			];
1016
		}
1017
		return $errors;
1018
	}
1019
1020
	/**
1021
	 * Check if the user is logged in, redirects to home if not. With
1022
	 * redirect URL parameter to the request URI.
1023
	 *
1024
	 * @return void
1025
	 */
1026
	public static function checkLoggedIn() {
1027
		// Check if we are a user
1028
		if (!\OC::$server->getUserSession()->isLoggedIn()) {
1029
			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
1030
						'core.login.showLoginForm',
1031
						[
1032
							'redirect_url' => \OC::$server->getRequest()->getRequestUri(),
1033
						]
1034
					)
1035
			);
1036
			exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method checkLoggedIn() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1037
		}
1038
		// Redirect to 2FA challenge selection if 2FA challenge was not solved yet
1039
		if (\OC::$server->getTwoFactorAuthManager()->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
1040
			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
1041
			exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method checkLoggedIn() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1042
		}
1043
	}
1044
1045
	/**
1046
	 * Check if the user is a admin, redirects to home if not
1047
	 *
1048
	 * @return void
1049
	 */
1050
	public static function checkAdminUser() {
1051
		OC_Util::checkLoggedIn();
1052
		if (!OC_User::isAdminUser(OC_User::getUser())) {
1053
			header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
1054
			exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method checkAdminUser() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1055
		}
1056
	}
1057
1058
	/**
1059
	 * Check if the user is a subadmin, redirects to home if not
1060
	 *
1061
	 * @return null|boolean $groups where the current user is subadmin
1062
	 */
1063
	public static function checkSubAdminUser() {
1064
		OC_Util::checkLoggedIn();
1065
		$userObject = \OC::$server->getUserSession()->getUser();
1066
		$isSubAdmin = false;
1067
		if($userObject !== null) {
1068
			$isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject);
1069
		}
1070
1071
		if (!$isSubAdmin) {
1072
			header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
1073
			exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method checkSubAdminUser() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1074
		}
1075
		return true;
1076
	}
1077
1078
	/**
1079
	 * Returns the URL of the default page
1080
	 * based on the system configuration and
1081
	 * the apps visible for the current user
1082
	 *
1083
	 * @return string URL
1084
	 * @suppress PhanDeprecatedFunction
1085
	 */
1086
	public static function getDefaultPageUrl() {
0 ignored issues
show
Coding Style introduced by
getDefaultPageUrl uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1087
		$urlGenerator = \OC::$server->getURLGenerator();
1088
		// Deny the redirect if the URL contains a @
1089
		// This prevents unvalidated redirects like ?redirect_url=:[email protected]
1090
		if (isset($_REQUEST['redirect_url']) && strpos($_REQUEST['redirect_url'], '@') === false) {
1091
			$location = $urlGenerator->getAbsoluteURL(urldecode($_REQUEST['redirect_url']));
1092
		} else {
1093
			$defaultPage = \OC::$server->getAppConfig()->getValue('core', 'defaultpage');
1094
			if ($defaultPage) {
1095
				$location = $urlGenerator->getAbsoluteURL($defaultPage);
1096
			} else {
1097
				$appId = 'files';
1098
				$config = \OC::$server->getConfig();
1099
				$defaultApps = explode(',', $config->getSystemValue('defaultapp', 'files'));
1100
				// find the first app that is enabled for the current user
1101
				foreach ($defaultApps as $defaultApp) {
1102
					$defaultApp = OC_App::cleanAppId(strip_tags($defaultApp));
1103
					if (static::getAppManager()->isEnabledForUser($defaultApp)) {
1104
						$appId = $defaultApp;
1105
						break;
1106
					}
1107
				}
1108
1109
				if($config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true') {
1110
					$location = $urlGenerator->getAbsoluteURL('/apps/' . $appId . '/');
1111
				} else {
1112
					$location = $urlGenerator->getAbsoluteURL('/index.php/apps/' . $appId . '/');
1113
				}
1114
			}
1115
		}
1116
		return $location;
1117
	}
1118
1119
	/**
1120
	 * Redirect to the user default page
1121
	 *
1122
	 * @return void
1123
	 */
1124
	public static function redirectToDefaultPage() {
1125
		$location = self::getDefaultPageUrl();
1126
		header('Location: ' . $location);
1127
		exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method redirectToDefaultPage() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1128
	}
1129
1130
	/**
1131
	 * get an id unique for this instance
1132
	 *
1133
	 * @return string
1134
	 */
1135
	public static function getInstanceId() {
1136
		$id = \OC::$server->getSystemConfig()->getValue('instanceid', null);
1137
		if (is_null($id)) {
1138
			// We need to guarantee at least one letter in instanceid so it can be used as the session_name
1139
			$id = 'oc' . \OC::$server->getSecureRandom()->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_DIGITS);
1140
			\OC::$server->getSystemConfig()->setValue('instanceid', $id);
1141
		}
1142
		return $id;
1143
	}
1144
1145
	/**
1146
	 * Public function to sanitize HTML
1147
	 *
1148
	 * This function is used to sanitize HTML and should be applied on any
1149
	 * string or array of strings before displaying it on a web page.
1150
	 *
1151
	 * @param string|array $value
1152
	 * @return string|array an array of sanitized strings or a single sanitized string, depends on the input parameter.
1153
	 */
1154
	public static function sanitizeHTML($value) {
1155
		if (is_array($value)) {
1156
			$value = array_map(function($value) {
1157
				return self::sanitizeHTML($value);
1158
			}, $value);
1159
		} else {
1160
			// Specify encoding for PHP<5.4
1161
			$value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
1162
		}
1163
		return $value;
1164
	}
1165
1166
	/**
1167
	 * Public function to encode url parameters
1168
	 *
1169
	 * This function is used to encode path to file before output.
1170
	 * Encoding is done according to RFC 3986 with one exception:
1171
	 * Character '/' is preserved as is.
1172
	 *
1173
	 * @param string $component part of URI to encode
1174
	 * @return string
1175
	 */
1176
	public static function encodePath($component) {
1177
		$encoded = rawurlencode($component);
1178
		$encoded = str_replace('%2F', '/', $encoded);
1179
		return $encoded;
1180
	}
1181
1182
1183
	public function createHtaccessTestFile(\OCP\IConfig $config) {
1184
		// php dev server does not support htaccess
1185
		if (php_sapi_name() === 'cli-server') {
1186
			return false;
1187
		}
1188
1189
		// testdata
1190
		$fileName = '/htaccesstest.txt';
1191
		$testContent = 'This is used for testing whether htaccess is properly enabled to disallow access from the outside. This file can be safely removed.';
1192
1193
		// creating a test file
1194
		$testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName;
1195
1196
		if (file_exists($testFile)) {// already running this test, possible recursive call
1197
			return false;
1198
		}
1199
1200
		$fp = @fopen($testFile, 'w');
1201
		if (!$fp) {
1202
			throw new OC\HintException('Can\'t create test file to check for working .htaccess file.',
1203
				'Make sure it is possible for the webserver to write to ' . $testFile);
1204
		}
1205
		fwrite($fp, $testContent);
1206
		fclose($fp);
1207
1208
		return $testContent;
1209
	}
1210
1211
	/**
1212
	 * Check if the .htaccess file is working
1213
	 * @param \OCP\IConfig $config
1214
	 * @return bool
1215
	 * @throws Exception
1216
	 * @throws \OC\HintException If the test file can't get written.
1217
	 */
1218
	public function isHtaccessWorking(\OCP\IConfig $config) {
1219
1220
		if (\OC::$CLI || !$config->getSystemValue('check_for_working_htaccess', true)) {
1221
			return true;
1222
		}
1223
1224
		$testContent = $this->createHtaccessTestFile($config);
1225
		if ($testContent === false) {
1226
			return false;
1227
		}
1228
1229
		$fileName = '/htaccesstest.txt';
1230
		$testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName;
1231
1232
		// accessing the file via http
1233
		$url = \OC::$server->getURLGenerator()->getAbsoluteURL(OC::$WEBROOT . '/data' . $fileName);
1234
		try {
1235
			$content = \OC::$server->getHTTPClientService()->newClient()->get($url)->getBody();
1236
		} catch (\Exception $e) {
1237
			$content = false;
1238
		}
1239
1240
		// cleanup
1241
		@unlink($testFile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1242
1243
		/*
1244
		 * If the content is not equal to test content our .htaccess
1245
		 * is working as required
1246
		 */
1247
		return $content !== $testContent;
1248
	}
1249
1250
	/**
1251
	 * Check if the setlocal call does not work. This can happen if the right
1252
	 * local packages are not available on the server.
1253
	 *
1254
	 * @return bool
1255
	 */
1256
	public static function isSetLocaleWorking() {
1257
		\Patchwork\Utf8\Bootup::initLocale();
1258
		if ('' === basename('§')) {
1259
			return false;
1260
		}
1261
		return true;
1262
	}
1263
1264
	/**
1265
	 * Check if it's possible to get the inline annotations
1266
	 *
1267
	 * @return bool
1268
	 */
1269
	public static function isAnnotationsWorking() {
1270
		$reflection = new \ReflectionMethod(__METHOD__);
1271
		$docs = $reflection->getDocComment();
1272
1273
		return (is_string($docs) && strlen($docs) > 50);
1274
	}
1275
1276
	/**
1277
	 * Check if the PHP module fileinfo is loaded.
1278
	 *
1279
	 * @return bool
1280
	 */
1281
	public static function fileInfoLoaded() {
1282
		return function_exists('finfo_open');
1283
	}
1284
1285
	/**
1286
	 * clear all levels of output buffering
1287
	 *
1288
	 * @return void
1289
	 */
1290
	public static function obEnd() {
1291
		while (ob_get_level()) {
1292
			ob_end_clean();
1293
		}
1294
	}
1295
1296
	/**
1297
	 * Checks whether the server is running on Mac OS X
1298
	 *
1299
	 * @return bool true if running on Mac OS X, false otherwise
1300
	 */
1301
	public static function runningOnMac() {
1302
		return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
1303
	}
1304
1305
	/**
1306
	 * Checks whether server is running on HHVM
1307
	 *
1308
	 * @return bool True if running on HHVM, false otherwise
1309
	 */
1310
	public static function runningOnHhvm() {
1311
		return defined('HHVM_VERSION');
1312
	}
1313
1314
	/**
1315
	 * Handles the case that there may not be a theme, then check if a "default"
1316
	 * theme exists and take that one
1317
	 *
1318
	 * @return string the theme
1319
	 */
1320
	public static function getTheme() {
1321
		$theme = \OC::$server->getSystemConfig()->getValue("theme", '');
1322
1323
		if ($theme === '') {
1324
			if (is_dir(OC::$SERVERROOT . '/themes/default')) {
1325
				$theme = 'default';
1326
			}
1327
		}
1328
1329
		return $theme;
1330
	}
1331
1332
	/**
1333
	 * Clear a single file from the opcode cache
1334
	 * This is useful for writing to the config file
1335
	 * in case the opcode cache does not re-validate files
1336
	 * Returns true if successful, false if unsuccessful:
1337
	 * caller should fall back on clearing the entire cache
1338
	 * with clearOpcodeCache() if unsuccessful
1339
	 *
1340
	 * @param string $path the path of the file to clear from the cache
1341
	 * @return bool true if underlying function returns true, otherwise false
1342
	 */
1343
	public static function deleteFromOpcodeCache($path) {
1344
		$ret = false;
1345
		if ($path) {
1346
			// APC >= 3.1.1
1347
			if (function_exists('apc_delete_file')) {
1348
				$ret = @apc_delete_file($path);
1349
			}
1350
			// Zend OpCache >= 7.0.0, PHP >= 5.5.0
1351
			if (function_exists('opcache_invalidate')) {
1352
				$ret = opcache_invalidate($path);
1353
			}
1354
		}
1355
		return $ret;
1356
	}
1357
1358
	/**
1359
	 * Clear the opcode cache if one exists
1360
	 * This is necessary for writing to the config file
1361
	 * in case the opcode cache does not re-validate files
1362
	 *
1363
	 * @return void
1364
	 * @suppress PhanDeprecatedFunction
1365
	 * @suppress PhanUndeclaredConstant
1366
	 */
1367
	public static function clearOpcodeCache() {
1368
		// APC
1369
		if (function_exists('apc_clear_cache')) {
1370
			apc_clear_cache();
1371
		}
1372
		// Zend Opcache
1373
		if (function_exists('accelerator_reset')) {
1374
			accelerator_reset();
1375
		}
1376
		// XCache
1377
		if (function_exists('xcache_clear_cache')) {
1378
			if (\OC::$server->getIniWrapper()->getBool('xcache.admin.enable_auth')) {
1379
				\OCP\Util::writeLog('core', 'XCache opcode cache will not be cleared because "xcache.admin.enable_auth" is enabled.', \OCP\Util::WARN);
1380
			} else {
1381
				@xcache_clear_cache(XC_TYPE_PHP, 0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1382
			}
1383
		}
1384
		// Opcache (PHP >= 5.5)
1385
		if (function_exists('opcache_reset')) {
1386
			opcache_reset();
1387
		}
1388
	}
1389
1390
	/**
1391
	 * Normalize a unicode string
1392
	 *
1393
	 * @param string $value a not normalized string
1394
	 * @return bool|string
1395
	 */
1396
	public static function normalizeUnicode($value) {
1397
		if(Normalizer::isNormalized($value)) {
1398
			return $value;
1399
		}
1400
1401
		$normalizedValue = Normalizer::normalize($value);
1402
		if ($normalizedValue === null || $normalizedValue === false) {
1403
			\OC::$server->getLogger()->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
1404
			return $value;
1405
		}
1406
1407
		return $normalizedValue;
1408
	}
1409
1410
	/**
1411
	 * A human readable string is generated based on version and build number
1412
	 *
1413
	 * @return string
1414
	 */
1415
	public static function getHumanVersion() {
1416
		$version = OC_Util::getVersionString();
1417
		$build = OC_Util::getBuild();
1418
		if (!empty($build) and OC_Util::getChannel() === 'daily') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1419
			$version .= ' Build:' . $build;
1420
		}
1421
		return $version;
1422
	}
1423
1424
	/**
1425
	 * Returns whether the given file name is valid
1426
	 *
1427
	 * @param string $file file name to check
1428
	 * @return bool true if the file name is valid, false otherwise
1429
	 * @deprecated use \OC\Files\View::verifyPath()
1430
	 */
1431
	public static function isValidFileName($file) {
1432
		$trimmed = trim($file);
1433
		if ($trimmed === '') {
1434
			return false;
1435
		}
1436
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1437
			return false;
1438
		}
1439
1440
		// detect part files
1441
		if (preg_match('/' . \OCP\Files\FileInfo::BLACKLIST_FILES_REGEX . '/', $trimmed) !== 0) {
1442
			return false;
1443
		}
1444
1445
		foreach (str_split($trimmed) as $char) {
1446
			if (strpos(\OCP\Constants::FILENAME_INVALID_CHARS, $char) !== false) {
1447
				return false;
1448
			}
1449
		}
1450
		return true;
1451
	}
1452
1453
	/**
1454
	 * Check whether the instance needs to perform an upgrade,
1455
	 * either when the core version is higher or any app requires
1456
	 * an upgrade.
1457
	 *
1458
	 * @param \OC\SystemConfig $config
1459
	 * @return bool whether the core or any app needs an upgrade
1460
	 * @throws \OC\HintException When the upgrade from the given version is not allowed
1461
	 */
1462
	public static function needUpgrade(\OC\SystemConfig $config) {
1463
		if ($config->getValue('installed', false)) {
1464
			$installedVersion = $config->getValue('version', '0.0.0');
1465
			$currentVersion = implode('.', \OCP\Util::getVersion());
1466
			$versionDiff = version_compare($currentVersion, $installedVersion);
1467
			if ($versionDiff > 0) {
1468
				return true;
1469
			} else if ($config->getValue('debug', false) && $versionDiff < 0) {
1470
				// downgrade with debug
1471
				$installedMajor = explode('.', $installedVersion);
1472
				$installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
1473
				$currentMajor = explode('.', $currentVersion);
1474
				$currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
1475
				if ($installedMajor === $currentMajor) {
1476
					// Same major, allow downgrade for developers
1477
					return true;
1478
				} else {
1479
					// downgrade attempt, throw exception
1480
					throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
1481
				}
1482
			} else if ($versionDiff < 0) {
1483
				// downgrade attempt, throw exception
1484
				throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
1485
			}
1486
1487
			// also check for upgrades for apps (independently from the user)
1488
			$apps = \OC_App::getEnabledApps(false, true);
1489
			$shouldUpgrade = false;
1490
			foreach ($apps as $app) {
1491
				if (\OC_App::shouldUpgrade($app)) {
1492
					$shouldUpgrade = true;
1493
					break;
1494
				}
1495
			}
1496
			return $shouldUpgrade;
1497
		} else {
1498
			return false;
1499
		}
1500
	}
1501
1502
}
1503