From 368be8894cebd8e730f2ef7994d0b56bdc0f47e2 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Sat, 30 Apr 2016 10:45:19 +0200 Subject: Move non PSR-4 files from lib/private root to legacy As discussed we move all old style classes (OC_FOO_BAR) to legacy. Then from there we can evaluate the need to convert them back or if they can be fully deprecated/deleted. --- lib/private/NaturalSort_DefaultCollator.php | 37 + lib/private/api.php | 533 ---------- lib/private/app.php | 1284 ------------------------ lib/private/archive.php | 158 --- lib/private/db.php | 258 ----- lib/private/defaults.php | 286 ------ lib/private/eventsource.php | 125 --- lib/private/filechunking.php | 175 ---- lib/private/files.php | 316 ------ lib/private/group.php | 300 ------ lib/private/helper.php | 703 ------------- lib/private/hook.php | 145 --- lib/private/image.php | 1147 --------------------- lib/private/installer.php | 638 ------------ lib/private/json.php | 179 ---- lib/private/legacy/api.php | 533 ++++++++++ lib/private/legacy/app.php | 1284 ++++++++++++++++++++++++ lib/private/legacy/archive.php | 158 +++ lib/private/legacy/db.php | 258 +++++ lib/private/legacy/defaults.php | 286 ++++++ lib/private/legacy/eventsource.php | 125 +++ lib/private/legacy/filechunking.php | 175 ++++ lib/private/legacy/files.php | 316 ++++++ lib/private/legacy/group.php | 300 ++++++ lib/private/legacy/helper.php | 703 +++++++++++++ lib/private/legacy/hook.php | 145 +++ lib/private/legacy/image.php | 1147 +++++++++++++++++++++ lib/private/legacy/installer.php | 638 ++++++++++++ lib/private/legacy/json.php | 179 ++++ lib/private/legacy/ocs.php | 42 + lib/private/legacy/response.php | 268 +++++ lib/private/legacy/template.php | 433 ++++++++ lib/private/legacy/user.php | 639 ++++++++++++ lib/private/legacy/util.php | 1450 +++++++++++++++++++++++++++ lib/private/naturalsort_defaultcollator.php | 37 - lib/private/ocs.php | 42 - lib/private/response.php | 268 ----- lib/private/template.php | 433 -------- lib/private/user.php | 639 ------------ lib/private/util.php | 1450 --------------------------- 40 files changed, 9116 insertions(+), 9116 deletions(-) create mode 100644 lib/private/NaturalSort_DefaultCollator.php delete mode 100644 lib/private/api.php delete mode 100644 lib/private/app.php delete mode 100644 lib/private/archive.php delete mode 100644 lib/private/db.php delete mode 100644 lib/private/defaults.php delete mode 100644 lib/private/eventsource.php delete mode 100644 lib/private/filechunking.php delete mode 100644 lib/private/files.php delete mode 100644 lib/private/group.php delete mode 100644 lib/private/helper.php delete mode 100644 lib/private/hook.php delete mode 100644 lib/private/image.php delete mode 100644 lib/private/installer.php delete mode 100644 lib/private/json.php create mode 100644 lib/private/legacy/api.php create mode 100644 lib/private/legacy/app.php create mode 100644 lib/private/legacy/archive.php create mode 100644 lib/private/legacy/db.php create mode 100644 lib/private/legacy/defaults.php create mode 100644 lib/private/legacy/eventsource.php create mode 100644 lib/private/legacy/filechunking.php create mode 100644 lib/private/legacy/files.php create mode 100644 lib/private/legacy/group.php create mode 100644 lib/private/legacy/helper.php create mode 100644 lib/private/legacy/hook.php create mode 100644 lib/private/legacy/image.php create mode 100644 lib/private/legacy/installer.php create mode 100644 lib/private/legacy/json.php create mode 100644 lib/private/legacy/ocs.php create mode 100644 lib/private/legacy/response.php create mode 100644 lib/private/legacy/template.php create mode 100644 lib/private/legacy/user.php create mode 100644 lib/private/legacy/util.php delete mode 100644 lib/private/naturalsort_defaultcollator.php delete mode 100644 lib/private/ocs.php delete mode 100644 lib/private/response.php delete mode 100644 lib/private/template.php delete mode 100644 lib/private/user.php delete mode 100644 lib/private/util.php (limited to 'lib/private') diff --git a/lib/private/NaturalSort_DefaultCollator.php b/lib/private/NaturalSort_DefaultCollator.php new file mode 100644 index 00000000000..7b8400fa8e1 --- /dev/null +++ b/lib/private/NaturalSort_DefaultCollator.php @@ -0,0 +1,37 @@ + + * @author Joas Schilling + * @author Morris Jobke + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC; + +class NaturalSort_DefaultCollator { + public function compare($a, $b) { + $result = strcasecmp($a, $b); + if ($result === 0) { + if ($a === $b) { + return 0; + } + return ($a > $b) ? -1 : 1; + } + return ($result < 0) ? -1 : 1; + } +} diff --git a/lib/private/api.php b/lib/private/api.php deleted file mode 100644 index bab879c95f8..00000000000 --- a/lib/private/api.php +++ /dev/null @@ -1,533 +0,0 @@ - - * @author Bernhard Posselt - * @author Björn Schießle - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Michael Gapczynski - * @author Morris Jobke - * @author Robin Appelman - * @author Thomas Müller - * @author Tom Needham - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -use OCP\API; -use OCP\AppFramework\Http; - -/** - * @author Bart Visscher - * @author Bernhard Posselt - * @author Björn Schießle - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Michael Gapczynski - * @author Morris Jobke - * @author Robin Appelman - * @author Thomas Müller - * @author Tom Needham - * @author Vincent Petry - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -class OC_API { - - /** - * API authentication levels - */ - - /** @deprecated Use \OCP\API::GUEST_AUTH instead */ - const GUEST_AUTH = 0; - - /** @deprecated Use \OCP\API::USER_AUTH instead */ - const USER_AUTH = 1; - - /** @deprecated Use \OCP\API::SUBADMIN_AUTH instead */ - const SUBADMIN_AUTH = 2; - - /** @deprecated Use \OCP\API::ADMIN_AUTH instead */ - const ADMIN_AUTH = 3; - - /** - * API Response Codes - */ - - /** @deprecated Use \OCP\API::RESPOND_UNAUTHORISED instead */ - const RESPOND_UNAUTHORISED = 997; - - /** @deprecated Use \OCP\API::RESPOND_SERVER_ERROR instead */ - const RESPOND_SERVER_ERROR = 996; - - /** @deprecated Use \OCP\API::RESPOND_NOT_FOUND instead */ - const RESPOND_NOT_FOUND = 998; - - /** @deprecated Use \OCP\API::RESPOND_UNKNOWN_ERROR instead */ - const RESPOND_UNKNOWN_ERROR = 999; - - /** - * api actions - */ - protected static $actions = array(); - private static $logoutRequired = false; - private static $isLoggedIn = false; - - /** - * registers an api call - * @param string $method the http method - * @param string $url the url to match - * @param callable $action the function to run - * @param string $app the id of the app registering the call - * @param int $authLevel the level of authentication required for the call - * @param array $defaults - * @param array $requirements - */ - public static function register($method, $url, $action, $app, - $authLevel = API::USER_AUTH, - $defaults = array(), - $requirements = array()) { - $name = strtolower($method).$url; - $name = str_replace(array('/', '{', '}'), '_', $name); - if(!isset(self::$actions[$name])) { - $oldCollection = OC::$server->getRouter()->getCurrentCollection(); - OC::$server->getRouter()->useCollection('ocs'); - OC::$server->getRouter()->create($name, $url) - ->method($method) - ->defaults($defaults) - ->requirements($requirements) - ->action('OC_API', 'call'); - self::$actions[$name] = array(); - OC::$server->getRouter()->useCollection($oldCollection); - } - self::$actions[$name][] = array('app' => $app, 'action' => $action, 'authlevel' => $authLevel); - } - - /** - * handles an api call - * @param array $parameters - */ - public static function call($parameters) { - $request = \OC::$server->getRequest(); - $method = $request->getMethod(); - - // Prepare the request variables - if($method === 'PUT') { - $parameters['_put'] = $request->getParams(); - } else if($method === 'DELETE') { - $parameters['_delete'] = $request->getParams(); - } - $name = $parameters['_route']; - // Foreach registered action - $responses = array(); - foreach(self::$actions[$name] as $action) { - // Check authentication and availability - if(!self::isAuthorised($action)) { - $responses[] = array( - 'app' => $action['app'], - 'response' => new OC_OCS_Result(null, API::RESPOND_UNAUTHORISED, 'Unauthorised'), - 'shipped' => OC_App::isShipped($action['app']), - ); - continue; - } - if(!is_callable($action['action'])) { - $responses[] = array( - 'app' => $action['app'], - 'response' => new OC_OCS_Result(null, API::RESPOND_NOT_FOUND, 'Api method not found'), - 'shipped' => OC_App::isShipped($action['app']), - ); - continue; - } - // Run the action - $responses[] = array( - 'app' => $action['app'], - 'response' => call_user_func($action['action'], $parameters), - 'shipped' => OC_App::isShipped($action['app']), - ); - } - $response = self::mergeResponses($responses); - $format = self::requestedFormat(); - if (self::$logoutRequired) { - \OC::$server->getUserSession()->logout(); - } - - self::respond($response, $format); - } - - /** - * merge the returned result objects into one response - * @param array $responses - * @return OC_OCS_Result - */ - public static function mergeResponses($responses) { - // Sort into shipped and third-party - $shipped = array( - 'succeeded' => array(), - 'failed' => array(), - ); - $thirdparty = array( - 'succeeded' => array(), - 'failed' => array(), - ); - - foreach($responses as $response) { - if($response['shipped'] || ($response['app'] === 'core')) { - if($response['response']->succeeded()) { - $shipped['succeeded'][$response['app']] = $response; - } else { - $shipped['failed'][$response['app']] = $response; - } - } else { - if($response['response']->succeeded()) { - $thirdparty['succeeded'][$response['app']] = $response; - } else { - $thirdparty['failed'][$response['app']] = $response; - } - } - } - - // Remove any error responses if there is one shipped response that succeeded - if(!empty($shipped['failed'])) { - // Which shipped response do we use if they all failed? - // They may have failed for different reasons (different status codes) - // Which response code should we return? - // Maybe any that are not \OCP\API::RESPOND_SERVER_ERROR - // Merge failed responses if more than one - $data = array(); - foreach($shipped['failed'] as $failure) { - $data = array_merge_recursive($data, $failure['response']->getData()); - } - $picked = reset($shipped['failed']); - $code = $picked['response']->getStatusCode(); - $meta = $picked['response']->getMeta(); - $headers = $picked['response']->getHeaders(); - $response = new OC_OCS_Result($data, $code, $meta['message'], $headers); - return $response; - } elseif(!empty($shipped['succeeded'])) { - $responses = array_merge($shipped['succeeded'], $thirdparty['succeeded']); - } elseif(!empty($thirdparty['failed'])) { - // Merge failed responses if more than one - $data = array(); - foreach($thirdparty['failed'] as $failure) { - $data = array_merge_recursive($data, $failure['response']->getData()); - } - $picked = reset($thirdparty['failed']); - $code = $picked['response']->getStatusCode(); - $meta = $picked['response']->getMeta(); - $headers = $picked['response']->getHeaders(); - $response = new OC_OCS_Result($data, $code, $meta['message'], $headers); - return $response; - } else { - $responses = $thirdparty['succeeded']; - } - // Merge the successful responses - $data = []; - $codes = []; - $header = []; - - foreach($responses as $response) { - if($response['shipped']) { - $data = array_merge_recursive($response['response']->getData(), $data); - } else { - $data = array_merge_recursive($data, $response['response']->getData()); - } - $header = array_merge_recursive($header, $response['response']->getHeaders()); - $codes[] = ['code' => $response['response']->getStatusCode(), - 'meta' => $response['response']->getMeta()]; - } - - // Use any non 100 status codes - $statusCode = 100; - $statusMessage = null; - foreach($codes as $code) { - if($code['code'] != 100) { - $statusCode = $code['code']; - $statusMessage = $code['meta']['message']; - break; - } - } - - return new OC_OCS_Result($data, $statusCode, $statusMessage, $header); - } - - /** - * authenticate the api call - * @param array $action the action details as supplied to OC_API::register() - * @return bool - */ - private static function isAuthorised($action) { - $level = $action['authlevel']; - switch($level) { - case API::GUEST_AUTH: - // Anyone can access - return true; - case API::USER_AUTH: - // User required - return self::loginUser(); - case API::SUBADMIN_AUTH: - // Check for subadmin - $user = self::loginUser(); - if(!$user) { - return false; - } else { - $userObject = \OC::$server->getUserSession()->getUser(); - if($userObject === null) { - return false; - } - $isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject); - $admin = OC_User::isAdminUser($user); - if($isSubAdmin || $admin) { - return true; - } else { - return false; - } - } - case API::ADMIN_AUTH: - // Check for admin - $user = self::loginUser(); - if(!$user) { - return false; - } else { - return OC_User::isAdminUser($user); - } - default: - // oops looks like invalid level supplied - return false; - } - } - - /** - * http basic auth - * @return string|false (username, or false on failure) - */ - private static function loginUser() { - if(self::$isLoggedIn === true) { - return \OC_User::getUser(); - } - - // reuse existing login - $loggedIn = OC_User::isLoggedIn(); - if ($loggedIn === true) { - $ocsApiRequest = isset($_SERVER['HTTP_OCS_APIREQUEST']) ? $_SERVER['HTTP_OCS_APIREQUEST'] === 'true' : false; - if ($ocsApiRequest) { - - // initialize the user's filesystem - \OC_Util::setUpFS(\OC_User::getUser()); - self::$isLoggedIn = true; - - return OC_User::getUser(); - } - return false; - } - - // basic auth - because OC_User::login will create a new session we shall only try to login - // if user and pass are set - if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']) ) { - $authUser = $_SERVER['PHP_AUTH_USER']; - $authPw = $_SERVER['PHP_AUTH_PW']; - $return = OC_User::login($authUser, $authPw); - if ($return === true) { - self::$logoutRequired = true; - - // initialize the user's filesystem - \OC_Util::setUpFS(\OC_User::getUser()); - self::$isLoggedIn = true; - - /** - * Add DAV authenticated. This should in an ideal world not be - * necessary but the iOS App reads cookies from anywhere instead - * only the DAV endpoint. - * This makes sure that the cookies will be valid for the whole scope - * @see https://github.com/owncloud/core/issues/22893 - */ - \OC::$server->getSession()->set( - \OCA\DAV\Connector\Sabre\Auth::DAV_AUTHENTICATED, - \OC::$server->getUserSession()->getUser()->getUID() - ); - - return \OC_User::getUser(); - } - } - - return false; - } - - /** - * respond to a call - * @param OC_OCS_Result $result - * @param string $format the format xml|json - */ - public static function respond($result, $format='xml') { - $request = \OC::$server->getRequest(); - - // Send 401 headers if unauthorised - if($result->getStatusCode() === API::RESPOND_UNAUTHORISED) { - // If request comes from JS return dummy auth request - if($request->getHeader('X-Requested-With') === 'XMLHttpRequest') { - header('WWW-Authenticate: DummyBasic realm="Authorisation Required"'); - } else { - header('WWW-Authenticate: Basic realm="Authorisation Required"'); - } - header('HTTP/1.0 401 Unauthorized'); - } - - foreach($result->getHeaders() as $name => $value) { - header($name . ': ' . $value); - } - - $meta = $result->getMeta(); - $data = $result->getData(); - if (self::isV2($request)) { - $statusCode = self::mapStatusCodes($result->getStatusCode()); - if (!is_null($statusCode)) { - $meta['statuscode'] = $statusCode; - OC_Response::setStatus($statusCode); - } - } - - self::setContentType($format); - $body = self::renderResult($format, $meta, $data); - echo $body; - } - - /** - * @param XMLWriter $writer - */ - private static function toXML($array, $writer) { - foreach($array as $k => $v) { - if ($k[0] === '@') { - $writer->writeAttribute(substr($k, 1), $v); - continue; - } else if (is_numeric($k)) { - $k = 'element'; - } - if(is_array($v)) { - $writer->startElement($k); - self::toXML($v, $writer); - $writer->endElement(); - } else { - $writer->writeElement($k, $v); - } - } - } - - /** - * @return string - */ - public static function requestedFormat() { - $formats = array('json', 'xml'); - - $format = !empty($_GET['format']) && in_array($_GET['format'], $formats) ? $_GET['format'] : 'xml'; - return $format; - } - - /** - * Based on the requested format the response content type is set - * @param string $format - */ - public static function setContentType($format = null) { - $format = is_null($format) ? self::requestedFormat() : $format; - if ($format === 'xml') { - header('Content-type: text/xml; charset=UTF-8'); - return; - } - - if ($format === 'json') { - header('Content-Type: application/json; charset=utf-8'); - return; - } - - header('Content-Type: application/octet-stream; charset=utf-8'); - } - - /** - * @param \OCP\IRequest $request - * @return bool - */ - protected static function isV2(\OCP\IRequest $request) { - $script = $request->getScriptName(); - - return substr($script, -11) === '/ocs/v2.php'; - } - - /** - * @param integer $sc - * @return int - */ - public static function mapStatusCodes($sc) { - switch ($sc) { - case API::RESPOND_NOT_FOUND: - return Http::STATUS_NOT_FOUND; - case API::RESPOND_SERVER_ERROR: - return Http::STATUS_INTERNAL_SERVER_ERROR; - case API::RESPOND_UNKNOWN_ERROR: - return Http::STATUS_INTERNAL_SERVER_ERROR; - case API::RESPOND_UNAUTHORISED: - // already handled for v1 - return null; - case 100: - return Http::STATUS_OK; - } - // any 2xx, 4xx and 5xx will be used as is - if ($sc >= 200 && $sc < 600) { - return $sc; - } - - return Http::STATUS_BAD_REQUEST; - } - - /** - * @param string $format - * @return string - */ - public static function renderResult($format, $meta, $data) { - $response = array( - 'ocs' => array( - 'meta' => $meta, - 'data' => $data, - ), - ); - if ($format == 'json') { - return OC_JSON::encode($response); - } - - $writer = new XMLWriter(); - $writer->openMemory(); - $writer->setIndent(true); - $writer->startDocument(); - self::toXML($response, $writer); - $writer->endDocument(); - return $writer->outputMemory(true); - } -} diff --git a/lib/private/app.php b/lib/private/app.php deleted file mode 100644 index 246bf97ee91..00000000000 --- a/lib/private/app.php +++ /dev/null @@ -1,1284 +0,0 @@ - - * @author Bart Visscher - * @author Bernhard Posselt - * @author Björn Schießle - * @author Borjan Tchakaloff - * @author Brice Maron - * @author Christopher Schäpers - * @author Felix Moeller - * @author Frank Karlitschek - * @author Georg Ehrke - * @author Jakob Sack - * @author Jan-Christoph Borchardt - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Kamil Domanski - * @author Lukas Reschke - * @author Markus Goetz - * @author Morris Jobke - * @author RealRancor - * @author Robin Appelman - * @author Robin McCorkell - * @author Roeland Jago Douma - * @author Sam Tuke - * @author Thomas Müller - * @author Thomas Tanghus - * @author Tom Needham - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -use OC\App\DependencyAnalyzer; -use OC\App\Platform; -use OC\OCSClient; -use OC\Repair; - -/** - * This class manages the apps. It allows them to register and integrate in the - * ownCloud ecosystem. Furthermore, this class is responsible for installing, - * upgrading and removing apps. - */ -class OC_App { - static private $appVersion = []; - static private $adminForms = array(); - static private $personalForms = array(); - static private $appInfo = array(); - static private $appTypes = array(); - static private $loadedApps = array(); - static private $altLogin = array(); - const officialApp = 200; - - /** - * clean the appId - * - * @param string|boolean $app AppId that needs to be cleaned - * @return string - */ - public static function cleanAppId($app) { - return str_replace(array('\0', '/', '\\', '..'), '', $app); - } - - /** - * Check if an app is loaded - * - * @param string $app - * @return bool - */ - public static function isAppLoaded($app) { - return in_array($app, self::$loadedApps, true); - } - - /** - * loads all apps - * - * @param string[] | string | null $types - * @return bool - * - * This function walks through the ownCloud directory and loads all apps - * it can find. A directory contains an app if the file /appinfo/info.xml - * exists. - * - * if $types is set, only apps of those types will be loaded - */ - public static function loadApps($types = null) { - if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) { - return false; - } - // Load the enabled apps here - $apps = self::getEnabledApps(); - - // Add each apps' folder as allowed class path - foreach($apps as $app) { - $path = self::getAppPath($app); - if($path !== false) { - \OC::$loader->addValidRoot($path); - } - } - - // prevent app.php from printing output - ob_start(); - foreach ($apps as $app) { - if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) { - self::loadApp($app); - } - } - ob_end_clean(); - - return true; - } - - /** - * load a single app - * - * @param string $app - * @param bool $checkUpgrade whether an upgrade check should be done - * @throws \OC\NeedsUpdateException - */ - public static function loadApp($app, $checkUpgrade = true) { - self::$loadedApps[] = $app; - $appPath = self::getAppPath($app); - if($appPath === false) { - return; - } - \OC::$loader->addValidRoot($appPath); // in case someone calls loadApp() directly - if (is_file($appPath . '/appinfo/app.php')) { - \OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app); - if ($checkUpgrade and self::shouldUpgrade($app)) { - throw new \OC\NeedsUpdateException(); - } - self::requireAppFile($app); - if (self::isType($app, array('authentication'))) { - // since authentication apps affect the "is app enabled for group" check, - // the enabled apps cache needs to be cleared to make sure that the - // next time getEnableApps() is called it will also include apps that were - // enabled for groups - self::$enabledAppsCache = array(); - } - \OC::$server->getEventLogger()->end('load_app_' . $app); - } - } - - /** - * Load app.php from the given app - * - * @param string $app app name - */ - private static function requireAppFile($app) { - try { - // encapsulated here to avoid variable scope conflicts - require_once $app . '/appinfo/app.php'; - } catch (Error $ex) { - \OC::$server->getLogger()->logException($ex); - $blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps(); - if (!in_array($app, $blacklist)) { - self::disable($app); - } - } - } - - /** - * check if an app is of a specific type - * - * @param string $app - * @param string|array $types - * @return bool - */ - public static function isType($app, $types) { - if (is_string($types)) { - $types = array($types); - } - $appTypes = self::getAppTypes($app); - foreach ($types as $type) { - if (array_search($type, $appTypes) !== false) { - return true; - } - } - return false; - } - - /** - * get the types of an app - * - * @param string $app - * @return array - */ - private static function getAppTypes($app) { - //load the cache - if (count(self::$appTypes) == 0) { - self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types'); - } - - if (isset(self::$appTypes[$app])) { - return explode(',', self::$appTypes[$app]); - } else { - return array(); - } - } - - /** - * read app types from info.xml and cache them in the database - */ - public static function setAppTypes($app) { - $appData = self::getAppInfo($app); - if(!is_array($appData)) { - return; - } - - if (isset($appData['types'])) { - $appTypes = implode(',', $appData['types']); - } else { - $appTypes = ''; - } - - \OC::$server->getAppConfig()->setValue($app, 'types', $appTypes); - } - - /** - * check if app is shipped - * - * @param string $appId the id of the app to check - * @return bool - * - * Check if an app that is installed is a shipped app or installed from the appstore. - */ - public static function isShipped($appId) { - return \OC::$server->getAppManager()->isShipped($appId); - } - - /** - * get all enabled apps - */ - protected static $enabledAppsCache = array(); - - /** - * Returns apps enabled for the current user. - * - * @param bool $forceRefresh whether to refresh the cache - * @param bool $all whether to return apps for all users, not only the - * currently logged in one - * @return string[] - */ - public static function getEnabledApps($forceRefresh = false, $all = false) { - if (!\OC::$server->getSystemConfig()->getValue('installed', false)) { - return array(); - } - // in incognito mode or when logged out, $user will be false, - // which is also the case during an upgrade - $appManager = \OC::$server->getAppManager(); - if ($all) { - $user = null; - } else { - $user = \OC::$server->getUserSession()->getUser(); - } - - if (is_null($user)) { - $apps = $appManager->getInstalledApps(); - } else { - $apps = $appManager->getEnabledAppsForUser($user); - } - $apps = array_filter($apps, function ($app) { - return $app !== 'files';//we add this manually - }); - sort($apps); - array_unshift($apps, 'files'); - return $apps; - } - - /** - * checks whether or not an app is enabled - * - * @param string $app app - * @return bool - * - * This function checks whether or not an app is enabled. - */ - public static function isEnabled($app) { - return \OC::$server->getAppManager()->isEnabledForUser($app); - } - - /** - * enables an app - * - * @param mixed $app app - * @param array $groups (optional) when set, only these groups will have access to the app - * @throws \Exception - * @return void - * - * This function set an app as enabled in appconfig. - */ - public static function enable($app, $groups = null) { - self::$enabledAppsCache = array(); // flush - if (!OC_Installer::isInstalled($app)) { - $app = self::installApp($app); - } - - $appManager = \OC::$server->getAppManager(); - if (!is_null($groups)) { - $groupManager = \OC::$server->getGroupManager(); - $groupsList = []; - foreach ($groups as $group) { - $groupItem = $groupManager->get($group); - if ($groupItem instanceof \OCP\IGroup) { - $groupsList[] = $groupManager->get($group); - } - } - $appManager->enableAppForGroups($app, $groupsList); - } else { - $appManager->enableApp($app); - } - } - - /** - * @param string $app - * @return int - */ - private static function downloadApp($app) { - $ocsClient = new OCSClient( - \OC::$server->getHTTPClientService(), - \OC::$server->getConfig(), - \OC::$server->getLogger() - ); - $appData = $ocsClient->getApplication($app, \OCP\Util::getVersion()); - $download = $ocsClient->getApplicationDownload($app, \OCP\Util::getVersion()); - if(isset($download['downloadlink']) and $download['downloadlink']!='') { - // Replace spaces in download link without encoding entire URL - $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); - $info = array('source' => 'http', 'href' => $download['downloadlink'], 'appdata' => $appData); - $app = OC_Installer::installApp($info); - } - return $app; - } - - /** - * @param string $app - * @return bool - */ - public static function removeApp($app) { - if (self::isShipped($app)) { - return false; - } - - return OC_Installer::removeApp($app); - } - - /** - * This function set an app as disabled in appconfig. - * - * @param string $app app - * @throws Exception - */ - public static function disable($app) { - // Convert OCS ID to regular application identifier - if(self::getInternalAppIdByOcs($app) !== false) { - $app = self::getInternalAppIdByOcs($app); - } - - self::$enabledAppsCache = array(); // flush - // check if app is a shipped app or not. if not delete - \OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app)); - $appManager = \OC::$server->getAppManager(); - $appManager->disableApp($app); - } - - /** - * Returns the Settings Navigation - * - * @return string[] - * - * This function returns an array containing all settings pages added. The - * entries are sorted by the key 'order' ascending. - */ - public static function getSettingsNavigation() { - $l = \OC::$server->getL10N('lib'); - $urlGenerator = \OC::$server->getURLGenerator(); - - $settings = array(); - // by default, settings only contain the help menu - if (OC_Util::getEditionString() === '' && - \OC::$server->getSystemConfig()->getValue('knowledgebaseenabled', true) == true - ) { - $settings = array( - array( - "id" => "help", - "order" => 1000, - "href" => $urlGenerator->linkToRoute('settings_help'), - "name" => $l->t("Help"), - "icon" => $urlGenerator->imagePath("settings", "help.svg") - ) - ); - } - - // if the user is logged-in - if (OC_User::isLoggedIn()) { - // personal menu - $settings[] = array( - "id" => "personal", - "order" => 1, - "href" => $urlGenerator->linkToRoute('settings_personal'), - "name" => $l->t("Personal"), - "icon" => $urlGenerator->imagePath("settings", "personal.svg") - ); - - //SubAdmins are also allowed to access user management - $userObject = \OC::$server->getUserSession()->getUser(); - $isSubAdmin = false; - if($userObject !== null) { - $isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject); - } - if ($isSubAdmin) { - // admin users menu - $settings[] = array( - "id" => "core_users", - "order" => 2, - "href" => $urlGenerator->linkToRoute('settings_users'), - "name" => $l->t("Users"), - "icon" => $urlGenerator->imagePath("settings", "users.svg") - ); - } - - // if the user is an admin - if (OC_User::isAdminUser(OC_User::getUser())) { - // admin settings - $settings[] = array( - "id" => "admin", - "order" => 1000, - "href" => $urlGenerator->linkToRoute('settings_admin'), - "name" => $l->t("Admin"), - "icon" => $urlGenerator->imagePath("settings", "admin.svg") - ); - } - } - - $navigation = self::proceedNavigation($settings); - return $navigation; - } - - // This is private as well. It simply works, so don't ask for more details - private static function proceedNavigation($list) { - $activeApp = OC::$server->getNavigationManager()->getActiveEntry(); - foreach ($list as &$navEntry) { - if ($navEntry['id'] == $activeApp) { - $navEntry['active'] = true; - } else { - $navEntry['active'] = false; - } - } - unset($navEntry); - - usort($list, create_function('$a, $b', 'if( $a["order"] == $b["order"] ) {return 0;}elseif( $a["order"] < $b["order"] ) {return -1;}else{return 1;}')); - - return $list; - } - - /** - * Get the path where to install apps - * - * @return string|false - */ - public static function getInstallPath() { - if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) { - return false; - } - - foreach (OC::$APPSROOTS as $dir) { - if (isset($dir['writable']) && $dir['writable'] === true) { - return $dir['path']; - } - } - - \OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR); - return null; - } - - - /** - * search for an app in all app-directories - * - * @param string $appId - * @return false|string - */ - protected static function findAppInDirectories($appId) { - $sanitizedAppId = self::cleanAppId($appId); - if($sanitizedAppId !== $appId) { - return false; - } - static $app_dir = array(); - - if (isset($app_dir[$appId])) { - return $app_dir[$appId]; - } - - $possibleApps = array(); - foreach (OC::$APPSROOTS as $dir) { - if (file_exists($dir['path'] . '/' . $appId)) { - $possibleApps[] = $dir; - } - } - - if (empty($possibleApps)) { - return false; - } elseif (count($possibleApps) === 1) { - $dir = array_shift($possibleApps); - $app_dir[$appId] = $dir; - return $dir; - } else { - $versionToLoad = array(); - foreach ($possibleApps as $possibleApp) { - $version = self::getAppVersionByPath($possibleApp['path']); - if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) { - $versionToLoad = array( - 'dir' => $possibleApp, - 'version' => $version, - ); - } - } - $app_dir[$appId] = $versionToLoad['dir']; - return $versionToLoad['dir']; - //TODO - write test - } - } - - /** - * Get the directory for the given app. - * If the app is defined in multiple directories, the first one is taken. (false if not found) - * - * @param string $appId - * @return string|false - */ - public static function getAppPath($appId) { - if ($appId === null || trim($appId) === '') { - return false; - } - - if (($dir = self::findAppInDirectories($appId)) != false) { - return $dir['path'] . '/' . $appId; - } - return false; - } - - - /** - * check if an app's directory is writable - * - * @param string $appId - * @return bool - */ - public static function isAppDirWritable($appId) { - $path = self::getAppPath($appId); - return ($path !== false) ? is_writable($path) : false; - } - - /** - * Get the path for the given app on the access - * If the app is defined in multiple directories, the first one is taken. (false if not found) - * - * @param string $appId - * @return string|false - */ - public static function getAppWebPath($appId) { - if (($dir = self::findAppInDirectories($appId)) != false) { - return OC::$WEBROOT . $dir['url'] . '/' . $appId; - } - return false; - } - - /** - * get the last version of the app from appinfo/info.xml - * - * @param string $appId - * @return string - */ - public static function getAppVersion($appId) { - if (!isset(self::$appVersion[$appId])) { - $file = self::getAppPath($appId); - self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0'; - } - return self::$appVersion[$appId]; - } - - /** - * get app's version based on it's path - * - * @param string $path - * @return string - */ - public static function getAppVersionByPath($path) { - $infoFile = $path . '/appinfo/info.xml'; - $appData = self::getAppInfo($infoFile, true); - return isset($appData['version']) ? $appData['version'] : ''; - } - - - /** - * Read all app metadata from the info.xml file - * - * @param string $appId id of the app or the path of the info.xml file - * @param boolean $path (optional) - * @return array|null - * @note all data is read from info.xml, not just pre-defined fields - */ - public static function getAppInfo($appId, $path = false) { - if ($path) { - $file = $appId; - } else { - if (isset(self::$appInfo[$appId])) { - return self::$appInfo[$appId]; - } - $appPath = self::getAppPath($appId); - if($appPath === false) { - return null; - } - $file = $appPath . '/appinfo/info.xml'; - } - - $parser = new \OC\App\InfoParser(\OC::$server->getURLGenerator()); - $data = $parser->parse($file); - - if (is_array($data)) { - $data = OC_App::parseAppInfo($data); - } - if(isset($data['ocsid'])) { - $storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid'); - if($storedId !== '' && $storedId !== $data['ocsid']) { - $data['ocsid'] = $storedId; - } - } - - self::$appInfo[$appId] = $data; - - return $data; - } - - /** - * Returns the navigation - * - * @return array - * - * This function returns an array containing all entries added. The - * entries are sorted by the key 'order' ascending. Additional to the keys - * given for each app the following keys exist: - * - active: boolean, signals if the user is on this navigation entry - */ - public static function getNavigation() { - $entries = OC::$server->getNavigationManager()->getAll(); - $navigation = self::proceedNavigation($entries); - return $navigation; - } - - /** - * get the id of loaded app - * - * @return string - */ - public static function getCurrentApp() { - $request = \OC::$server->getRequest(); - $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1); - $topFolder = substr($script, 0, strpos($script, '/')); - if (empty($topFolder)) { - $path_info = $request->getPathInfo(); - if ($path_info) { - $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1); - } - } - if ($topFolder == 'apps') { - $length = strlen($topFolder); - return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1); - } else { - return $topFolder; - } - } - - /** - * @param string $type - * @return array - */ - public static function getForms($type) { - $forms = array(); - switch ($type) { - case 'admin': - $source = self::$adminForms; - break; - case 'personal': - $source = self::$personalForms; - break; - default: - return array(); - } - foreach ($source as $form) { - $forms[] = include $form; - } - return $forms; - } - - /** - * register an admin form to be shown - * - * @param string $app - * @param string $page - */ - public static function registerAdmin($app, $page) { - self::$adminForms[] = $app . '/' . $page . '.php'; - } - - /** - * register a personal form to be shown - * @param string $app - * @param string $page - */ - public static function registerPersonal($app, $page) { - self::$personalForms[] = $app . '/' . $page . '.php'; - } - - /** - * @param array $entry - */ - public static function registerLogIn(array $entry) { - self::$altLogin[] = $entry; - } - - /** - * @return array - */ - public static function getAlternativeLogIns() { - return self::$altLogin; - } - - /** - * get a list of all apps in the apps folder - * - * @return array an array of app names (string IDs) - * @todo: change the name of this method to getInstalledApps, which is more accurate - */ - public static function getAllApps() { - - $apps = array(); - - foreach (OC::$APPSROOTS as $apps_dir) { - if (!is_readable($apps_dir['path'])) { - \OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN); - continue; - } - $dh = opendir($apps_dir['path']); - - if (is_resource($dh)) { - while (($file = readdir($dh)) !== false) { - - if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) { - - $apps[] = $file; - } - } - } - } - - return $apps; - } - - /** - * List all apps, this is used in apps.php - * - * @param bool $onlyLocal - * @param bool $includeUpdateInfo Should we check whether there is an update - * in the app store? - * @param OCSClient $ocsClient - * @return array - */ - public static function listAllApps($onlyLocal = false, - $includeUpdateInfo = true, - OCSClient $ocsClient) { - $installedApps = OC_App::getAllApps(); - - //TODO which apps do we want to blacklist and how do we integrate - // blacklisting with the multi apps folder feature? - - //we don't want to show configuration for these - $blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps(); - $appList = array(); - - foreach ($installedApps as $app) { - if (array_search($app, $blacklist) === false) { - - $info = OC_App::getAppInfo($app); - if (!is_array($info)) { - \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR); - continue; - } - - if (!isset($info['name'])) { - \OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR); - continue; - } - - $enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'no'); - $info['groups'] = null; - if ($enabled === 'yes') { - $active = true; - } else if ($enabled === 'no') { - $active = false; - } else { - $active = true; - $info['groups'] = $enabled; - } - - $info['active'] = $active; - - if (self::isShipped($app)) { - $info['internal'] = true; - $info['level'] = self::officialApp; - $info['removable'] = false; - } else { - $info['internal'] = false; - $info['removable'] = true; - } - - $info['update'] = ($includeUpdateInfo) ? OC_Installer::isUpdateAvailable($app) : null; - - $appPath = self::getAppPath($app); - if($appPath !== false) { - $appIcon = $appPath . '/img/' . $app . '.svg'; - if (file_exists($appIcon)) { - $info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg'); - $info['previewAsIcon'] = true; - } else { - $appIcon = $appPath . '/img/app.svg'; - if (file_exists($appIcon)) { - $info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg'); - $info['previewAsIcon'] = true; - } - } - } - $info['version'] = OC_App::getAppVersion($app); - $appList[] = $info; - } - } - if ($onlyLocal) { - $remoteApps = []; - } else { - $remoteApps = OC_App::getAppstoreApps('approved', null, $ocsClient); - } - if ($remoteApps) { - // Remove duplicates - foreach ($appList as $app) { - foreach ($remoteApps AS $key => $remote) { - if ($app['name'] === $remote['name'] || - (isset($app['ocsid']) && - $app['ocsid'] === $remote['id']) - ) { - unset($remoteApps[$key]); - } - } - } - $combinedApps = array_merge($appList, $remoteApps); - } else { - $combinedApps = $appList; - } - - return $combinedApps; - } - - /** - * Returns the internal app ID or false - * @param string $ocsID - * @return string|false - */ - public static function getInternalAppIdByOcs($ocsID) { - if(is_numeric($ocsID)) { - $idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid'); - if(array_search($ocsID, $idArray)) { - return array_search($ocsID, $idArray); - } - } - return false; - } - - /** - * Get a list of all apps on the appstore - * @param string $filter - * @param string|null $category - * @param OCSClient $ocsClient - * @return array|bool multi-dimensional array of apps. - * Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description - */ - public static function getAppstoreApps($filter = 'approved', - $category = null, - OCSClient $ocsClient) { - $categories = [$category]; - - if (is_null($category)) { - $categoryNames = $ocsClient->getCategories(\OCP\Util::getVersion()); - if (is_array($categoryNames)) { - // Check that categories of apps were retrieved correctly - if (!$categories = array_keys($categoryNames)) { - return false; - } - } else { - return false; - } - } - - $page = 0; - $remoteApps = $ocsClient->getApplications($categories, $page, $filter, \OCP\Util::getVersion()); - $apps = []; - $i = 0; - $l = \OC::$server->getL10N('core'); - foreach ($remoteApps as $app) { - $potentialCleanId = self::getInternalAppIdByOcs($app['id']); - // enhance app info (for example the description) - $apps[$i] = OC_App::parseAppInfo($app); - $apps[$i]['author'] = $app['personid']; - $apps[$i]['ocs_id'] = $app['id']; - $apps[$i]['internal'] = 0; - $apps[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false; - $apps[$i]['update'] = false; - $apps[$i]['groups'] = false; - $apps[$i]['score'] = $app['score']; - $apps[$i]['removable'] = false; - if ($app['label'] == 'recommended') { - $apps[$i]['internallabel'] = (string)$l->t('Recommended'); - $apps[$i]['internalclass'] = 'recommendedapp'; - } - - // Apps from the appstore are always assumed to be compatible with the - // the current release as the initial filtering is done on the appstore - $apps[$i]['dependencies']['owncloud']['@attributes']['min-version'] = implode('.', \OCP\Util::getVersion()); - $apps[$i]['dependencies']['owncloud']['@attributes']['max-version'] = implode('.', \OCP\Util::getVersion()); - - $i++; - } - - - - if (empty($apps)) { - return false; - } else { - return $apps; - } - } - - public static function shouldUpgrade($app) { - $versions = self::getAppVersions(); - $currentVersion = OC_App::getAppVersion($app); - if ($currentVersion && isset($versions[$app])) { - $installedVersion = $versions[$app]; - if (version_compare($currentVersion, $installedVersion, '>')) { - return true; - } - } - return false; - } - - /** - * Adjust the number of version parts of $version1 to match - * the number of version parts of $version2. - * - * @param string $version1 version to adjust - * @param string $version2 version to take the number of parts from - * @return string shortened $version1 - */ - private static function adjustVersionParts($version1, $version2) { - $version1 = explode('.', $version1); - $version2 = explode('.', $version2); - // reduce $version1 to match the number of parts in $version2 - while (count($version1) > count($version2)) { - array_pop($version1); - } - // if $version1 does not have enough parts, add some - while (count($version1) < count($version2)) { - $version1[] = '0'; - } - return implode('.', $version1); - } - - /** - * Check whether the current ownCloud version matches the given - * application's version requirements. - * - * The comparison is made based on the number of parts that the - * app info version has. For example for ownCloud 6.0.3 if the - * app info version is expecting version 6.0, the comparison is - * made on the first two parts of the ownCloud version. - * This means that it's possible to specify "requiremin" => 6 - * and "requiremax" => 6 and it will still match ownCloud 6.0.3. - * - * @param string $ocVersion ownCloud version to check against - * @param array $appInfo app info (from xml) - * - * @return boolean true if compatible, otherwise false - */ - public static function isAppCompatible($ocVersion, $appInfo) { - $requireMin = ''; - $requireMax = ''; - if (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) { - $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version']; - } else if (isset($appInfo['requiremin'])) { - $requireMin = $appInfo['requiremin']; - } else if (isset($appInfo['require'])) { - $requireMin = $appInfo['require']; - } - - if (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) { - $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version']; - } else if (isset($appInfo['requiremax'])) { - $requireMax = $appInfo['requiremax']; - } - - if (is_array($ocVersion)) { - $ocVersion = implode('.', $ocVersion); - } - - if (!empty($requireMin) - && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<') - ) { - - return false; - } - - if (!empty($requireMax) - && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>') - ) { - return false; - } - - return true; - } - - /** - * get the installed version of all apps - */ - public static function getAppVersions() { - static $versions; - - if(!$versions) { - $appConfig = \OC::$server->getAppConfig(); - $versions = $appConfig->getValues(false, 'installed_version'); - } - return $versions; - } - - /** - * @param string $app - * @return bool - * @throws Exception if app is not compatible with this version of ownCloud - * @throws Exception if no app-name was specified - */ - public static function installApp($app) { - $l = \OC::$server->getL10N('core'); - $config = \OC::$server->getConfig(); - $ocsClient = new OCSClient( - \OC::$server->getHTTPClientService(), - $config, - \OC::$server->getLogger() - ); - $appData = $ocsClient->getApplication($app, \OCP\Util::getVersion()); - - // check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string - if (!is_numeric($app)) { - $shippedVersion = self::getAppVersion($app); - if ($appData && version_compare($shippedVersion, $appData['version'], '<')) { - $app = self::downloadApp($app); - } else { - $app = OC_Installer::installShippedApp($app); - } - } else { - // Maybe the app is already installed - compare the version in this - // case and use the local already installed one. - // FIXME: This is a horrible hack. I feel sad. The god of code cleanness may forgive me. - $internalAppId = self::getInternalAppIdByOcs($app); - if($internalAppId !== false) { - if($appData && version_compare(\OC_App::getAppVersion($internalAppId), $appData['version'], '<')) { - $app = self::downloadApp($app); - } else { - self::enable($internalAppId); - $app = $internalAppId; - } - } else { - $app = self::downloadApp($app); - } - } - - if ($app !== false) { - // check if the app is compatible with this version of ownCloud - $info = self::getAppInfo($app); - if(!is_array($info)) { - throw new \Exception( - $l->t('App "%s" cannot be installed because appinfo file cannot be read.', - [$info['name']] - ) - ); - } - - $version = \OCP\Util::getVersion(); - if (!self::isAppCompatible($version, $info)) { - throw new \Exception( - $l->t('App "%s" cannot be installed because it is not compatible with this version of ownCloud.', - array($info['name']) - ) - ); - } - - // check for required dependencies - $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l); - $missing = $dependencyAnalyzer->analyze($info); - if (!empty($missing)) { - $missingMsg = join(PHP_EOL, $missing); - throw new \Exception( - $l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s', - array($info['name'], $missingMsg) - ) - ); - } - - $config->setAppValue($app, 'enabled', 'yes'); - if (isset($appData['id'])) { - $config->setAppValue($app, 'ocsid', $appData['id']); - } - \OC_Hook::emit('OC_App', 'post_enable', array('app' => $app)); - } else { - throw new \Exception($l->t("No app name specified")); - } - - return $app; - } - - /** - * update the database for the app and call the update script - * - * @param string $appId - * @return bool - */ - public static function updateApp($appId) { - $appPath = self::getAppPath($appId); - if($appPath === false) { - return false; - } - $appData = self::getAppInfo($appId); - self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']); - if (file_exists($appPath . '/appinfo/database.xml')) { - OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml'); - } - self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']); - self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']); - unset(self::$appVersion[$appId]); - // run upgrade code - if (file_exists($appPath . '/appinfo/update.php')) { - self::loadApp($appId, false); - include $appPath . '/appinfo/update.php'; - } - - //set remote/public handlers - if (array_key_exists('ocsid', $appData)) { - \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']); - } elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) { - \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid'); - } - foreach ($appData['remote'] as $name => $path) { - \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path); - } - foreach ($appData['public'] as $name => $path) { - \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path); - } - - self::setAppTypes($appId); - - $version = \OC_App::getAppVersion($appId); - \OC::$server->getAppConfig()->setValue($appId, 'installed_version', $version); - - return true; - } - - /** - * @param string $appId - * @param string[] $steps - * @throws \OC\NeedsUpdateException - */ - private static function executeRepairSteps($appId, array $steps) { - if (empty($steps)) { - return; - } - // load the app - self::loadApp($appId, false); - - $dispatcher = OC::$server->getEventDispatcher(); - - // load the steps - $r = new Repair([], $dispatcher); - foreach ($steps as $step) { - try { - $r->addStep($step); - } catch (Exception $ex) { - $r->emit('\OC\Repair', 'error', [$ex->getMessage()]); - \OC::$server->getLogger()->logException($ex); - } - } - // run the steps - $r->run(); - } - - /** - * @param string $appId - * @param string[] $steps - */ - private static function setupLiveMigrations($appId, array $steps) { - $queue = \OC::$server->getJobList(); - foreach ($steps as $step) { - $queue->add('OC\Migration\BackgroundRepair', [ - 'app' => $appId, - 'step' => $step]); - } - } - - /** - * @param string $appId - * @return \OC\Files\View|false - */ - public static function getStorage($appId) { - if (OC_App::isEnabled($appId)) { //sanity check - if (OC_User::isLoggedIn()) { - $view = new \OC\Files\View('/' . OC_User::getUser()); - if (!$view->file_exists($appId)) { - $view->mkdir($appId); - } - return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId); - } else { - \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR); - return false; - } - } else { - \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR); - return false; - } - } - - /** - * parses the app data array and enhanced the 'description' value - * - * @param array $data the app data - * @return array improved app data - */ - public static function parseAppInfo(array $data) { - - // just modify the description if it is available - // otherwise this will create a $data element with an empty 'description' - if (isset($data['description'])) { - if (is_string($data['description'])) { - // sometimes the description contains line breaks and they are then also - // shown in this way in the app management which isn't wanted as HTML - // manages line breaks itself - - // first of all we split on empty lines - $paragraphs = preg_split("!\n[[:space:]]*\n!mu", $data['description']); - - $result = []; - foreach ($paragraphs as $value) { - // replace multiple whitespace (tabs, space, newlines) inside a paragraph - // with a single space - also trims whitespace - $result[] = trim(preg_replace('![[:space:]]+!mu', ' ', $value)); - } - - // join the single paragraphs with a empty line in between - $data['description'] = implode("\n\n", $result); - - } else { - $data['description'] = ''; - } - } - - return $data; - } -} diff --git a/lib/private/archive.php b/lib/private/archive.php deleted file mode 100644 index 62512d1448a..00000000000 --- a/lib/private/archive.php +++ /dev/null @@ -1,158 +0,0 @@ - - * @author Bart Visscher - * @author Christopher Schäpers - * @author Felix Moeller - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Morris Jobke - * @author Robin Appelman - * @author Thomas Müller - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -abstract class OC_Archive{ - /** - * Open any of the supported archive types - * - * @param string $path - * @return OC_Archive|void - */ - public static function open($path) { - $mime = \OC::$server->getMimeTypeDetector()->detect($path); - - switch($mime) { - case 'application/zip': - return new OC_Archive_ZIP($path); - case 'application/x-gzip': - return new OC_Archive_TAR($path); - case 'application/x-bzip2': - return new OC_Archive_TAR($path); - } - } - - /** - * @param $source - */ - abstract function __construct($source); - /** - * add an empty folder to the archive - * @param string $path - * @return bool - */ - abstract function addFolder($path); - /** - * add a file to the archive - * @param string $path - * @param string $source either a local file or string data - * @return bool - */ - abstract function addFile($path, $source=''); - /** - * rename a file or folder in the archive - * @param string $source - * @param string $dest - * @return bool - */ - abstract function rename($source, $dest); - /** - * get the uncompressed size of a file in the archive - * @param string $path - * @return int - */ - abstract function filesize($path); - /** - * get the last modified time of a file in the archive - * @param string $path - * @return int - */ - abstract function mtime($path); - /** - * get the files in a folder - * @param string $path - * @return array - */ - abstract function getFolder($path); - /** - * get all files in the archive - * @return array - */ - abstract function getFiles(); - /** - * get the content of a file - * @param string $path - * @return string - */ - abstract function getFile($path); - /** - * extract a single file from the archive - * @param string $path - * @param string $dest - * @return bool - */ - abstract function extractFile($path, $dest); - /** - * extract the archive - * @param string $dest - * @return bool - */ - abstract function extract($dest); - /** - * check if a file or folder exists in the archive - * @param string $path - * @return bool - */ - abstract function fileExists($path); - /** - * remove a file or folder from the archive - * @param string $path - * @return bool - */ - abstract function remove($path); - /** - * get a file handler - * @param string $path - * @param string $mode - * @return resource - */ - abstract function getStream($path, $mode); - /** - * add a folder and all its content - * @param string $path - * @param string $source - * @return boolean|null - */ - function addRecursive($path, $source) { - $dh = opendir($source); - if(is_resource($dh)) { - $this->addFolder($path); - while (($file = readdir($dh)) !== false) { - if($file=='.' or $file=='..') { - continue; - } - if(is_dir($source.'/'.$file)) { - $this->addRecursive($path.'/'.$file, $source.'/'.$file); - }else{ - $this->addFile($path.'/'.$file, $source.'/'.$file); - } - } - } - } -} diff --git a/lib/private/db.php b/lib/private/db.php deleted file mode 100644 index 5d612312919..00000000000 --- a/lib/private/db.php +++ /dev/null @@ -1,258 +0,0 @@ - - * @author Bart Visscher - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Morris Jobke - * @author Robin Appelman - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -/** - * This class manages the access to the database. It basically is a wrapper for - * Doctrine with some adaptions. - */ -class OC_DB { - - /** - * get MDB2 schema manager - * - * @return \OC\DB\MDB2SchemaManager - */ - private static function getMDB2SchemaManager() { - return new \OC\DB\MDB2SchemaManager(\OC::$server->getDatabaseConnection()); - } - - /** - * Prepare a SQL query - * @param string $query Query string - * @param int $limit - * @param int $offset - * @param bool $isManipulation - * @throws \OC\DatabaseException - * @return OC_DB_StatementWrapper prepared SQL query - * - * SQL query via Doctrine prepare(), needs to be execute()'d! - */ - static public function prepare( $query , $limit = null, $offset = null, $isManipulation = null) { - $connection = \OC::$server->getDatabaseConnection(); - - if ($isManipulation === null) { - //try to guess, so we return the number of rows on manipulations - $isManipulation = self::isManipulation($query); - } - - // return the result - try { - $result =$connection->prepare($query, $limit, $offset); - } catch (\Doctrine\DBAL\DBALException $e) { - throw new \OC\DatabaseException($e->getMessage(), $query); - } - // differentiate between query and manipulation - $result = new OC_DB_StatementWrapper($result, $isManipulation); - return $result; - } - - /** - * tries to guess the type of statement based on the first 10 characters - * the current check allows some whitespace but does not work with IF EXISTS or other more complex statements - * - * @param string $sql - * @return bool - */ - static public function isManipulation( $sql ) { - $selectOccurrence = stripos($sql, 'SELECT'); - if ($selectOccurrence !== false && $selectOccurrence < 10) { - return false; - } - $insertOccurrence = stripos($sql, 'INSERT'); - if ($insertOccurrence !== false && $insertOccurrence < 10) { - return true; - } - $updateOccurrence = stripos($sql, 'UPDATE'); - if ($updateOccurrence !== false && $updateOccurrence < 10) { - return true; - } - $deleteOccurrence = stripos($sql, 'DELETE'); - if ($deleteOccurrence !== false && $deleteOccurrence < 10) { - return true; - } - return false; - } - - /** - * execute a prepared statement, on error write log and throw exception - * @param mixed $stmt OC_DB_StatementWrapper, - * an array with 'sql' and optionally 'limit' and 'offset' keys - * .. or a simple sql query string - * @param array $parameters - * @return OC_DB_StatementWrapper - * @throws \OC\DatabaseException - */ - static public function executeAudited( $stmt, array $parameters = null) { - if (is_string($stmt)) { - // convert to an array with 'sql' - if (stripos($stmt, 'LIMIT') !== false) { //OFFSET requires LIMIT, so we only need to check for LIMIT - // TODO try to convert LIMIT OFFSET notation to parameters - $message = 'LIMIT and OFFSET are forbidden for portability reasons,' - . ' pass an array with \'limit\' and \'offset\' instead'; - throw new \OC\DatabaseException($message); - } - $stmt = array('sql' => $stmt, 'limit' => null, 'offset' => null); - } - if (is_array($stmt)) { - // convert to prepared statement - if ( ! array_key_exists('sql', $stmt) ) { - $message = 'statement array must at least contain key \'sql\''; - throw new \OC\DatabaseException($message); - } - if ( ! array_key_exists('limit', $stmt) ) { - $stmt['limit'] = null; - } - if ( ! array_key_exists('limit', $stmt) ) { - $stmt['offset'] = null; - } - $stmt = self::prepare($stmt['sql'], $stmt['limit'], $stmt['offset']); - } - self::raiseExceptionOnError($stmt, 'Could not prepare statement'); - if ($stmt instanceof OC_DB_StatementWrapper) { - $result = $stmt->execute($parameters); - self::raiseExceptionOnError($result, 'Could not execute statement'); - } else { - if (is_object($stmt)) { - $message = 'Expected a prepared statement or array got ' . get_class($stmt); - } else { - $message = 'Expected a prepared statement or array got ' . gettype($stmt); - } - throw new \OC\DatabaseException($message); - } - return $result; - } - - /** - * saves database schema to xml file - * @param string $file name of file - * @param int $mode - * @return bool - * - * TODO: write more documentation - */ - public static function getDbStructure($file) { - $schemaManager = self::getMDB2SchemaManager(); - return $schemaManager->getDbStructure($file); - } - - /** - * Creates tables from XML file - * @param string $file file to read structure from - * @return bool - * - * TODO: write more documentation - */ - public static function createDbFromStructure( $file ) { - $schemaManager = self::getMDB2SchemaManager(); - $result = $schemaManager->createDbFromStructure($file); - return $result; - } - - /** - * update the database schema - * @param string $file file to read structure from - * @throws Exception - * @return string|boolean - */ - public static function updateDbFromStructure($file) { - $schemaManager = self::getMDB2SchemaManager(); - try { - $result = $schemaManager->updateDbFromStructure($file); - } catch (Exception $e) { - \OCP\Util::writeLog('core', 'Failed to update database structure ('.$e.')', \OCP\Util::FATAL); - throw $e; - } - return $result; - } - - /** - * simulate the database schema update - * @param string $file file to read structure from - * @throws Exception - * @return string|boolean - */ - public static function simulateUpdateDbFromStructure($file) { - $schemaManager = self::getMDB2SchemaManager(); - try { - $result = $schemaManager->simulateUpdateDbFromStructure($file); - } catch (Exception $e) { - \OCP\Util::writeLog('core', 'Simulated database structure update failed ('.$e.')', \OCP\Util::FATAL); - throw $e; - } - return $result; - } - - /** - * remove all tables defined in a database structure xml file - * @param string $file the xml file describing the tables - */ - public static function removeDBStructure($file) { - $schemaManager = self::getMDB2SchemaManager(); - $schemaManager->removeDBStructure($file); - } - - /** - * check if a result is an error and throws an exception, works with \Doctrine\DBAL\DBALException - * @param mixed $result - * @param string $message - * @return void - * @throws \OC\DatabaseException - */ - public static function raiseExceptionOnError($result, $message = null) { - if($result === false) { - if ($message === null) { - $message = self::getErrorMessage(); - } else { - $message .= ', Root cause:' . self::getErrorMessage(); - } - throw new \OC\DatabaseException($message, \OC::$server->getDatabaseConnection()->errorCode()); - } - } - - /** - * returns the error code and message as a string for logging - * works with DoctrineException - * @return string - */ - public static function getErrorMessage() { - $connection = \OC::$server->getDatabaseConnection(); - return $connection->getError(); - } - - /** - * Checks if a table exists in the database - the database prefix will be prepended - * - * @param string $table - * @return bool - * @throws \OC\DatabaseException - */ - public static function tableExists($table) { - $connection = \OC::$server->getDatabaseConnection(); - return $connection->tableExists($table); - } -} diff --git a/lib/private/defaults.php b/lib/private/defaults.php deleted file mode 100644 index 43e8c8082cc..00000000000 --- a/lib/private/defaults.php +++ /dev/null @@ -1,286 +0,0 @@ - - * @author Jan-Christoph Borchardt - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Morris Jobke - * @author Pascal de Bruijn - * @author Robin Appelman - * @author Robin McCorkell - * @author scolebrook - * @author Thomas Müller - * @author Volkan Gezer - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -class OC_Defaults { - - private $theme; - private $l; - - private $defaultEntity; - private $defaultName; - private $defaultTitle; - private $defaultBaseUrl; - private $defaultSyncClientUrl; - private $defaultiOSClientUrl; - private $defaultiTunesAppId; - private $defaultAndroidClientUrl; - private $defaultDocBaseUrl; - private $defaultDocVersion; - private $defaultSlogan; - private $defaultLogoClaim; - private $defaultMailHeaderColor; - - function __construct() { - $this->l = \OC::$server->getL10N('lib'); - $version = \OCP\Util::getVersion(); - - $this->defaultEntity = 'ownCloud'; /* e.g. company name, used for footers and copyright notices */ - $this->defaultName = 'ownCloud'; /* short name, used when referring to the software */ - $this->defaultTitle = 'ownCloud'; /* can be a longer name, for titles */ - $this->defaultBaseUrl = 'https://owncloud.org'; - $this->defaultSyncClientUrl = 'https://owncloud.org/sync-clients/'; - $this->defaultiOSClientUrl = 'https://itunes.apple.com/us/app/owncloud/id543672169?mt=8'; - $this->defaultiTunesAppId = '543672169'; - $this->defaultAndroidClientUrl = 'https://play.google.com/store/apps/details?id=com.owncloud.android'; - $this->defaultDocBaseUrl = 'https://doc.owncloud.org'; - $this->defaultDocVersion = $version[0] . '.' . $version[1]; // used to generate doc links - $this->defaultSlogan = $this->l->t('web services under your control'); - $this->defaultLogoClaim = ''; - $this->defaultMailHeaderColor = '#1d2d44'; /* header color of mail notifications */ - - $themePath = OC::$SERVERROOT . '/themes/' . OC_Util::getTheme() . '/defaults.php'; - if (file_exists($themePath)) { - // prevent defaults.php from printing output - ob_start(); - require_once $themePath; - ob_end_clean(); - if (class_exists('OC_Theme')) { - $this->theme = new OC_Theme(); - } - } - } - - /** - * @param string $method - */ - private function themeExist($method) { - if (isset($this->theme) && method_exists($this->theme, $method)) { - return true; - } - return false; - } - - /** - * Returns the base URL - * @return string URL - */ - public function getBaseUrl() { - if ($this->themeExist('getBaseUrl')) { - return $this->theme->getBaseUrl(); - } else { - return $this->defaultBaseUrl; - } - } - - /** - * Returns the URL where the sync clients are listed - * @return string URL - */ - public function getSyncClientUrl() { - if ($this->themeExist('getSyncClientUrl')) { - return $this->theme->getSyncClientUrl(); - } else { - return $this->defaultSyncClientUrl; - } - } - - /** - * Returns the URL to the App Store for the iOS Client - * @return string URL - */ - public function getiOSClientUrl() { - if ($this->themeExist('getiOSClientUrl')) { - return $this->theme->getiOSClientUrl(); - } else { - return $this->defaultiOSClientUrl; - } - } - - /** - * Returns the AppId for the App Store for the iOS Client - * @return string AppId - */ - public function getiTunesAppId() { - if ($this->themeExist('getiTunesAppId')) { - return $this->theme->getiTunesAppId(); - } else { - return $this->defaultiTunesAppId; - } - } - - /** - * Returns the URL to Google Play for the Android Client - * @return string URL - */ - public function getAndroidClientUrl() { - if ($this->themeExist('getAndroidClientUrl')) { - return $this->theme->getAndroidClientUrl(); - } else { - return $this->defaultAndroidClientUrl; - } - } - - /** - * Returns the documentation URL - * @return string URL - */ - public function getDocBaseUrl() { - if ($this->themeExist('getDocBaseUrl')) { - return $this->theme->getDocBaseUrl(); - } else { - return $this->defaultDocBaseUrl; - } - } - - /** - * Returns the title - * @return string title - */ - public function getTitle() { - if ($this->themeExist('getTitle')) { - return $this->theme->getTitle(); - } else { - return $this->defaultTitle; - } - } - - /** - * Returns the short name of the software - * @return string title - */ - public function getName() { - if ($this->themeExist('getName')) { - return $this->theme->getName(); - } else { - return $this->defaultName; - } - } - - /** - * Returns the short name of the software containing HTML strings - * @return string title - */ - public function getHTMLName() { - if ($this->themeExist('getHTMLName')) { - return $this->theme->getHTMLName(); - } else { - return $this->defaultName; - } - } - - /** - * Returns entity (e.g. company name) - used for footer, copyright - * @return string entity name - */ - public function getEntity() { - if ($this->themeExist('getEntity')) { - return $this->theme->getEntity(); - } else { - return $this->defaultEntity; - } - } - - /** - * Returns slogan - * @return string slogan - */ - public function getSlogan() { - if ($this->themeExist('getSlogan')) { - return $this->theme->getSlogan(); - } else { - return $this->defaultSlogan; - } - } - - /** - * Returns logo claim - * @return string logo claim - */ - public function getLogoClaim() { - if ($this->themeExist('getLogoClaim')) { - return $this->theme->getLogoClaim(); - } else { - return $this->defaultLogoClaim; - } - } - - /** - * Returns short version of the footer - * @return string short footer - */ - public function getShortFooter() { - if ($this->themeExist('getShortFooter')) { - $footer = $this->theme->getShortFooter(); - } else { - $footer = '' .$this->getEntity() . ''. - ' – ' . $this->getSlogan(); - } - - return $footer; - } - - /** - * Returns long version of the footer - * @return string long footer - */ - public function getLongFooter() { - if ($this->themeExist('getLongFooter')) { - $footer = $this->theme->getLongFooter(); - } else { - $footer = $this->getShortFooter(); - } - - return $footer; - } - - /** - * @param string $key - */ - public function buildDocLinkToKey($key) { - if ($this->themeExist('buildDocLinkToKey')) { - return $this->theme->buildDocLinkToKey($key); - } - return $this->getDocBaseUrl() . '/server/' . $this->defaultDocVersion . '/go.php?to=' . $key; - } - - /** - * Returns mail header color - * @return string - */ - public function getMailHeaderColor() { - if ($this->themeExist('getMailHeaderColor')) { - return $this->theme->getMailHeaderColor(); - } else { - return $this->defaultMailHeaderColor; - } - } - -} diff --git a/lib/private/eventsource.php b/lib/private/eventsource.php deleted file mode 100644 index f567d1e6ca5..00000000000 --- a/lib/private/eventsource.php +++ /dev/null @@ -1,125 +0,0 @@ - - * @author Felix Moeller - * @author Lukas Reschke - * @author Morris Jobke - * @author Robin Appelman - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -/** - * wrapper for server side events (http://en.wikipedia.org/wiki/Server-sent_events) - * includes a fallback for older browsers and IE - * - * use server side events with caution, to many open requests can hang the server - */ -class OC_EventSource implements \OCP\IEventSource { - /** - * @var bool - */ - private $fallback; - - /** - * @var int - */ - private $fallBackId = 0; - - /** - * @var bool - */ - private $started = false; - - protected function init() { - if ($this->started) { - return; - } - $this->started = true; - - // prevent php output buffering, caching and nginx buffering - OC_Util::obEnd(); - header('Cache-Control: no-cache'); - header('X-Accel-Buffering: no'); - $this->fallback = isset($_GET['fallback']) and $_GET['fallback'] == 'true'; - if ($this->fallback) { - $this->fallBackId = (int)$_GET['fallback_id']; - /** - * FIXME: The default content-security-policy of ownCloud forbids inline - * JavaScript for security reasons. IE starting on Windows 10 will - * however also obey the CSP which will break the event source fallback. - * - * As a workaround thus we set a custom policy which allows the execution - * of inline JavaScript. - * - * @link https://github.com/owncloud/core/issues/14286 - */ - header("Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline'"); - header("Content-Type: text/html"); - echo str_repeat('' . PHP_EOL, 10); //dummy data to keep IE happy - } else { - header("Content-Type: text/event-stream"); - } - if (!(\OC::$server->getRequest()->passesCSRFCheck())) { - $this->send('error', 'Possible CSRF attack. Connection will be closed.'); - $this->close(); - exit(); - } - flush(); - } - - /** - * send a message to the client - * - * @param string $type - * @param mixed $data - * - * @throws \BadMethodCallException - * if only one parameter is given, a typeless message will be send with that parameter as data - */ - public function send($type, $data = null) { - if ($data and !preg_match('/^[A-Za-z0-9_]+$/', $type)) { - throw new BadMethodCallException('Type needs to be alphanumeric ('. $type .')'); - } - $this->init(); - if (is_null($data)) { - $data = $type; - $type = null; - } - if ($this->fallback) { - $response = '' . PHP_EOL; - echo $response; - } else { - if ($type) { - echo 'event: ' . $type . PHP_EOL; - } - echo 'data: ' . OCP\JSON::encode($data) . PHP_EOL; - } - echo PHP_EOL; - flush(); - } - - /** - * close the connection of the event source - */ - public function close() { - $this->send('__internal__', 'close'); //server side closing can be an issue, let the client do it - } -} diff --git a/lib/private/filechunking.php b/lib/private/filechunking.php deleted file mode 100644 index f2cef275458..00000000000 --- a/lib/private/filechunking.php +++ /dev/null @@ -1,175 +0,0 @@ - - * @author Felix Moeller - * @author Jörn Friedrich Dreyer - * @author Morris Jobke - * @author Robin Appelman - * @author Thomas Müller - * @author Thomas Tanghus - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - - -class OC_FileChunking { - protected $info; - protected $cache; - - static public function decodeName($name) { - preg_match('/(?P.*)-chunking-(?P\d+)-(?P\d+)-(?P\d+)/', $name, $matches); - return $matches; - } - - /** - * @param string[] $info - */ - public function __construct($info) { - $this->info = $info; - } - - public function getPrefix() { - $name = $this->info['name']; - $transferid = $this->info['transferid']; - - return $name.'-chunking-'.$transferid.'-'; - } - - protected function getCache() { - if (!isset($this->cache)) { - $this->cache = new \OC\Cache\File(); - } - return $this->cache; - } - - /** - * Stores the given $data under the given $key - the number of stored bytes is returned - * - * @param string $index - * @param resource $data - * @return int - */ - public function store($index, $data) { - $cache = $this->getCache(); - $name = $this->getPrefix().$index; - $cache->set($name, $data); - - return $cache->size($name); - } - - public function isComplete() { - $prefix = $this->getPrefix(); - $cache = $this->getCache(); - $chunkcount = (int)$this->info['chunkcount']; - - for($i=($chunkcount-1); $i >= 0; $i--) { - if (!$cache->hasKey($prefix.$i)) { - return false; - } - } - - return true; - } - - /** - * Assembles the chunks into the file specified by the path. - * Chunks are deleted afterwards. - * - * @param resource $f target path - * - * @return integer assembled file size - * - * @throws \OC\InsufficientStorageException when file could not be fully - * assembled due to lack of free space - */ - public function assemble($f) { - $cache = $this->getCache(); - $prefix = $this->getPrefix(); - $count = 0; - for ($i = 0; $i < $this->info['chunkcount']; $i++) { - $chunk = $cache->get($prefix.$i); - // remove after reading to directly save space - $cache->remove($prefix.$i); - $count += fwrite($f, $chunk); - // let php release the memory to work around memory exhausted error with php 5.6 - $chunk = null; - } - - return $count; - } - - /** - * Returns the size of the chunks already present - * @return integer size in bytes - */ - public function getCurrentSize() { - $cache = $this->getCache(); - $prefix = $this->getPrefix(); - $total = 0; - for ($i = 0; $i < $this->info['chunkcount']; $i++) { - $total += $cache->size($prefix.$i); - } - return $total; - } - - /** - * Removes all chunks which belong to this transmission - */ - public function cleanup() { - $cache = $this->getCache(); - $prefix = $this->getPrefix(); - for($i=0; $i < $this->info['chunkcount']; $i++) { - $cache->remove($prefix.$i); - } - } - - /** - * Removes one specific chunk - * @param string $index - */ - public function remove($index) { - $cache = $this->getCache(); - $prefix = $this->getPrefix(); - $cache->remove($prefix.$index); - } - - /** - * Assembles the chunks into the file specified by the path. - * Also triggers the relevant hooks and proxies. - * - * @param \OC\Files\Storage\Storage $storage storage - * @param string $path target path relative to the storage - * @return bool true on success or false if file could not be created - * - * @throws \OC\ServerNotAvailableException - */ - public function file_assemble($storage, $path) { - // use file_put_contents as method because that best matches what this function does - if (\OC\Files\Filesystem::isValidPath($path)) { - $target = $storage->fopen($path, 'w'); - if ($target) { - $count = $this->assemble($target); - fclose($target); - return $count > 0; - } else { - return false; - } - } - return false; - } -} diff --git a/lib/private/files.php b/lib/private/files.php deleted file mode 100644 index 9b6a1a4465f..00000000000 --- a/lib/private/files.php +++ /dev/null @@ -1,316 +0,0 @@ - - * @author Bart Visscher - * @author Björn Schießle - * @author Clark Tomlinson - * @author Frank Karlitschek - * @author Jakob Sack - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Michael Gapczynski - * @author Nicolai Ehemann - * @author Robin Appelman - * @author Robin McCorkell - * @author Thibaut GRIDEL - * @author Thomas Müller - * @author Victor Dubiniuk - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -use OC\Files\View; -use OC\Streamer; -use OCP\Lock\ILockingProvider; - -/** - * Class for file server access - * - */ -class OC_Files { - const FILE = 1; - const ZIP_FILES = 2; - const ZIP_DIR = 3; - - const UPLOAD_MIN_LIMIT_BYTES = 1048576; // 1 MiB - - /** - * @param string $filename - * @param string $name - */ - private static function sendHeaders($filename, $name) { - OC_Response::setContentDispositionHeader($name, 'attachment'); - header('Content-Transfer-Encoding: binary'); - OC_Response::disableCaching(); - $fileSize = \OC\Files\Filesystem::filesize($filename); - $type = \OC::$server->getMimeTypeDetector()->getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename)); - header('Content-Type: '.$type); - if ($fileSize > -1) { - OC_Response::setContentLengthHeader($fileSize); - } - } - - /** - * return the content of a file or return a zip file containing multiple files - * - * @param string $dir - * @param string $files ; separated list of files to download - * @param boolean $onlyHeader ; boolean to only send header of the request - */ - public static function get($dir, $files, $onlyHeader = false) { - - $view = \OC\Files\Filesystem::getView(); - $getType = self::FILE; - $filename = $dir; - try { - - if (is_array($files) && count($files) === 1) { - $files = $files[0]; - } - - if (!is_array($files)) { - $filename = $dir . '/' . $files; - if (!$view->is_dir($filename)) { - self::getSingleFile($view, $dir, $files, $onlyHeader); - return; - } - } - - $name = 'download'; - if (is_array($files)) { - $getType = self::ZIP_FILES; - $basename = basename($dir); - if ($basename) { - $name = $basename; - } - - $filename = $dir . '/' . $name; - } else { - $filename = $dir . '/' . $files; - $getType = self::ZIP_DIR; - // downloading root ? - if ($files !== '') { - $name = $files; - } - } - - $streamer = new Streamer(); - OC_Util::obEnd(); - - self::lockFiles($view, $dir, $files); - - $streamer->sendHeaders($name); - $executionTime = intval(OC::$server->getIniWrapper()->getNumeric('max_execution_time')); - set_time_limit(0); - if ($getType === self::ZIP_FILES) { - foreach ($files as $file) { - $file = $dir . '/' . $file; - if (\OC\Files\Filesystem::is_file($file)) { - $fileSize = \OC\Files\Filesystem::filesize($file); - $fh = \OC\Files\Filesystem::fopen($file, 'r'); - $streamer->addFileFromStream($fh, basename($file), $fileSize); - fclose($fh); - } elseif (\OC\Files\Filesystem::is_dir($file)) { - $streamer->addDirRecursive($file); - } - } - } elseif ($getType === self::ZIP_DIR) { - $file = $dir . '/' . $files; - $streamer->addDirRecursive($file); - } - $streamer->finalize(); - set_time_limit($executionTime); - self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); - } catch (\OCP\Lock\LockedException $ex) { - self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); - OC::$server->getLogger()->logException($ex); - $l = \OC::$server->getL10N('core'); - $hint = method_exists($ex, 'getHint') ? $ex->getHint() : ''; - \OC_Template::printErrorPage($l->t('File is currently busy, please try again later'), $hint); - } catch (\OCP\Files\ForbiddenException $ex) { - self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); - OC::$server->getLogger()->logException($ex); - $l = \OC::$server->getL10N('core'); - \OC_Template::printErrorPage($l->t('Can\'t read file'), $ex->getMessage()); - } catch (\Exception $ex) { - self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); - OC::$server->getLogger()->logException($ex); - $l = \OC::$server->getL10N('core'); - $hint = method_exists($ex, 'getHint') ? $ex->getHint() : ''; - \OC_Template::printErrorPage($l->t('Can\'t read file'), $hint); - } - } - - /** - * @param View $view - * @param string $name - * @param string $dir - * @param boolean $onlyHeader - */ - private static function getSingleFile($view, $dir, $name, $onlyHeader) { - $filename = $dir . '/' . $name; - OC_Util::obEnd(); - $view->lockFile($filename, ILockingProvider::LOCK_SHARED); - - if (\OC\Files\Filesystem::isReadable($filename)) { - self::sendHeaders($filename, $name); - } elseif (!\OC\Files\Filesystem::file_exists($filename)) { - header("HTTP/1.0 404 Not Found"); - $tmpl = new OC_Template('', '404', 'guest'); - $tmpl->printPage(); - exit(); - } else { - header("HTTP/1.0 403 Forbidden"); - die('403 Forbidden'); - } - if ($onlyHeader) { - return; - } - $view->readfile($filename); - } - - /** - * @param View $view - * @param string $dir - * @param string[]|string $files - */ - public static function lockFiles($view, $dir, $files) { - if (!is_array($files)) { - $file = $dir . '/' . $files; - $files = [$file]; - } - foreach ($files as $file) { - $file = $dir . '/' . $file; - $view->lockFile($file, ILockingProvider::LOCK_SHARED); - if ($view->is_dir($file)) { - $contents = $view->getDirectoryContent($file); - $contents = array_map(function($fileInfo) use ($file) { - /** @var \OCP\Files\FileInfo $fileInfo */ - return $file . '/' . $fileInfo->getName(); - }, $contents); - self::lockFiles($view, $dir, $contents); - } - } - } - - /** - * set the maximum upload size limit for apache hosts using .htaccess - * - * @param int $size file size in bytes - * @param array $files override '.htaccess' and '.user.ini' locations - * @return bool false on failure, size on success - */ - public static function setUploadLimit($size, $files = []) { - //don't allow user to break his config - $size = intval($size); - if ($size < self::UPLOAD_MIN_LIMIT_BYTES) { - return false; - } - $size = OC_Helper::phpFileSize($size); - - $phpValueKeys = array( - 'upload_max_filesize', - 'post_max_size' - ); - - // default locations if not overridden by $files - $files = array_merge([ - '.htaccess' => OC::$SERVERROOT . '/.htaccess', - '.user.ini' => OC::$SERVERROOT . '/.user.ini' - ], $files); - - $updateFiles = [ - $files['.htaccess'] => [ - 'pattern' => '/php_value %1$s (\S)*/', - 'setting' => 'php_value %1$s %2$s' - ], - $files['.user.ini'] => [ - 'pattern' => '/%1$s=(\S)*/', - 'setting' => '%1$s=%2$s' - ] - ]; - - $success = true; - - foreach ($updateFiles as $filename => $patternMap) { - // suppress warnings from fopen() - $handle = @fopen($filename, 'r+'); - if (!$handle) { - \OCP\Util::writeLog('files', - 'Can\'t write upload limit to ' . $filename . '. Please check the file permissions', - \OCP\Util::WARN); - $success = false; - continue; // try to update as many files as possible - } - - $content = ''; - while (!feof($handle)) { - $content .= fread($handle, 1000); - } - - foreach ($phpValueKeys as $key) { - $pattern = vsprintf($patternMap['pattern'], [$key]); - $setting = vsprintf($patternMap['setting'], [$key, $size]); - $hasReplaced = 0; - $newContent = preg_replace($pattern, $setting, $content, 2, $hasReplaced); - if ($newContent !== null) { - $content = $newContent; - } - if ($hasReplaced === 0) { - $content .= "\n" . $setting; - } - } - - // write file back - ftruncate($handle, 0); - rewind($handle); - fwrite($handle, $content); - - fclose($handle); - } - - if ($success) { - return OC_Helper::computerFileSize($size); - } - return false; - } - - /** - * @param string $dir - * @param $files - * @param integer $getType - * @param View $view - * @param string $filename - */ - private static function unlockAllTheFiles($dir, $files, $getType, $view, $filename) { - if ($getType === self::FILE) { - $view->unlockFile($filename, ILockingProvider::LOCK_SHARED); - } - if ($getType === self::ZIP_FILES) { - foreach ($files as $file) { - $file = $dir . '/' . $file; - $view->unlockFile($file, ILockingProvider::LOCK_SHARED); - } - } - if ($getType === self::ZIP_DIR) { - $file = $dir . '/' . $files; - $view->unlockFile($file, ILockingProvider::LOCK_SHARED); - } - } - -} diff --git a/lib/private/group.php b/lib/private/group.php deleted file mode 100644 index f1b84069a38..00000000000 --- a/lib/private/group.php +++ /dev/null @@ -1,300 +0,0 @@ - - * @author Bart Visscher - * @author Björn Schießle - * @author Georg Ehrke - * @author goodkiller - * @author Jakob Sack - * @author Lukas Reschke - * @author macjohnny - * @author Michael Gapczynski - * @author Morris Jobke - * @author Qingping Hou - * @author Robin Appelman - * @author Robin McCorkell - * @author Thomas Müller - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -/** - * This class provides all methods needed for managing groups. - * - * Note that &run is deprecated and won't work anymore. - * Hooks provided: - * pre_createGroup(&run, gid) - * post_createGroup(gid) - * pre_deleteGroup(&run, gid) - * post_deleteGroup(gid) - * pre_addToGroup(&run, uid, gid) - * post_addToGroup(uid, gid) - * pre_removeFromGroup(&run, uid, gid) - * post_removeFromGroup(uid, gid) - */ -class OC_Group { - - /** - * @return \OC\Group\Manager - * @deprecated Use \OC::$server->getGroupManager(); - */ - public static function getManager() { - return \OC::$server->getGroupManager(); - } - - /** - * @return \OC\User\Manager - * @deprecated Use \OC::$server->getUserManager() - */ - private static function getUserManager() { - return \OC::$server->getUserManager(); - } - - /** - * set the group backend - * @param \OC_Group_Backend $backend The backend to use for user management - * @return bool - */ - public static function useBackend($backend) { - self::getManager()->addBackend($backend); - return true; - } - - /** - * remove all used backends - */ - public static function clearBackends() { - self::getManager()->clearBackends(); - } - - /** - * Try to create a new group - * @param string $gid The name of the group to create - * @return bool - * - * Tries to create a new group. If the group name already exists, false will - * be returned. Basic checking of Group name - * @deprecated Use \OC::$server->getGroupManager()->createGroup() instead - */ - public static function createGroup($gid) { - if (self::getManager()->createGroup($gid)) { - return true; - } else { - return false; - } - } - - /** - * delete a group - * @param string $gid gid of the group to delete - * @return bool - * - * Deletes a group and removes it from the group_user-table - * @deprecated Use \OC::$server->getGroupManager()->delete() instead - */ - public static function deleteGroup($gid) { - $group = self::getManager()->get($gid); - if ($group) { - if ($group->delete()) { - return true; - } - } - return false; - } - - /** - * is user in group? - * @param string $uid uid of the user - * @param string $gid gid of the group - * @return bool - * - * Checks whether the user is member of a group or not. - * @deprecated Use \OC::$server->getGroupManager->inGroup($user); - */ - public static function inGroup($uid, $gid) { - $group = self::getManager()->get($gid); - $user = self::getUserManager()->get($uid); - if ($group and $user) { - return $group->inGroup($user); - } - return false; - } - - /** - * Add a user to a group - * @param string $uid Name of the user to add to group - * @param string $gid Name of the group in which add the user - * @return bool - * - * Adds a user to a group. - * @deprecated Use \OC::$server->getGroupManager->addUser(); - */ - public static function addToGroup($uid, $gid) { - $group = self::getManager()->get($gid); - $user = self::getUserManager()->get($uid); - if ($group and $user) { - $group->addUser($user); - return true; - } else { - return false; - } - } - - /** - * Removes a user from a group - * @param string $uid Name of the user to remove from group - * @param string $gid Name of the group from which remove the user - * @return bool - * - * removes the user from a group. - */ - public static function removeFromGroup($uid, $gid) { - $group = self::getManager()->get($gid); - $user = self::getUserManager()->get($uid); - if ($group and $user) { - OC_Hook::emit("OC_Group", "pre_removeFromGroup", array("run" => true, "uid" => $uid, "gid" => $gid)); - $group->removeUser($user); - OC_Hook::emit("OC_User", "post_removeFromGroup", array("uid" => $uid, "gid" => $gid)); - return true; - } else { - return false; - } - } - - /** - * Get all groups a user belongs to - * @param string $uid Name of the user - * @return array an array of group names - * - * This function fetches all groups a user belongs to. It does not check - * if the user exists at all. - * @deprecated Use \OC::$server->getGroupManager->getUserGroupIds($user) - */ - public static function getUserGroups($uid) { - $user = self::getUserManager()->get($uid); - if ($user) { - return self::getManager()->getUserGroupIds($user); - } else { - return array(); - } - } - - /** - * get a list of all groups - * @param string $search - * @param int|null $limit - * @param int|null $offset - * @return array an array of group names - * - * Returns a list with all groups - */ - public static function getGroups($search = '', $limit = null, $offset = null) { - $groups = self::getManager()->search($search, $limit, $offset); - $groupIds = array(); - foreach ($groups as $group) { - $groupIds[] = $group->getGID(); - } - return $groupIds; - } - - /** - * check if a group exists - * - * @param string $gid - * @return bool - * @deprecated Use \OC::$server->getGroupManager->groupExists($gid) - */ - public static function groupExists($gid) { - return self::getManager()->groupExists($gid); - } - - /** - * get a list of all users in a group - * @param string $gid - * @param string $search - * @param int $limit - * @param int $offset - * @return array an array of user ids - */ - public static function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { - $group = self::getManager()->get($gid); - if ($group) { - $users = $group->searchUsers($search, $limit, $offset); - $userIds = array(); - foreach ($users as $user) { - $userIds[] = $user->getUID(); - } - return $userIds; - } else { - return array(); - } - } - - /** - * get a list of all users in several groups - * @param string[] $gids - * @param string $search - * @param int $limit - * @param int $offset - * @return array an array of user ids - */ - public static function usersInGroups($gids, $search = '', $limit = -1, $offset = 0) { - $users = array(); - foreach ($gids as $gid) { - // TODO Need to apply limits to groups as total - $users = array_merge(array_diff(self::usersInGroup($gid, $search, $limit, $offset), $users), $users); - } - return $users; - } - - /** - * get a list of all display names in a group - * @param string $gid - * @param string $search - * @param int $limit - * @param int $offset - * @return array an array of display names (value) and user ids(key) - * @deprecated Use \OC::$server->getGroupManager->displayNamesInGroup($gid, $search, $limit, $offset) - */ - public static function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0) { - return self::getManager()->displayNamesInGroup($gid, $search, $limit, $offset); - } - - /** - * get a list of all display names in several groups - * @param array $gids - * @param string $search - * @param int $limit - * @param int $offset - * @return array an array of display names (Key) user ids (value) - */ - public static function displayNamesInGroups($gids, $search = '', $limit = -1, $offset = 0) { - $displayNames = array(); - foreach ($gids as $gid) { - // TODO Need to apply limits to groups as total - $diff = array_diff( - self::displayNamesInGroup($gid, $search, $limit, $offset), - $displayNames - ); - if ($diff) { - // A fix for LDAP users. array_merge loses keys... - $displayNames = $diff + $displayNames; - } - } - return $displayNames; - } -} diff --git a/lib/private/helper.php b/lib/private/helper.php deleted file mode 100644 index e6aaed0fd15..00000000000 --- a/lib/private/helper.php +++ /dev/null @@ -1,703 +0,0 @@ - - * @author Bart Visscher - * @author Björn Schießle - * @author Christopher Schäpers - * @author Clark Tomlinson - * @author Fabian Henze - * @author Felix Moeller - * @author Georg Ehrke - * @author Jakob Sack - * @author Jan-Christoph Borchardt - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Michael Gapczynski - * @author Morris Jobke - * @author Olivier Paroz - * @author Pellaeon Lin - * @author Robin Appelman - * @author Robin McCorkell - * @author Roeland Jago Douma - * @author Simon Könnecke - * @author Thomas Müller - * @author Thomas Tanghus - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -use Symfony\Component\Process\ExecutableFinder; - -/** - * Collection of useful functions - */ -class OC_Helper { - private static $templateManager; - - /** - * Creates an absolute url for public use - * @param string $service id - * @param bool $add_slash - * @return string the url - * - * Returns a absolute url to the given service. - */ - public static function linkToPublic($service, $add_slash = false) { - if ($service === 'files') { - $url = OC::$server->getURLGenerator()->getAbsoluteURL('/s'); - } else { - $url = OC::$server->getURLGenerator()->getAbsoluteURL(OC::$server->getURLGenerator()->linkTo('', 'public.php').'?service='.$service); - } - return $url . (($add_slash && $service[strlen($service) - 1] != '/') ? '/' : ''); - } - - /** - * get path to preview of file - * @param string $path path - * @return string the url - * - * Returns the path to the preview of the file. - */ - public static function previewIcon($path) { - return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_preview', ['x' => 32, 'y' => 32, 'file' => $path]); - } - - public static function publicPreviewIcon( $path, $token ) { - return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_public_preview', ['x' => 32, 'y' => 32, 'file' => $path, 't' => $token]); - } - - /** - * Make a human file size - * @param int $bytes file size in bytes - * @return string a human readable file size - * - * Makes 2048 to 2 kB. - */ - public static function humanFileSize($bytes) { - if ($bytes < 0) { - return "?"; - } - if ($bytes < 1024) { - return "$bytes B"; - } - $bytes = round($bytes / 1024, 0); - if ($bytes < 1024) { - return "$bytes KB"; - } - $bytes = round($bytes / 1024, 1); - if ($bytes < 1024) { - return "$bytes MB"; - } - $bytes = round($bytes / 1024, 1); - if ($bytes < 1024) { - return "$bytes GB"; - } - $bytes = round($bytes / 1024, 1); - if ($bytes < 1024) { - return "$bytes TB"; - } - - $bytes = round($bytes / 1024, 1); - return "$bytes PB"; - } - - /** - * Make a php file size - * @param int $bytes file size in bytes - * @return string a php parseable file size - * - * Makes 2048 to 2k and 2^41 to 2048G - */ - public static function phpFileSize($bytes) { - if ($bytes < 0) { - return "?"; - } - if ($bytes < 1024) { - return $bytes . "B"; - } - $bytes = round($bytes / 1024, 1); - if ($bytes < 1024) { - return $bytes . "K"; - } - $bytes = round($bytes / 1024, 1); - if ($bytes < 1024) { - return $bytes . "M"; - } - $bytes = round($bytes / 1024, 1); - return $bytes . "G"; - } - - /** - * Make a computer file size - * @param string $str file size in human readable format - * @return float a file size in bytes - * - * Makes 2kB to 2048. - * - * Inspired by: http://www.php.net/manual/en/function.filesize.php#92418 - */ - public static function computerFileSize($str) { - $str = strtolower($str); - if (is_numeric($str)) { - return floatval($str); - } - - $bytes_array = array( - 'b' => 1, - 'k' => 1024, - 'kb' => 1024, - 'mb' => 1024 * 1024, - 'm' => 1024 * 1024, - 'gb' => 1024 * 1024 * 1024, - 'g' => 1024 * 1024 * 1024, - 'tb' => 1024 * 1024 * 1024 * 1024, - 't' => 1024 * 1024 * 1024 * 1024, - 'pb' => 1024 * 1024 * 1024 * 1024 * 1024, - 'p' => 1024 * 1024 * 1024 * 1024 * 1024, - ); - - $bytes = floatval($str); - - if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && !empty($bytes_array[$matches[1]])) { - $bytes *= $bytes_array[$matches[1]]; - } else { - return false; - } - - $bytes = round($bytes); - - return $bytes; - } - - /** - * Recursive copying of folders - * @param string $src source folder - * @param string $dest target folder - * - */ - static function copyr($src, $dest) { - if (is_dir($src)) { - if (!is_dir($dest)) { - mkdir($dest); - } - $files = scandir($src); - foreach ($files as $file) { - if ($file != "." && $file != "..") { - self::copyr("$src/$file", "$dest/$file"); - } - } - } elseif (file_exists($src) && !\OC\Files\Filesystem::isFileBlacklisted($src)) { - copy($src, $dest); - } - } - - /** - * Recursive deletion of folders - * @param string $dir path to the folder - * @param bool $deleteSelf if set to false only the content of the folder will be deleted - * @return bool - */ - static function rmdirr($dir, $deleteSelf = true) { - if (is_dir($dir)) { - $files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), - RecursiveIteratorIterator::CHILD_FIRST - ); - - foreach ($files as $fileInfo) { - /** @var SplFileInfo $fileInfo */ - if ($fileInfo->isDir()) { - rmdir($fileInfo->getRealPath()); - } else { - unlink($fileInfo->getRealPath()); - } - } - if ($deleteSelf) { - rmdir($dir); - } - } elseif (file_exists($dir)) { - if ($deleteSelf) { - unlink($dir); - } - } - if (!$deleteSelf) { - return true; - } - - return !file_exists($dir); - } - - /** - * @return \OC\Files\Type\TemplateManager - */ - static public function getFileTemplateManager() { - if (!self::$templateManager) { - self::$templateManager = new \OC\Files\Type\TemplateManager(); - } - return self::$templateManager; - } - - /** - * detect if a given program is found in the search PATH - * - * @param string $name - * @param bool $path - * @internal param string $program name - * @internal param string $optional search path, defaults to $PATH - * @return bool true if executable program found in path - */ - public static function canExecute($name, $path = false) { - // path defaults to PATH from environment if not set - if ($path === false) { - $path = getenv("PATH"); - } - // check method depends on operating system - if (!strncmp(PHP_OS, "WIN", 3)) { - // on Windows an appropriate COM or EXE file needs to exist - $exts = array(".exe", ".com"); - $check_fn = "file_exists"; - } else { - // anywhere else we look for an executable file of that name - $exts = array(""); - $check_fn = "is_executable"; - } - // Default check will be done with $path directories : - $dirs = explode(PATH_SEPARATOR, $path); - // WARNING : We have to check if open_basedir is enabled : - $obd = OC::$server->getIniWrapper()->getString('open_basedir'); - if ($obd != "none") { - $obd_values = explode(PATH_SEPARATOR, $obd); - if (count($obd_values) > 0 and $obd_values[0]) { - // open_basedir is in effect ! - // We need to check if the program is in one of these dirs : - $dirs = $obd_values; - } - } - foreach ($dirs as $dir) { - foreach ($exts as $ext) { - if ($check_fn("$dir/$name" . $ext)) - return true; - } - } - return false; - } - - /** - * copy the contents of one stream to another - * - * @param resource $source - * @param resource $target - * @return array the number of bytes copied and result - */ - public static function streamCopy($source, $target) { - if (!$source or !$target) { - return array(0, false); - } - $bufSize = 8192; - $result = true; - $count = 0; - while (!feof($source)) { - $buf = fread($source, $bufSize); - $bytesWritten = fwrite($target, $buf); - if ($bytesWritten !== false) { - $count += $bytesWritten; - } - // note: strlen is expensive so only use it when necessary, - // on the last block - if ($bytesWritten === false - || ($bytesWritten < $bufSize && $bytesWritten < strlen($buf)) - ) { - // write error, could be disk full ? - $result = false; - break; - } - } - return array($count, $result); - } - - /** - * Adds a suffix to the name in case the file exists - * - * @param string $path - * @param string $filename - * @return string - */ - public static function buildNotExistingFileName($path, $filename) { - $view = \OC\Files\Filesystem::getView(); - return self::buildNotExistingFileNameForView($path, $filename, $view); - } - - /** - * Adds a suffix to the name in case the file exists - * - * @param string $path - * @param string $filename - * @return string - */ - public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) { - if ($path === '/') { - $path = ''; - } - if ($pos = strrpos($filename, '.')) { - $name = substr($filename, 0, $pos); - $ext = substr($filename, $pos); - } else { - $name = $filename; - $ext = ''; - } - - $newpath = $path . '/' . $filename; - if ($view->file_exists($newpath)) { - if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) { - //Replace the last "(number)" with "(number+1)" - $last_match = count($matches[0]) - 1; - $counter = $matches[1][$last_match][0] + 1; - $offset = $matches[0][$last_match][1]; - $match_length = strlen($matches[0][$last_match][0]); - } else { - $counter = 2; - $match_length = 0; - $offset = false; - } - do { - if ($offset) { - //Replace the last "(number)" with "(number+1)" - $newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length); - } else { - $newname = $name . ' (' . $counter . ')'; - } - $newpath = $path . '/' . $newname . $ext; - $counter++; - } while ($view->file_exists($newpath)); - } - - return $newpath; - } - - /** - * Checks if $sub is a subdirectory of $parent - * - * @param string $sub - * @param string $parent - * @return bool - */ - public static function isSubDirectory($sub, $parent) { - $realpathSub = realpath($sub); - $realpathParent = realpath($parent); - - // realpath() may return false in case the directory does not exist - // since we can not be sure how different PHP versions may behave here - // we do an additional check whether realpath returned false - if($realpathSub === false || $realpathParent === false) { - return false; - } - - // Check whether $sub is a subdirectory of $parent - if (strpos($realpathSub, $realpathParent) === 0) { - return true; - } - - return false; - } - - /** - * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is. - * - * @param array $input The array to work on - * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default) - * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8 - * @return array - * - * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is. - * based on http://www.php.net/manual/en/function.array-change-key-case.php#107715 - * - */ - public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') { - $case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER; - $ret = array(); - foreach ($input as $k => $v) { - $ret[mb_convert_case($k, $case, $encoding)] = $v; - } - return $ret; - } - - /** - * performs a search in a nested array - * @param array $haystack the array to be searched - * @param string $needle the search string - * @param string $index optional, only search this key name - * @return mixed the key of the matching field, otherwise false - * - * performs a search in a nested array - * - * taken from http://www.php.net/manual/en/function.array-search.php#97645 - */ - public static function recursiveArraySearch($haystack, $needle, $index = null) { - $aIt = new RecursiveArrayIterator($haystack); - $it = new RecursiveIteratorIterator($aIt); - - while ($it->valid()) { - if (((isset($index) AND ($it->key() == $index)) OR (!isset($index))) AND ($it->current() == $needle)) { - return $aIt->key(); - } - - $it->next(); - } - - return false; - } - - /** - * Shortens str to maxlen by replacing characters in the middle with '...', eg. - * ellipsis('a very long string with lots of useless info to make a better example', 14) becomes 'a very ...example' - * - * @param string $str the string - * @param string $maxlen the maximum length of the result - * @return string with at most maxlen characters - */ - public static function ellipsis($str, $maxlen) { - if (strlen($str) > $maxlen) { - $characters = floor($maxlen / 2); - return substr($str, 0, $characters) . '...' . substr($str, -1 * $characters); - } - return $str; - } - - /** - * calculates the maximum upload size respecting system settings, free space and user quota - * - * @param string $dir the current folder where the user currently operates - * @param int $freeSpace the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly - * @return int number of bytes representing - */ - public static function maxUploadFilesize($dir, $freeSpace = null) { - if (is_null($freeSpace) || $freeSpace < 0){ - $freeSpace = self::freeSpace($dir); - } - return min($freeSpace, self::uploadLimit()); - } - - /** - * Calculate free space left within user quota - * - * @param string $dir the current folder where the user currently operates - * @return int number of bytes representing - */ - public static function freeSpace($dir) { - $freeSpace = \OC\Files\Filesystem::free_space($dir); - if ($freeSpace !== \OCP\Files\FileInfo::SPACE_UNKNOWN) { - $freeSpace = max($freeSpace, 0); - return $freeSpace; - } else { - return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188 - } - } - - /** - * Calculate PHP upload limit - * - * @return int PHP upload file size limit - */ - public static function uploadLimit() { - $ini = \OC::$server->getIniWrapper(); - $upload_max_filesize = OCP\Util::computerFileSize($ini->get('upload_max_filesize')); - $post_max_size = OCP\Util::computerFileSize($ini->get('post_max_size')); - if ((int)$upload_max_filesize === 0 and (int)$post_max_size === 0) { - return INF; - } elseif ((int)$upload_max_filesize === 0 or (int)$post_max_size === 0) { - return max($upload_max_filesize, $post_max_size); //only the non 0 value counts - } else { - return min($upload_max_filesize, $post_max_size); - } - } - - /** - * Checks if a function is available - * - * @param string $function_name - * @return bool - */ - public static function is_function_enabled($function_name) { - if (!function_exists($function_name)) { - return false; - } - $ini = \OC::$server->getIniWrapper(); - $disabled = explode(',', $ini->get('disable_functions')); - $disabled = array_map('trim', $disabled); - if (in_array($function_name, $disabled)) { - return false; - } - $disabled = explode(',', $ini->get('suhosin.executor.func.blacklist')); - $disabled = array_map('trim', $disabled); - if (in_array($function_name, $disabled)) { - return false; - } - return true; - } - - /** - * Try to find a program - * Note: currently windows is not supported - * - * @param string $program - * @return null|string - */ - public static function findBinaryPath($program) { - $memcache = \OC::$server->getMemCacheFactory()->create('findBinaryPath'); - if ($memcache->hasKey($program)) { - return $memcache->get($program); - } - $result = null; - if (!\OC_Util::runningOnWindows() && self::is_function_enabled('exec')) { - $exeSniffer = new ExecutableFinder(); - // Returns null if nothing is found - $result = $exeSniffer->find($program); - if (empty($result)) { - $paths = getenv('PATH'); - if (empty($paths)) { - $paths = '/usr/local/bin /usr/bin /opt/bin /bin'; - } else { - $paths = str_replace(':',' ',getenv('PATH')); - } - $command = 'find ' . $paths . ' -name ' . escapeshellarg($program) . ' 2> /dev/null'; - exec($command, $output, $returnCode); - if (count($output) > 0) { - $result = escapeshellcmd($output[0]); - } - } - } - // store the value for 5 minutes - $memcache->set($program, $result, 300); - return $result; - } - - /** - * Calculate the disc space for the given path - * - * @param string $path - * @param \OCP\Files\FileInfo $rootInfo (optional) - * @return array - * @throws \OCP\Files\NotFoundException - */ - public static function getStorageInfo($path, $rootInfo = null) { - // return storage info without adding mount points - $includeExtStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false); - - if (!$rootInfo) { - $rootInfo = \OC\Files\Filesystem::getFileInfo($path, false); - } - if (!$rootInfo instanceof \OCP\Files\FileInfo) { - throw new \OCP\Files\NotFoundException(); - } - $used = $rootInfo->getSize(); - if ($used < 0) { - $used = 0; - } - $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED; - $storage = $rootInfo->getStorage(); - $sourceStorage = $storage; - if ($storage->instanceOfStorage('\OC\Files\Storage\Shared')) { - $includeExtStorage = false; - $sourceStorage = $storage->getSourceStorage(); - } - if ($includeExtStorage) { - $quota = OC_Util::getUserQuota(\OCP\User::getUser()); - if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { - // always get free space / total space from root + mount points - return self::getGlobalStorageInfo(); - } - } - - // TODO: need a better way to get total space from storage - if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) { - /** @var \OC\Files\Storage\Wrapper\Quota $storage */ - $quota = $sourceStorage->getQuota(); - } - $free = $sourceStorage->free_space(''); - if ($free >= 0) { - $total = $free + $used; - } else { - $total = $free; //either unknown or unlimited - } - if ($total > 0) { - if ($quota > 0 && $total > $quota) { - $total = $quota; - } - // prevent division by zero or error codes (negative values) - $relative = round(($used / $total) * 10000) / 100; - } else { - $relative = 0; - } - - $ownerId = $storage->getOwner($path); - $ownerDisplayName = ''; - $owner = \OC::$server->getUserManager()->get($ownerId); - if($owner) { - $ownerDisplayName = $owner->getDisplayName(); - } - - return [ - 'free' => $free, - 'used' => $used, - 'quota' => $quota, - 'total' => $total, - 'relative' => $relative, - 'owner' => $ownerId, - 'ownerDisplayName' => $ownerDisplayName, - ]; - } - - /** - * Get storage info including all mount points and quota - * - * @return array - */ - private static function getGlobalStorageInfo() { - $quota = OC_Util::getUserQuota(\OCP\User::getUser()); - - $rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext'); - $used = $rootInfo['size']; - if ($used < 0) { - $used = 0; - } - - $total = $quota; - $free = $quota - $used; - - if ($total > 0) { - if ($quota > 0 && $total > $quota) { - $total = $quota; - } - // prevent division by zero or error codes (negative values) - $relative = round(($used / $total) * 10000) / 100; - } else { - $relative = 0; - } - - return array('free' => $free, 'used' => $used, 'total' => $total, 'relative' => $relative); - - } - - /** - * Returns whether the config file is set manually to read-only - * @return bool - */ - public static function isReadOnlyConfigEnabled() { - return \OC::$server->getConfig()->getSystemValue('config_is_read_only', false); - } -} diff --git a/lib/private/hook.php b/lib/private/hook.php deleted file mode 100644 index 76f3b657eb9..00000000000 --- a/lib/private/hook.php +++ /dev/null @@ -1,145 +0,0 @@ - - * @author Jakob Sack - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Sam Tuke - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -class OC_Hook{ - public static $thrownExceptions = []; - - static private $registered = array(); - - /** - * connects a function to a hook - * - * @param string $signalClass class name of emitter - * @param string $signalName name of signal - * @param string|object $slotClass class name of slot - * @param string $slotName name of slot - * @return bool - * - * This function makes it very easy to connect to use hooks. - * - * TODO: write example - */ - static public function connect($signalClass, $signalName, $slotClass, $slotName ) { - // If we're trying to connect to an emitting class that isn't - // yet registered, register it - if( !array_key_exists($signalClass, self::$registered )) { - self::$registered[$signalClass] = array(); - } - // If we're trying to connect to an emitting method that isn't - // yet registered, register it with the emitting class - if( !array_key_exists( $signalName, self::$registered[$signalClass] )) { - self::$registered[$signalClass][$signalName] = array(); - } - - // don't connect hooks twice - foreach (self::$registered[$signalClass][$signalName] as $hook) { - if ($hook['class'] === $slotClass and $hook['name'] === $slotName) { - return false; - } - } - // Connect the hook handler to the requested emitter - self::$registered[$signalClass][$signalName][] = array( - "class" => $slotClass, - "name" => $slotName - ); - - // No chance for failure ;-) - return true; - } - - /** - * emits a signal - * - * @param string $signalClass class name of emitter - * @param string $signalName name of signal - * @param mixed $params default: array() array with additional data - * @return bool true if slots exists or false if not - * @throws \OC\HintException - * @throws \OC\ServerNotAvailableException Emits a signal. To get data from the slot use references! - * - * TODO: write example - */ - static public function emit($signalClass, $signalName, $params = []) { - - // Return false if no hook handlers are listening to this - // emitting class - if( !array_key_exists($signalClass, self::$registered )) { - return false; - } - - // Return false if no hook handlers are listening to this - // emitting method - if( !array_key_exists( $signalName, self::$registered[$signalClass] )) { - return false; - } - - // Call all slots - foreach( self::$registered[$signalClass][$signalName] as $i ) { - try { - call_user_func( array( $i["class"], $i["name"] ), $params ); - } catch (Exception $e){ - self::$thrownExceptions[] = $e; - \OC::$server->getLogger()->logException($e); - if($e instanceof \OC\HintException) { - throw $e; - } - if($e instanceof \OC\ServerNotAvailableException) { - throw $e; - } - } - } - - return true; - } - - /** - * clear hooks - * @param string $signalClass - * @param string $signalName - */ - static public function clear($signalClass='', $signalName='') { - if ($signalClass) { - if ($signalName) { - self::$registered[$signalClass][$signalName]=array(); - }else{ - self::$registered[$signalClass]=array(); - } - }else{ - self::$registered=array(); - } - } - - /** - * DO NOT USE! - * For unit tests ONLY! - */ - static public function getHooks() { - return self::$registered; - } -} diff --git a/lib/private/image.php b/lib/private/image.php deleted file mode 100644 index a97347df623..00000000000 --- a/lib/private/image.php +++ /dev/null @@ -1,1147 +0,0 @@ - - * @author Bart Visscher - * @author Bartek Przybylski - * @author Björn Schießle - * @author Byron Marohn - * @author Christopher Schäpers - * @author Georg Ehrke - * @author j-ed - * @author Joas Schilling - * @author Johannes Willnecker - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Morris Jobke - * @author Olivier Paroz - * @author Robin Appelman - * @author Thomas Müller - * @author Thomas Tanghus - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -/** - * Class for basic image manipulation - */ -class OC_Image implements \OCP\IImage { - /** @var false|resource */ - protected $resource = false; // tmp resource. - /** @var int */ - protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident. - /** @var string */ - protected $mimeType = 'image/png'; // Default to png - /** @var int */ - protected $bitDepth = 24; - /** @var null|string */ - protected $filePath = null; - /** @var finfo */ - private $fileInfo; - /** @var \OCP\ILogger */ - private $logger; - - /** - * Get mime type for an image file. - * - * @param string|null $filePath The path to a local image file. - * @return string The mime type if the it could be determined, otherwise an empty string. - */ - static public function getMimeTypeForFile($filePath) { - // exif_imagetype throws "read error!" if file is less than 12 byte - if ($filePath !== null && filesize($filePath) > 11) { - $imageType = exif_imagetype($filePath); - } else { - $imageType = false; - } - return $imageType ? image_type_to_mime_type($imageType) : ''; - } - - /** - * Constructor. - * - * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by - * an imagecreate* function. - * @param \OCP\ILogger $logger - */ - public function __construct($imageRef = null, $logger = null) { - $this->logger = $logger; - if (is_null($logger)) { - $this->logger = \OC::$server->getLogger(); - } - - if (!extension_loaded('gd') || !function_exists('gd_info')) { - $this->logger->error(__METHOD__ . '(): GD module not installed', array('app' => 'core')); - return false; - } - - if (\OC_Util::fileInfoLoaded()) { - $this->fileInfo = new finfo(FILEINFO_MIME_TYPE); - } - - if (!is_null($imageRef)) { - $this->load($imageRef); - } - } - - /** - * Determine whether the object contains an image resource. - * - * @return bool - */ - public function valid() { // apparently you can't name a method 'empty'... - return is_resource($this->resource); - } - - /** - * Returns the MIME type of the image or an empty string if no image is loaded. - * - * @return string - */ - public function mimeType() { - return $this->valid() ? $this->mimeType : ''; - } - - /** - * Returns the width of the image or -1 if no image is loaded. - * - * @return int - */ - public function width() { - return $this->valid() ? imagesx($this->resource) : -1; - } - - /** - * Returns the height of the image or -1 if no image is loaded. - * - * @return int - */ - public function height() { - return $this->valid() ? imagesy($this->resource) : -1; - } - - /** - * Returns the width when the image orientation is top-left. - * - * @return int - */ - public function widthTopLeft() { - $o = $this->getOrientation(); - $this->logger->debug('OC_Image->widthTopLeft() Orientation: ' . $o, array('app' => 'core')); - switch ($o) { - case -1: - case 1: - case 2: // Not tested - case 3: - case 4: // Not tested - return $this->width(); - case 5: // Not tested - case 6: - case 7: // Not tested - case 8: - return $this->height(); - } - return $this->width(); - } - - /** - * Returns the height when the image orientation is top-left. - * - * @return int - */ - public function heightTopLeft() { - $o = $this->getOrientation(); - $this->logger->debug('OC_Image->heightTopLeft() Orientation: ' . $o, array('app' => 'core')); - switch ($o) { - case -1: - case 1: - case 2: // Not tested - case 3: - case 4: // Not tested - return $this->height(); - case 5: // Not tested - case 6: - case 7: // Not tested - case 8: - return $this->width(); - } - return $this->height(); - } - - /** - * Outputs the image. - * - * @param string $mimeType - * @return bool - */ - public function show($mimeType = null) { - if ($mimeType === null) { - $mimeType = $this->mimeType(); - } - header('Content-Type: ' . $mimeType); - return $this->_output(null, $mimeType); - } - - /** - * Saves the image. - * - * @param string $filePath - * @param string $mimeType - * @return bool - */ - - public function save($filePath = null, $mimeType = null) { - if ($mimeType === null) { - $mimeType = $this->mimeType(); - } - if ($filePath === null && $this->filePath === null) { - $this->logger->error(__METHOD__ . '(): called with no path.', array('app' => 'core')); - return false; - } elseif ($filePath === null && $this->filePath !== null) { - $filePath = $this->filePath; - } - return $this->_output($filePath, $mimeType); - } - - /** - * Outputs/saves the image. - * - * @param string $filePath - * @param string $mimeType - * @return bool - * @throws Exception - */ - private function _output($filePath = null, $mimeType = null) { - if ($filePath) { - if (!file_exists(dirname($filePath))) - mkdir(dirname($filePath), 0777, true); - if (!is_writable(dirname($filePath))) { - $this->logger->error(__METHOD__ . '(): Directory \'' . dirname($filePath) . '\' is not writable.', array('app' => 'core')); - return false; - } elseif (is_writable(dirname($filePath)) && file_exists($filePath) && !is_writable($filePath)) { - $this->logger->error(__METHOD__ . '(): File \'' . $filePath . '\' is not writable.', array('app' => 'core')); - return false; - } - } - if (!$this->valid()) { - return false; - } - - $imageType = $this->imageType; - if ($mimeType !== null) { - switch ($mimeType) { - case 'image/gif': - $imageType = IMAGETYPE_GIF; - break; - case 'image/jpeg': - $imageType = IMAGETYPE_JPEG; - break; - case 'image/png': - $imageType = IMAGETYPE_PNG; - break; - case 'image/x-xbitmap': - $imageType = IMAGETYPE_XBM; - break; - case 'image/bmp': - case 'image/x-ms-bmp': - $imageType = IMAGETYPE_BMP; - break; - default: - throw new Exception('\OC_Image::_output(): "' . $mimeType . '" is not supported when forcing a specific output format'); - } - } - - switch ($imageType) { - case IMAGETYPE_GIF: - $retVal = imagegif($this->resource, $filePath); - break; - case IMAGETYPE_JPEG: - $retVal = imagejpeg($this->resource, $filePath); - break; - case IMAGETYPE_PNG: - $retVal = imagepng($this->resource, $filePath); - break; - case IMAGETYPE_XBM: - if (function_exists('imagexbm')) { - $retVal = imagexbm($this->resource, $filePath); - } else { - throw new Exception('\OC_Image::_output(): imagexbm() is not supported.'); - } - - break; - case IMAGETYPE_WBMP: - $retVal = imagewbmp($this->resource, $filePath); - break; - case IMAGETYPE_BMP: - $retVal = imagebmp($this->resource, $filePath, $this->bitDepth); - break; - default: - $retVal = imagepng($this->resource, $filePath); - } - return $retVal; - } - - /** - * Prints the image when called as $image(). - */ - public function __invoke() { - return $this->show(); - } - - /** - * @return resource Returns the image resource in any. - */ - public function resource() { - return $this->resource; - } - - /** - * @return null|string Returns the raw image data. - */ - public function data() { - if (!$this->valid()) { - return null; - } - ob_start(); - switch ($this->mimeType) { - case "image/png": - $res = imagepng($this->resource); - break; - case "image/jpeg": - $res = imagejpeg($this->resource); - break; - case "image/gif": - $res = imagegif($this->resource); - break; - default: - $res = imagepng($this->resource); - $this->logger->info('OC_Image->data. Could not guess mime-type, defaulting to png', array('app' => 'core')); - break; - } - if (!$res) { - $this->logger->error('OC_Image->data. Error getting image data.', array('app' => 'core')); - } - return ob_get_clean(); - } - - /** - * @return string - base64 encoded, which is suitable for embedding in a VCard. - */ - function __toString() { - return base64_encode($this->data()); - } - - /** - * (I'm open for suggestions on better method name ;) - * Get the orientation based on EXIF data. - * - * @return int The orientation or -1 if no EXIF data is available. - */ - public function getOrientation() { - if ($this->imageType !== IMAGETYPE_JPEG) { - $this->logger->debug('OC_Image->fixOrientation() Image is not a JPEG.', array('app' => 'core')); - return -1; - } - if (!is_callable('exif_read_data')) { - $this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', array('app' => 'core')); - return -1; - } - if (!$this->valid()) { - $this->logger->debug('OC_Image->fixOrientation() No image loaded.', array('app' => 'core')); - return -1; - } - if (is_null($this->filePath) || !is_readable($this->filePath)) { - $this->logger->debug('OC_Image->fixOrientation() No readable file path set.', array('app' => 'core')); - return -1; - } - $exif = @exif_read_data($this->filePath, 'IFD0'); - if (!$exif) { - return -1; - } - if (!isset($exif['Orientation'])) { - return -1; - } - return $exif['Orientation']; - } - - /** - * (I'm open for suggestions on better method name ;) - * Fixes orientation based on EXIF data. - * - * @return bool. - */ - public function fixOrientation() { - $o = $this->getOrientation(); - $this->logger->debug('OC_Image->fixOrientation() Orientation: ' . $o, array('app' => 'core')); - $rotate = 0; - $flip = false; - switch ($o) { - case -1: - return false; //Nothing to fix - case 1: - $rotate = 0; - break; - case 2: - $rotate = 0; - $flip = true; - break; - case 3: - $rotate = 180; - break; - case 4: - $rotate = 180; - $flip = true; - break; - case 5: - $rotate = 90; - $flip = true; - break; - case 6: - $rotate = 270; - break; - case 7: - $rotate = 270; - $flip = true; - break; - case 8: - $rotate = 90; - break; - } - if($flip && function_exists('imageflip')) { - imageflip($this->resource, IMG_FLIP_HORIZONTAL); - } - if ($rotate) { - $res = imagerotate($this->resource, $rotate, 0); - if ($res) { - if (imagealphablending($res, true)) { - if (imagesavealpha($res, true)) { - imagedestroy($this->resource); - $this->resource = $res; - return true; - } else { - $this->logger->debug('OC_Image->fixOrientation() Error during alpha-saving', array('app' => 'core')); - return false; - } - } else { - $this->logger->debug('OC_Image->fixOrientation() Error during alpha-blending', array('app' => 'core')); - return false; - } - } else { - $this->logger->debug('OC_Image->fixOrientation() Error during orientation fixing', array('app' => 'core')); - return false; - } - } - return false; - } - - /** - * Loads an image from a local file, a base64 encoded string or a resource created by an imagecreate* function. - * - * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by an imagecreate* function or a file resource (file handle ). - * @return resource|false An image resource or false on error - */ - public function load($imageRef) { - if (is_resource($imageRef)) { - if (get_resource_type($imageRef) == 'gd') { - $this->resource = $imageRef; - return $this->resource; - } elseif (in_array(get_resource_type($imageRef), array('file', 'stream'))) { - return $this->loadFromFileHandle($imageRef); - } - } elseif ($this->loadFromBase64($imageRef) !== false) { - return $this->resource; - } elseif ($this->loadFromFile($imageRef) !== false) { - return $this->resource; - } elseif ($this->loadFromData($imageRef) !== false) { - return $this->resource; - } - $this->logger->debug(__METHOD__ . '(): could not load anything. Giving up!', array('app' => 'core')); - return false; - } - - /** - * Loads an image from an open file handle. - * It is the responsibility of the caller to position the pointer at the correct place and to close the handle again. - * - * @param resource $handle - * @return resource|false An image resource or false on error - */ - public function loadFromFileHandle($handle) { - $contents = stream_get_contents($handle); - if ($this->loadFromData($contents)) { - return $this->resource; - } - return false; - } - - /** - * Loads an image from a local file. - * - * @param bool|string $imagePath The path to a local file. - * @return bool|resource An image resource or false on error - */ - public function loadFromFile($imagePath = false) { - // exif_imagetype throws "read error!" if file is less than 12 byte - if (!@is_file($imagePath) || !file_exists($imagePath) || filesize($imagePath) < 12 || !is_readable($imagePath)) { - return false; - } - $iType = exif_imagetype($imagePath); - switch ($iType) { - case IMAGETYPE_GIF: - if (imagetypes() & IMG_GIF) { - $this->resource = imagecreatefromgif($imagePath); - // Preserve transparency - imagealphablending($this->resource, true); - imagesavealpha($this->resource, true); - } else { - $this->logger->debug('OC_Image->loadFromFile, GIF images not supported: ' . $imagePath, array('app' => 'core')); - } - break; - case IMAGETYPE_JPEG: - if (imagetypes() & IMG_JPG) { - $this->resource = imagecreatefromjpeg($imagePath); - } else { - $this->logger->debug('OC_Image->loadFromFile, JPG images not supported: ' . $imagePath, array('app' => 'core')); - } - break; - case IMAGETYPE_PNG: - if (imagetypes() & IMG_PNG) { - $this->resource = imagecreatefrompng($imagePath); - // Preserve transparency - imagealphablending($this->resource, true); - imagesavealpha($this->resource, true); - } else { - $this->logger->debug('OC_Image->loadFromFile, PNG images not supported: ' . $imagePath, array('app' => 'core')); - } - break; - case IMAGETYPE_XBM: - if (imagetypes() & IMG_XPM) { - $this->resource = imagecreatefromxbm($imagePath); - } else { - $this->logger->debug('OC_Image->loadFromFile, XBM/XPM images not supported: ' . $imagePath, array('app' => 'core')); - } - break; - case IMAGETYPE_WBMP: - if (imagetypes() & IMG_WBMP) { - $this->resource = imagecreatefromwbmp($imagePath); - } else { - $this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, array('app' => 'core')); - } - break; - case IMAGETYPE_BMP: - $this->resource = $this->imagecreatefrombmp($imagePath); - break; - /* - case IMAGETYPE_TIFF_II: // (intel byte order) - break; - case IMAGETYPE_TIFF_MM: // (motorola byte order) - break; - case IMAGETYPE_JPC: - break; - case IMAGETYPE_JP2: - break; - case IMAGETYPE_JPX: - break; - case IMAGETYPE_JB2: - break; - case IMAGETYPE_SWC: - break; - case IMAGETYPE_IFF: - break; - case IMAGETYPE_ICO: - break; - case IMAGETYPE_SWF: - break; - case IMAGETYPE_PSD: - break; - */ - default: - - // this is mostly file created from encrypted file - $this->resource = imagecreatefromstring(\OC\Files\Filesystem::file_get_contents(\OC\Files\Filesystem::getLocalPath($imagePath))); - $iType = IMAGETYPE_PNG; - $this->logger->debug('OC_Image->loadFromFile, Default', array('app' => 'core')); - break; - } - if ($this->valid()) { - $this->imageType = $iType; - $this->mimeType = image_type_to_mime_type($iType); - $this->filePath = $imagePath; - } - return $this->resource; - } - - /** - * Loads an image from a string of data. - * - * @param string $str A string of image data as read from a file. - * @return bool|resource An image resource or false on error - */ - public function loadFromData($str) { - if (is_resource($str)) { - return false; - } - $this->resource = @imagecreatefromstring($str); - if ($this->fileInfo) { - $this->mimeType = $this->fileInfo->buffer($str); - } - if (is_resource($this->resource)) { - imagealphablending($this->resource, false); - imagesavealpha($this->resource, true); - } - - if (!$this->resource) { - $this->logger->debug('OC_Image->loadFromFile, could not load', array('app' => 'core')); - return false; - } - return $this->resource; - } - - /** - * Loads an image from a base64 encoded string. - * - * @param string $str A string base64 encoded string of image data. - * @return bool|resource An image resource or false on error - */ - public function loadFromBase64($str) { - if (!is_string($str)) { - return false; - } - $data = base64_decode($str); - if ($data) { // try to load from string data - $this->resource = @imagecreatefromstring($data); - if ($this->fileInfo) { - $this->mimeType = $this->fileInfo->buffer($data); - } - if (!$this->resource) { - $this->logger->debug('OC_Image->loadFromBase64, could not load', array('app' => 'core')); - return false; - } - return $this->resource; - } else { - return false; - } - } - - /** - * Create a new image from file or URL - * - * @link http://www.programmierer-forum.de/function-imagecreatefrombmp-laeuft-mit-allen-bitraten-t143137.htm - * @version 1.00 - * @param string $fileName

- * Path to the BMP image. - *

- * @return bool|resource an image resource identifier on success, FALSE on errors. - */ - private function imagecreatefrombmp($fileName) { - if (!($fh = fopen($fileName, 'rb'))) { - $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName, array('app' => 'core')); - return false; - } - // read file header - $meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14)); - // check for bitmap - if ($meta['type'] != 19778) { - fclose($fh); - $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName . ' is not a bitmap!', array('app' => 'core')); - return false; - } - // read image header - $meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40)); - // read additional 16bit header - if ($meta['bits'] == 16) { - $meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12)); - } - // set bytes and padding - $meta['bytes'] = $meta['bits'] / 8; - $this->bitDepth = $meta['bits']; //remember the bit depth for the imagebmp call - $meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4))); - if ($meta['decal'] == 4) { - $meta['decal'] = 0; - } - // obtain imagesize - if ($meta['imagesize'] < 1) { - $meta['imagesize'] = $meta['filesize'] - $meta['offset']; - // in rare cases filesize is equal to offset so we need to read physical size - if ($meta['imagesize'] < 1) { - $meta['imagesize'] = @filesize($fileName) - $meta['offset']; - if ($meta['imagesize'] < 1) { - fclose($fh); - $this->logger->warning('imagecreatefrombmp: Can not obtain file size of ' . $fileName . ' is not a bitmap!', array('app' => 'core')); - return false; - } - } - } - // calculate colors - $meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors']; - // read color palette - $palette = array(); - if ($meta['bits'] < 16) { - $palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4)); - // in rare cases the color value is signed - if ($palette[1] < 0) { - foreach ($palette as $i => $color) { - $palette[$i] = $color + 16777216; - } - } - } - // create gd image - $im = imagecreatetruecolor($meta['width'], $meta['height']); - if ($im == false) { - fclose($fh); - $this->logger->warning( - 'imagecreatefrombmp: imagecreatetruecolor failed for file "' . $fileName . '" with dimensions ' . $meta['width'] . 'x' . $meta['height'], - array('app' => 'core')); - return false; - } - - $data = fread($fh, $meta['imagesize']); - $p = 0; - $vide = chr(0); - $y = $meta['height'] - 1; - $error = 'imagecreatefrombmp: ' . $fileName . ' has not enough data!'; - // loop through the image data beginning with the lower left corner - while ($y >= 0) { - $x = 0; - while ($x < $meta['width']) { - switch ($meta['bits']) { - case 32: - case 24: - if (!($part = substr($data, $p, 3))) { - $this->logger->warning($error, array('app' => 'core')); - return $im; - } - $color = unpack('V', $part . $vide); - break; - case 16: - if (!($part = substr($data, $p, 2))) { - fclose($fh); - $this->logger->warning($error, array('app' => 'core')); - return $im; - } - $color = unpack('v', $part); - $color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3); - break; - case 8: - $color = unpack('n', $vide . substr($data, $p, 1)); - $color[1] = $palette[$color[1] + 1]; - break; - case 4: - $color = unpack('n', $vide . substr($data, floor($p), 1)); - $color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F; - $color[1] = $palette[$color[1] + 1]; - break; - case 1: - $color = unpack('n', $vide . substr($data, floor($p), 1)); - switch (($p * 8) % 8) { - case 0: - $color[1] = $color[1] >> 7; - break; - case 1: - $color[1] = ($color[1] & 0x40) >> 6; - break; - case 2: - $color[1] = ($color[1] & 0x20) >> 5; - break; - case 3: - $color[1] = ($color[1] & 0x10) >> 4; - break; - case 4: - $color[1] = ($color[1] & 0x8) >> 3; - break; - case 5: - $color[1] = ($color[1] & 0x4) >> 2; - break; - case 6: - $color[1] = ($color[1] & 0x2) >> 1; - break; - case 7: - $color[1] = ($color[1] & 0x1); - break; - } - $color[1] = $palette[$color[1] + 1]; - break; - default: - fclose($fh); - $this->logger->warning('imagecreatefrombmp: ' . $fileName . ' has ' . $meta['bits'] . ' bits and this is not supported!', array('app' => 'core')); - return false; - } - imagesetpixel($im, $x, $y, $color[1]); - $x++; - $p += $meta['bytes']; - } - $y--; - $p += $meta['decal']; - } - fclose($fh); - return $im; - } - - /** - * Resizes the image preserving ratio. - * - * @param integer $maxSize The maximum size of either the width or height. - * @return bool - */ - public function resize($maxSize) { - if (!$this->valid()) { - $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core')); - return false; - } - $widthOrig = imageSX($this->resource); - $heightOrig = imageSY($this->resource); - $ratioOrig = $widthOrig / $heightOrig; - - if ($ratioOrig > 1) { - $newHeight = round($maxSize / $ratioOrig); - $newWidth = $maxSize; - } else { - $newWidth = round($maxSize * $ratioOrig); - $newHeight = $maxSize; - } - - $this->preciseResize(round($newWidth), round($newHeight)); - return true; - } - - /** - * @param int $width - * @param int $height - * @return bool - */ - public function preciseResize($width, $height) { - if (!$this->valid()) { - $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core')); - return false; - } - $widthOrig = imageSX($this->resource); - $heightOrig = imageSY($this->resource); - $process = imagecreatetruecolor($width, $height); - - if ($process == false) { - $this->logger->error(__METHOD__ . '(): Error creating true color image', array('app' => 'core')); - imagedestroy($process); - return false; - } - - // preserve transparency - if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) { - imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127)); - imagealphablending($process, false); - imagesavealpha($process, true); - } - - imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig); - if ($process == false) { - $this->logger->error(__METHOD__ . '(): Error re-sampling process image', array('app' => 'core')); - imagedestroy($process); - return false; - } - imagedestroy($this->resource); - $this->resource = $process; - return true; - } - - /** - * Crops the image to the middle square. If the image is already square it just returns. - * - * @param int $size maximum size for the result (optional) - * @return bool for success or failure - */ - public function centerCrop($size = 0) { - if (!$this->valid()) { - $this->logger->error('OC_Image->centerCrop, No image loaded', array('app' => 'core')); - return false; - } - $widthOrig = imageSX($this->resource); - $heightOrig = imageSY($this->resource); - if ($widthOrig === $heightOrig and $size == 0) { - return true; - } - $ratioOrig = $widthOrig / $heightOrig; - $width = $height = min($widthOrig, $heightOrig); - - if ($ratioOrig > 1) { - $x = ($widthOrig / 2) - ($width / 2); - $y = 0; - } else { - $y = ($heightOrig / 2) - ($height / 2); - $x = 0; - } - if ($size > 0) { - $targetWidth = $size; - $targetHeight = $size; - } else { - $targetWidth = $width; - $targetHeight = $height; - } - $process = imagecreatetruecolor($targetWidth, $targetHeight); - if ($process == false) { - $this->logger->error('OC_Image->centerCrop, Error creating true color image', array('app' => 'core')); - imagedestroy($process); - return false; - } - - // preserve transparency - if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) { - imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127)); - imagealphablending($process, false); - imagesavealpha($process, true); - } - - imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height); - if ($process == false) { - $this->logger->error('OC_Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height, array('app' => 'core')); - imagedestroy($process); - return false; - } - imagedestroy($this->resource); - $this->resource = $process; - return true; - } - - /** - * Crops the image from point $x$y with dimension $wx$h. - * - * @param int $x Horizontal position - * @param int $y Vertical position - * @param int $w Width - * @param int $h Height - * @return bool for success or failure - */ - public function crop($x, $y, $w, $h) { - if (!$this->valid()) { - $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core')); - return false; - } - $process = imagecreatetruecolor($w, $h); - if ($process == false) { - $this->logger->error(__METHOD__ . '(): Error creating true color image', array('app' => 'core')); - imagedestroy($process); - return false; - } - - // preserve transparency - if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) { - imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127)); - imagealphablending($process, false); - imagesavealpha($process, true); - } - - imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h); - if ($process == false) { - $this->logger->error(__METHOD__ . '(): Error re-sampling process image ' . $w . 'x' . $h, array('app' => 'core')); - imagedestroy($process); - return false; - } - imagedestroy($this->resource); - $this->resource = $process; - return true; - } - - /** - * Resizes the image to fit within a boundary while preserving ratio. - * - * Warning: Images smaller than $maxWidth x $maxHeight will end up being scaled up - * - * @param integer $maxWidth - * @param integer $maxHeight - * @return bool - */ - public function fitIn($maxWidth, $maxHeight) { - if (!$this->valid()) { - $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core')); - return false; - } - $widthOrig = imageSX($this->resource); - $heightOrig = imageSY($this->resource); - $ratio = $widthOrig / $heightOrig; - - $newWidth = min($maxWidth, $ratio * $maxHeight); - $newHeight = min($maxHeight, $maxWidth / $ratio); - - $this->preciseResize(round($newWidth), round($newHeight)); - return true; - } - - /** - * Shrinks larger images to fit within specified boundaries while preserving ratio. - * - * @param integer $maxWidth - * @param integer $maxHeight - * @return bool - */ - public function scaleDownToFit($maxWidth, $maxHeight) { - if (!$this->valid()) { - $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core')); - return false; - } - $widthOrig = imageSX($this->resource); - $heightOrig = imageSY($this->resource); - - if ($widthOrig > $maxWidth || $heightOrig > $maxHeight) { - return $this->fitIn($maxWidth, $maxHeight); - } - - return false; - } - - /** - * Destroys the current image and resets the object - */ - public function destroy() { - if ($this->valid()) { - imagedestroy($this->resource); - } - $this->resource = null; - } - - public function __destruct() { - $this->destroy(); - } -} - -if (!function_exists('imagebmp')) { - /** - * Output a BMP image to either the browser or a file - * - * @link http://www.ugia.cn/wp-data/imagebmp.php - * @author legend - * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm - * @author mgutt - * @version 1.00 - * @param string $fileName [optional]

The path to save the file to.

- * @param int $bit [optional]

Bit depth, (default is 24).

- * @param int $compression [optional] - * @return bool TRUE on success or FALSE on failure. - */ - function imagebmp($im, $fileName = '', $bit = 24, $compression = 0) { - if (!in_array($bit, array(1, 4, 8, 16, 24, 32))) { - $bit = 24; - } else if ($bit == 32) { - $bit = 24; - } - $bits = pow(2, $bit); - imagetruecolortopalette($im, true, $bits); - $width = imagesx($im); - $height = imagesy($im); - $colorsNum = imagecolorstotal($im); - $rgbQuad = ''; - if ($bit <= 8) { - for ($i = 0; $i < $colorsNum; $i++) { - $colors = imagecolorsforindex($im, $i); - $rgbQuad .= chr($colors['blue']) . chr($colors['green']) . chr($colors['red']) . "\0"; - } - $bmpData = ''; - if ($compression == 0 || $bit < 8) { - $compression = 0; - $extra = ''; - $padding = 4 - ceil($width / (8 / $bit)) % 4; - if ($padding % 4 != 0) { - $extra = str_repeat("\0", $padding); - } - for ($j = $height - 1; $j >= 0; $j--) { - $i = 0; - while ($i < $width) { - $bin = 0; - $limit = $width - $i < 8 / $bit ? (8 / $bit - $width + $i) * $bit : 0; - for ($k = 8 - $bit; $k >= $limit; $k -= $bit) { - $index = imagecolorat($im, $i, $j); - $bin |= $index << $k; - $i++; - } - $bmpData .= chr($bin); - } - $bmpData .= $extra; - } - } // RLE8 - else if ($compression == 1 && $bit == 8) { - for ($j = $height - 1; $j >= 0; $j--) { - $lastIndex = "\0"; - $sameNum = 0; - for ($i = 0; $i <= $width; $i++) { - $index = imagecolorat($im, $i, $j); - if ($index !== $lastIndex || $sameNum > 255) { - if ($sameNum != 0) { - $bmpData .= chr($sameNum) . chr($lastIndex); - } - $lastIndex = $index; - $sameNum = 1; - } else { - $sameNum++; - } - } - $bmpData .= "\0\0"; - } - $bmpData .= "\0\1"; - } - $sizeQuad = strlen($rgbQuad); - $sizeData = strlen($bmpData); - } else { - $extra = ''; - $padding = 4 - ($width * ($bit / 8)) % 4; - if ($padding % 4 != 0) { - $extra = str_repeat("\0", $padding); - } - $bmpData = ''; - for ($j = $height - 1; $j >= 0; $j--) { - for ($i = 0; $i < $width; $i++) { - $index = imagecolorat($im, $i, $j); - $colors = imagecolorsforindex($im, $index); - if ($bit == 16) { - $bin = 0 << $bit; - $bin |= ($colors['red'] >> 3) << 10; - $bin |= ($colors['green'] >> 3) << 5; - $bin |= $colors['blue'] >> 3; - $bmpData .= pack("v", $bin); - } else { - $bmpData .= pack("c*", $colors['blue'], $colors['green'], $colors['red']); - } - } - $bmpData .= $extra; - } - $sizeQuad = 0; - $sizeData = strlen($bmpData); - $colorsNum = 0; - } - $fileHeader = 'BM' . pack('V3', 54 + $sizeQuad + $sizeData, 0, 54 + $sizeQuad); - $infoHeader = pack('V3v2V*', 0x28, $width, $height, 1, $bit, $compression, $sizeData, 0, 0, $colorsNum, 0); - if ($fileName != '') { - $fp = fopen($fileName, 'wb'); - fwrite($fp, $fileHeader . $infoHeader . $rgbQuad . $bmpData); - fclose($fp); - return true; - } - echo $fileHeader . $infoHeader . $rgbQuad . $bmpData; - return true; - } -} - -if (!function_exists('exif_imagetype')) { - /** - * Workaround if exif_imagetype does not exist - * - * @link http://www.php.net/manual/en/function.exif-imagetype.php#80383 - * @param string $fileName - * @return string|boolean - */ - function exif_imagetype($fileName) { - if (($info = getimagesize($fileName)) !== false) { - return $info[2]; - } - return false; - } -} diff --git a/lib/private/installer.php b/lib/private/installer.php deleted file mode 100644 index 24c79b2dd8c..00000000000 --- a/lib/private/installer.php +++ /dev/null @@ -1,638 +0,0 @@ - - * @author Bart Visscher - * @author Brice Maron - * @author Christian Weiske - * @author Christopher Schäpers - * @author Frank Karlitschek - * @author Georg Ehrke - * @author Jakob Sack - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Kamil Domanski - * @author Lukas Reschke - * @author michag86 - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author root - * @author Thomas Müller - * @author Thomas Tanghus - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -use OC\App\CodeChecker\CodeChecker; -use OC\App\CodeChecker\EmptyCheck; -use OC\App\CodeChecker\PrivateCheck; -use OC\OCSClient; - -/** - * This class provides the functionality needed to install, update and remove plugins/apps - */ -class OC_Installer{ - - /** - * - * This function installs an app. All information needed are passed in the - * associative array $data. - * The following keys are required: - * - source: string, can be "path" or "http" - * - * One of the following keys is required: - * - path: path to the file containing the app - * - href: link to the downloadable file containing the app - * - * The following keys are optional: - * - pretend: boolean, if set true the system won't do anything - * - noinstall: boolean, if true appinfo/install.php won't be loaded - * - inactive: boolean, if set true the appconfig/app.sample.php won't be - * renamed - * - * This function works as follows - * -# fetching the file - * -# unzipping it - * -# check the code - * -# installing the database at appinfo/database.xml - * -# including appinfo/install.php - * -# setting the installed version - * - * It is the task of oc_app_install to create the tables and do whatever is - * needed to get the app working. - * - * Installs an app - * @param array $data with all information - * @throws \Exception - * @return integer - */ - public static function installApp( $data = array()) { - $l = \OC::$server->getL10N('lib'); - - list($extractDir, $path) = self::downloadApp($data); - - $info = self::checkAppsIntegrity($data, $extractDir, $path); - $appId = OC_App::cleanAppId($info['id']); - $basedir = OC_App::getInstallPath().'/'.$appId; - //check if the destination directory already exists - if(is_dir($basedir)) { - OC_Helper::rmdirr($extractDir); - if($data['source']=='http') { - unlink($path); - } - throw new \Exception($l->t("App directory already exists")); - } - - if(!empty($data['pretent'])) { - return false; - } - - //copy the app to the correct place - if(@!mkdir($basedir)) { - OC_Helper::rmdirr($extractDir); - if($data['source']=='http') { - unlink($path); - } - throw new \Exception($l->t("Can't create app folder. Please fix permissions. %s", array($basedir))); - } - - $extractDir .= '/' . $info['id']; - if(!file_exists($extractDir)) { - OC_Helper::rmdirr($basedir); - throw new \Exception($l->t("Archive does not contain a directory named %s", $info['id'])); - } - OC_Helper::copyr($extractDir, $basedir); - - //remove temporary files - OC_Helper::rmdirr($extractDir); - - //install the database - if(is_file($basedir.'/appinfo/database.xml')) { - if (\OC::$server->getAppConfig()->getValue($info['id'], 'installed_version') === null) { - OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml'); - } else { - OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml'); - } - } - - //run appinfo/install.php - if((!isset($data['noinstall']) or $data['noinstall']==false)) { - self::includeAppScript($basedir . '/appinfo/install.php'); - } - - //set the installed version - \OC::$server->getAppConfig()->setValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'])); - \OC::$server->getAppConfig()->setValue($info['id'], 'enabled', 'no'); - - //set remote/public handelers - foreach($info['remote'] as $name=>$path) { - OCP\CONFIG::setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path); - } - foreach($info['public'] as $name=>$path) { - OCP\CONFIG::setAppValue('core', 'public_'.$name, $info['id'].'/'.$path); - } - - OC_App::setAppTypes($info['id']); - - return $info['id']; - } - - /** - * @brief checks whether or not an app is installed - * @param string $app app - * @returns bool - * - * Checks whether or not an app is installed, i.e. registered in apps table. - */ - public static function isInstalled( $app ) { - return (\OC::$server->getAppConfig()->getValue($app, "installed_version") !== null); - } - - /** - * @brief Update an application - * @param array $info - * @param bool $isShipped - * @throws Exception - * @return bool - * - * This function could work like described below, but currently it disables and then - * enables the app again. This does result in an updated app. - * - * - * This function installs an app. All information needed are passed in the - * associative array $info. - * The following keys are required: - * - source: string, can be "path" or "http" - * - * One of the following keys is required: - * - path: path to the file containing the app - * - href: link to the downloadable file containing the app - * - * The following keys are optional: - * - pretend: boolean, if set true the system won't do anything - * - noupgrade: boolean, if true appinfo/upgrade.php won't be loaded - * - * This function works as follows - * -# fetching the file - * -# removing the old files - * -# unzipping new file - * -# including appinfo/upgrade.php - * -# setting the installed version - * - * upgrade.php can determine the current installed version of the app using - * "\OC::$server->getAppConfig()->getValue($appid, 'installed_version')" - */ - public static function updateApp($info=array(), $isShipped=false) { - list($extractDir, $path) = self::downloadApp($info); - $info = self::checkAppsIntegrity($info, $extractDir, $path, $isShipped); - - $currentDir = OC_App::getAppPath($info['id']); - $basedir = OC_App::getInstallPath(); - $basedir .= '/'; - $basedir .= $info['id']; - - if($currentDir !== false && is_writable($currentDir)) { - $basedir = $currentDir; - } - if(is_dir($basedir)) { - OC_Helper::rmdirr($basedir); - } - - $appInExtractDir = $extractDir; - if (substr($extractDir, -1) !== '/') { - $appInExtractDir .= '/'; - } - - $appInExtractDir .= $info['id']; - OC_Helper::copyr($appInExtractDir, $basedir); - OC_Helper::rmdirr($extractDir); - - return OC_App::updateApp($info['id']); - } - - /** - * update an app by it's id - * - * @param integer $ocsId - * @return bool - * @throws Exception - */ - public static function updateAppByOCSId($ocsId) { - $ocsClient = new OCSClient( - \OC::$server->getHTTPClientService(), - \OC::$server->getConfig(), - \OC::$server->getLogger() - ); - $appData = $ocsClient->getApplication($ocsId, \OCP\Util::getVersion()); - $download = $ocsClient->getApplicationDownload($ocsId, \OCP\Util::getVersion()); - - if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') { - $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); - $info = array( - 'source' => 'http', - 'href' => $download['downloadlink'], - 'appdata' => $appData - ); - } else { - throw new \Exception('Could not fetch app info!'); - } - - return self::updateApp($info); - } - - /** - * @param array $data - * @return array - * @throws Exception - */ - public static function downloadApp($data = array()) { - $l = \OC::$server->getL10N('lib'); - - if(!isset($data['source'])) { - throw new \Exception($l->t("No source specified when installing app")); - } - - //download the file if necessary - if($data['source']=='http') { - $pathInfo = pathinfo($data['href']); - $extension = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : ''; - $path = \OC::$server->getTempManager()->getTemporaryFile($extension); - if(!isset($data['href'])) { - throw new \Exception($l->t("No href specified when installing app from http")); - } - $client = \OC::$server->getHTTPClientService()->newClient(); - $client->get($data['href'], ['save_to' => $path]); - } else { - if(!isset($data['path'])) { - throw new \Exception($l->t("No path specified when installing app from local file")); - } - $path=$data['path']; - } - - //detect the archive type - $mime = \OC::$server->getMimeTypeDetector()->detect($path); - if ($mime !=='application/zip' && $mime !== 'application/x-gzip' && $mime !== 'application/x-bzip2') { - throw new \Exception($l->t("Archives of type %s are not supported", array($mime))); - } - - //extract the archive in a temporary folder - $extractDir = \OC::$server->getTempManager()->getTemporaryFolder(); - OC_Helper::rmdirr($extractDir); - mkdir($extractDir); - if($archive=OC_Archive::open($path)) { - $archive->extract($extractDir); - } else { - OC_Helper::rmdirr($extractDir); - if($data['source']=='http') { - unlink($path); - } - throw new \Exception($l->t("Failed to open archive when installing app")); - } - - return array( - $extractDir, - $path - ); - } - - /** - * check an app's integrity - * @param array $data - * @param string $extractDir - * @param string $path - * @param bool $isShipped - * @return array - * @throws \Exception - */ - public static function checkAppsIntegrity($data, $extractDir, $path, $isShipped = false) { - $l = \OC::$server->getL10N('lib'); - //load the info.xml file of the app - if(!is_file($extractDir.'/appinfo/info.xml')) { - //try to find it in a subdir - $dh=opendir($extractDir); - if(is_resource($dh)) { - while (($folder = readdir($dh)) !== false) { - if($folder[0]!='.' and is_dir($extractDir.'/'.$folder)) { - if(is_file($extractDir.'/'.$folder.'/appinfo/info.xml')) { - $extractDir.='/'.$folder; - } - } - } - } - } - if(!is_file($extractDir.'/appinfo/info.xml')) { - OC_Helper::rmdirr($extractDir); - if($data['source'] === 'http') { - unlink($path); - } - throw new \Exception($l->t("App does not provide an info.xml file")); - } - - $info = OC_App::getAppInfo($extractDir.'/appinfo/info.xml', true); - if(!is_array($info)) { - throw new \Exception($l->t('App cannot be installed because appinfo file cannot be read.')); - } - - // We can't trust the parsed info.xml file as it may have been tampered - // with by an attacker and thus we need to use the local data to check - // whether the application needs to be signed. - $appId = OC_App::cleanAppId($data['appdata']['id']); - $appBelongingToId = OC_App::getInternalAppIdByOcs($appId); - if(is_string($appBelongingToId)) { - $previouslySigned = \OC::$server->getConfig()->getAppValue($appBelongingToId, 'signed', 'false'); - } else { - $appBelongingToId = $info['id']; - $previouslySigned = 'false'; - } - if($data['appdata']['level'] === OC_App::officialApp || $previouslySigned === 'true') { - \OC::$server->getConfig()->setAppValue($appBelongingToId, 'signed', 'true'); - $integrityResult = \OC::$server->getIntegrityCodeChecker()->verifyAppSignature( - $appBelongingToId, - $extractDir - ); - if($integrityResult !== []) { - $e = new \Exception( - $l->t( - 'Signature could not get checked. Please contact the app developer and check your admin screen.' - ) - ); - throw $e; - } - } - - // check the code for not allowed calls - if(!$isShipped && !OC_Installer::checkCode($extractDir)) { - OC_Helper::rmdirr($extractDir); - throw new \Exception($l->t("App can't be installed because of not allowed code in the App")); - } - - // check if the app is compatible with this version of ownCloud - if(!OC_App::isAppCompatible(\OCP\Util::getVersion(), $info)) { - OC_Helper::rmdirr($extractDir); - throw new \Exception($l->t("App can't be installed because it is not compatible with this version of ownCloud")); - } - - // check if shipped tag is set which is only allowed for apps that are shipped with ownCloud - if(!$isShipped && isset($info['shipped']) && ($info['shipped']=='true')) { - OC_Helper::rmdirr($extractDir); - throw new \Exception($l->t("App can't be installed because it contains the true tag which is not allowed for non shipped apps")); - } - - // check if the ocs version is the same as the version in info.xml/version - $version = trim($info['version']); - - if(isset($data['appdata']['version']) && $version<>trim($data['appdata']['version'])) { - OC_Helper::rmdirr($extractDir); - throw new \Exception($l->t("App can't be installed because the version in info.xml is not the same as the version reported from the app store")); - } - - return $info; - } - - /** - * Check if an update for the app is available - * @param string $app - * @return string|false false or the version number of the update - * - * The function will check if an update for a version is available - */ - public static function isUpdateAvailable( $app ) { - static $isInstanceReadyForUpdates = null; - - if ($isInstanceReadyForUpdates === null) { - $installPath = OC_App::getInstallPath(); - if ($installPath === false || $installPath === null) { - $isInstanceReadyForUpdates = false; - } else { - $isInstanceReadyForUpdates = true; - } - } - - if ($isInstanceReadyForUpdates === false) { - return false; - } - - $ocsid=\OC::$server->getAppConfig()->getValue( $app, 'ocsid', ''); - - if($ocsid<>'') { - $ocsClient = new OCSClient( - \OC::$server->getHTTPClientService(), - \OC::$server->getConfig(), - \OC::$server->getLogger() - ); - $ocsdata = $ocsClient->getApplication($ocsid, \OCP\Util::getVersion()); - $ocsversion= (string) $ocsdata['version']; - $currentversion=OC_App::getAppVersion($app); - if (version_compare($ocsversion, $currentversion, '>')) { - return($ocsversion); - }else{ - return false; - } - - }else{ - return false; - } - - } - - /** - * Check if app is already downloaded - * @param string $name name of the application to remove - * @return boolean - * - * The function will check if the app is already downloaded in the apps repository - */ - public static function isDownloaded( $name ) { - foreach(OC::$APPSROOTS as $dir) { - $dirToTest = $dir['path']; - $dirToTest .= '/'; - $dirToTest .= $name; - $dirToTest .= '/'; - - if (is_dir($dirToTest)) { - return true; - } - } - - return false; - } - - /** - * Removes an app - * @param string $name name of the application to remove - * @param array $options options - * @return boolean - * - * This function removes an app. $options is an associative array. The - * following keys are optional:ja - * - keeppreferences: boolean, if true the user preferences won't be deleted - * - keepappconfig: boolean, if true the config will be kept - * - keeptables: boolean, if true the database will be kept - * - keepfiles: boolean, if true the user files will be kept - * - * This function works as follows - * -# including appinfo/remove.php - * -# removing the files - * - * The function will not delete preferences, tables and the configuration, - * this has to be done by the function oc_app_uninstall(). - */ - public static function removeApp( $name, $options = array()) { - - if(isset($options['keeppreferences']) and $options['keeppreferences']==false ) { - // todo - // remove preferences - } - - if(isset($options['keepappconfig']) and $options['keepappconfig']==false ) { - // todo - // remove app config - } - - if(isset($options['keeptables']) and $options['keeptables']==false ) { - // todo - // remove app database tables - } - - if(isset($options['keepfiles']) and $options['keepfiles']==false ) { - // todo - // remove user files - } - - if(OC_Installer::isDownloaded( $name )) { - $appdir=OC_App::getInstallPath().'/'.$name; - OC_Helper::rmdirr($appdir); - - return true; - }else{ - \OCP\Util::writeLog('core', 'can\'t remove app '.$name.'. It is not installed.', \OCP\Util::ERROR); - - return false; - } - - } - - /** - * Installs shipped apps - * - * This function installs all apps found in the 'apps' directory that should be enabled by default; - * @param bool $softErrors When updating we ignore errors and simply log them, better to have a - * working ownCloud at the end instead of an aborted update. - * @return array Array of error messages (appid => Exception) - */ - public static function installShippedApps($softErrors = false) { - $errors = []; - foreach(OC::$APPSROOTS as $app_dir) { - if($dir = opendir( $app_dir['path'] )) { - while( false !== ( $filename = readdir( $dir ))) { - if( substr( $filename, 0, 1 ) != '.' and is_dir($app_dir['path']."/$filename") ) { - if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) { - if(!OC_Installer::isInstalled($filename)) { - $info=OC_App::getAppInfo($filename); - $enabled = isset($info['default_enable']); - if (($enabled || in_array($filename, \OC::$server->getAppManager()->getAlwaysEnabledApps())) - && \OC::$server->getConfig()->getAppValue($filename, 'enabled') !== 'no') { - if ($softErrors) { - try { - OC_Installer::installShippedApp($filename); - } catch (\Doctrine\DBAL\Exception\TableExistsException $e) { - $errors[$filename] = $e; - continue; - } - } else { - OC_Installer::installShippedApp($filename); - } - \OC::$server->getConfig()->setAppValue($filename, 'enabled', 'yes'); - } - } - } - } - } - closedir( $dir ); - } - } - - return $errors; - } - - /** - * install an app already placed in the app folder - * @param string $app id of the app to install - * @return integer - */ - public static function installShippedApp($app) { - //install the database - $appPath = OC_App::getAppPath($app); - if(is_file("$appPath/appinfo/database.xml")) { - OC_DB::createDbFromStructure("$appPath/appinfo/database.xml"); - } - - //run appinfo/install.php - \OC::$loader->addValidRoot($appPath); - self::includeAppScript("$appPath/appinfo/install.php"); - - $info = OC_App::getAppInfo($app); - if (is_null($info)) { - return false; - } - - $config = \OC::$server->getConfig(); - - $config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app)); - if (array_key_exists('ocsid', $info)) { - $config->setAppValue($app, 'ocsid', $info['ocsid']); - } - - //set remote/public handlers - foreach($info['remote'] as $name=>$path) { - $config->setAppValue('core', 'remote_'.$name, $app.'/'.$path); - } - foreach($info['public'] as $name=>$path) { - $config->setAppValue('core', 'public_'.$name, $app.'/'.$path); - } - - OC_App::setAppTypes($info['id']); - - return $info['id']; - } - - /** - * check the code of an app with some static code checks - * @param string $folder the folder of the app to check - * @return boolean true for app is o.k. and false for app is not o.k. - */ - public static function checkCode($folder) { - // is the code checker enabled? - if(!\OC::$server->getConfig()->getSystemValue('appcodechecker', false)) { - return true; - } - - $codeChecker = new CodeChecker(new PrivateCheck(new EmptyCheck())); - $errors = $codeChecker->analyseFolder($folder); - - return empty($errors); - } - - /** - * @param $basedir - */ - private static function includeAppScript($script) { - if ( file_exists($script) ){ - include $script; - } - } -} diff --git a/lib/private/json.php b/lib/private/json.php deleted file mode 100644 index 74aebd476fb..00000000000 --- a/lib/private/json.php +++ /dev/null @@ -1,179 +0,0 @@ - - * @author Bernhard Posselt - * @author Felix Moeller - * @author Georg Ehrke - * @author Lukas Reschke - * @author Morris Jobke - * @author Robin Appelman - * @author Thomas Müller - * @author Thomas Tanghus - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -/** - * Class OC_JSON - * @deprecated Use a AppFramework JSONResponse instead - */ -class OC_JSON{ - static protected $send_content_type_header = false; - /** - * set Content-Type header to jsonrequest - * @deprecated Use a AppFramework JSONResponse instead - */ - public static function setContentTypeHeader($type='application/json') { - if (!self::$send_content_type_header) { - // We send json data - header( 'Content-Type: '.$type . '; charset=utf-8'); - self::$send_content_type_header = true; - } - } - - /** - * Check if the app is enabled, send json error msg if not - * @param string $app - * @deprecated Use the AppFramework instead. It will automatically check if the app is enabled. - */ - public static function checkAppEnabled($app) { - if( !OC_App::isEnabled($app)) { - $l = \OC::$server->getL10N('lib'); - self::error(array( 'data' => array( 'message' => $l->t('Application is not enabled'), 'error' => 'application_not_enabled' ))); - exit(); - } - } - - /** - * Check if the user is logged in, send json error msg if not - * @deprecated Use annotation based ACLs from the AppFramework instead - */ - public static function checkLoggedIn() { - if( !OC_User::isLoggedIn()) { - $l = \OC::$server->getL10N('lib'); - http_response_code(\OCP\AppFramework\Http::STATUS_UNAUTHORIZED); - self::error(array( 'data' => array( 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ))); - exit(); - } - } - - /** - * Check an ajax get/post call if the request token is valid, send json error msg if not. - * @deprecated Use annotation based CSRF checks from the AppFramework instead - */ - public static function callCheck() { - if( !(\OC::$server->getRequest()->passesCSRFCheck())) { - $l = \OC::$server->getL10N('lib'); - self::error(array( 'data' => array( 'message' => $l->t('Token expired. Please reload page.'), 'error' => 'token_expired' ))); - exit(); - } - } - - /** - * Check if the user is a admin, send json error msg if not. - * @deprecated Use annotation based ACLs from the AppFramework instead - */ - public static function checkAdminUser() { - if( !OC_User::isAdminUser(OC_User::getUser())) { - $l = \OC::$server->getL10N('lib'); - self::error(array( 'data' => array( 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ))); - exit(); - } - } - - /** - * Check is a given user exists - send json error msg if not - * @param string $user - * @deprecated Use a AppFramework JSONResponse instead - */ - public static function checkUserExists($user) { - if (!OCP\User::userExists($user)) { - $l = \OC::$server->getL10N('lib'); - OCP\JSON::error(array('data' => array('message' => $l->t('Unknown user'), 'error' => 'unknown_user' ))); - exit; - } - } - - - /** - * Check if the user is a subadmin, send json error msg if not - * @deprecated Use annotation based ACLs from the AppFramework instead - */ - public static function checkSubAdminUser() { - $userObject = \OC::$server->getUserSession()->getUser(); - $isSubAdmin = false; - if($userObject !== null) { - $isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject); - } - - if(!$isSubAdmin) { - $l = \OC::$server->getL10N('lib'); - self::error(array( 'data' => array( 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ))); - exit(); - } - } - - /** - * Send json error msg - * @deprecated Use a AppFramework JSONResponse instead - */ - public static function error($data = array()) { - $data['status'] = 'error'; - self::encodedPrint($data); - } - - /** - * Send json success msg - * @deprecated Use a AppFramework JSONResponse instead - */ - public static function success($data = array()) { - $data['status'] = 'success'; - self::encodedPrint($data); - } - - /** - * Convert OC_L10N_String to string, for use in json encodings - */ - protected static function to_string(&$value) { - if ($value instanceof OC_L10N_String) { - $value = (string)$value; - } - } - - /** - * Encode and print $data in json format - * @deprecated Use a AppFramework JSONResponse instead - */ - public static function encodedPrint($data, $setContentType=true) { - if($setContentType) { - self::setContentTypeHeader(); - } - echo self::encode($data); - } - - /** - * Encode JSON - * @deprecated Use a AppFramework JSONResponse instead - */ - public static function encode($data) { - if (is_array($data)) { - array_walk_recursive($data, array('OC_JSON', 'to_string')); - } - return json_encode($data, JSON_HEX_TAG); - } -} diff --git a/lib/private/legacy/api.php b/lib/private/legacy/api.php new file mode 100644 index 00000000000..bab879c95f8 --- /dev/null +++ b/lib/private/legacy/api.php @@ -0,0 +1,533 @@ + + * @author Bernhard Posselt + * @author Björn Schießle + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Tom Needham + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +use OCP\API; +use OCP\AppFramework\Http; + +/** + * @author Bart Visscher + * @author Bernhard Posselt + * @author Björn Schießle + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Tom Needham + * @author Vincent Petry + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +class OC_API { + + /** + * API authentication levels + */ + + /** @deprecated Use \OCP\API::GUEST_AUTH instead */ + const GUEST_AUTH = 0; + + /** @deprecated Use \OCP\API::USER_AUTH instead */ + const USER_AUTH = 1; + + /** @deprecated Use \OCP\API::SUBADMIN_AUTH instead */ + const SUBADMIN_AUTH = 2; + + /** @deprecated Use \OCP\API::ADMIN_AUTH instead */ + const ADMIN_AUTH = 3; + + /** + * API Response Codes + */ + + /** @deprecated Use \OCP\API::RESPOND_UNAUTHORISED instead */ + const RESPOND_UNAUTHORISED = 997; + + /** @deprecated Use \OCP\API::RESPOND_SERVER_ERROR instead */ + const RESPOND_SERVER_ERROR = 996; + + /** @deprecated Use \OCP\API::RESPOND_NOT_FOUND instead */ + const RESPOND_NOT_FOUND = 998; + + /** @deprecated Use \OCP\API::RESPOND_UNKNOWN_ERROR instead */ + const RESPOND_UNKNOWN_ERROR = 999; + + /** + * api actions + */ + protected static $actions = array(); + private static $logoutRequired = false; + private static $isLoggedIn = false; + + /** + * registers an api call + * @param string $method the http method + * @param string $url the url to match + * @param callable $action the function to run + * @param string $app the id of the app registering the call + * @param int $authLevel the level of authentication required for the call + * @param array $defaults + * @param array $requirements + */ + public static function register($method, $url, $action, $app, + $authLevel = API::USER_AUTH, + $defaults = array(), + $requirements = array()) { + $name = strtolower($method).$url; + $name = str_replace(array('/', '{', '}'), '_', $name); + if(!isset(self::$actions[$name])) { + $oldCollection = OC::$server->getRouter()->getCurrentCollection(); + OC::$server->getRouter()->useCollection('ocs'); + OC::$server->getRouter()->create($name, $url) + ->method($method) + ->defaults($defaults) + ->requirements($requirements) + ->action('OC_API', 'call'); + self::$actions[$name] = array(); + OC::$server->getRouter()->useCollection($oldCollection); + } + self::$actions[$name][] = array('app' => $app, 'action' => $action, 'authlevel' => $authLevel); + } + + /** + * handles an api call + * @param array $parameters + */ + public static function call($parameters) { + $request = \OC::$server->getRequest(); + $method = $request->getMethod(); + + // Prepare the request variables + if($method === 'PUT') { + $parameters['_put'] = $request->getParams(); + } else if($method === 'DELETE') { + $parameters['_delete'] = $request->getParams(); + } + $name = $parameters['_route']; + // Foreach registered action + $responses = array(); + foreach(self::$actions[$name] as $action) { + // Check authentication and availability + if(!self::isAuthorised($action)) { + $responses[] = array( + 'app' => $action['app'], + 'response' => new OC_OCS_Result(null, API::RESPOND_UNAUTHORISED, 'Unauthorised'), + 'shipped' => OC_App::isShipped($action['app']), + ); + continue; + } + if(!is_callable($action['action'])) { + $responses[] = array( + 'app' => $action['app'], + 'response' => new OC_OCS_Result(null, API::RESPOND_NOT_FOUND, 'Api method not found'), + 'shipped' => OC_App::isShipped($action['app']), + ); + continue; + } + // Run the action + $responses[] = array( + 'app' => $action['app'], + 'response' => call_user_func($action['action'], $parameters), + 'shipped' => OC_App::isShipped($action['app']), + ); + } + $response = self::mergeResponses($responses); + $format = self::requestedFormat(); + if (self::$logoutRequired) { + \OC::$server->getUserSession()->logout(); + } + + self::respond($response, $format); + } + + /** + * merge the returned result objects into one response + * @param array $responses + * @return OC_OCS_Result + */ + public static function mergeResponses($responses) { + // Sort into shipped and third-party + $shipped = array( + 'succeeded' => array(), + 'failed' => array(), + ); + $thirdparty = array( + 'succeeded' => array(), + 'failed' => array(), + ); + + foreach($responses as $response) { + if($response['shipped'] || ($response['app'] === 'core')) { + if($response['response']->succeeded()) { + $shipped['succeeded'][$response['app']] = $response; + } else { + $shipped['failed'][$response['app']] = $response; + } + } else { + if($response['response']->succeeded()) { + $thirdparty['succeeded'][$response['app']] = $response; + } else { + $thirdparty['failed'][$response['app']] = $response; + } + } + } + + // Remove any error responses if there is one shipped response that succeeded + if(!empty($shipped['failed'])) { + // Which shipped response do we use if they all failed? + // They may have failed for different reasons (different status codes) + // Which response code should we return? + // Maybe any that are not \OCP\API::RESPOND_SERVER_ERROR + // Merge failed responses if more than one + $data = array(); + foreach($shipped['failed'] as $failure) { + $data = array_merge_recursive($data, $failure['response']->getData()); + } + $picked = reset($shipped['failed']); + $code = $picked['response']->getStatusCode(); + $meta = $picked['response']->getMeta(); + $headers = $picked['response']->getHeaders(); + $response = new OC_OCS_Result($data, $code, $meta['message'], $headers); + return $response; + } elseif(!empty($shipped['succeeded'])) { + $responses = array_merge($shipped['succeeded'], $thirdparty['succeeded']); + } elseif(!empty($thirdparty['failed'])) { + // Merge failed responses if more than one + $data = array(); + foreach($thirdparty['failed'] as $failure) { + $data = array_merge_recursive($data, $failure['response']->getData()); + } + $picked = reset($thirdparty['failed']); + $code = $picked['response']->getStatusCode(); + $meta = $picked['response']->getMeta(); + $headers = $picked['response']->getHeaders(); + $response = new OC_OCS_Result($data, $code, $meta['message'], $headers); + return $response; + } else { + $responses = $thirdparty['succeeded']; + } + // Merge the successful responses + $data = []; + $codes = []; + $header = []; + + foreach($responses as $response) { + if($response['shipped']) { + $data = array_merge_recursive($response['response']->getData(), $data); + } else { + $data = array_merge_recursive($data, $response['response']->getData()); + } + $header = array_merge_recursive($header, $response['response']->getHeaders()); + $codes[] = ['code' => $response['response']->getStatusCode(), + 'meta' => $response['response']->getMeta()]; + } + + // Use any non 100 status codes + $statusCode = 100; + $statusMessage = null; + foreach($codes as $code) { + if($code['code'] != 100) { + $statusCode = $code['code']; + $statusMessage = $code['meta']['message']; + break; + } + } + + return new OC_OCS_Result($data, $statusCode, $statusMessage, $header); + } + + /** + * authenticate the api call + * @param array $action the action details as supplied to OC_API::register() + * @return bool + */ + private static function isAuthorised($action) { + $level = $action['authlevel']; + switch($level) { + case API::GUEST_AUTH: + // Anyone can access + return true; + case API::USER_AUTH: + // User required + return self::loginUser(); + case API::SUBADMIN_AUTH: + // Check for subadmin + $user = self::loginUser(); + if(!$user) { + return false; + } else { + $userObject = \OC::$server->getUserSession()->getUser(); + if($userObject === null) { + return false; + } + $isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject); + $admin = OC_User::isAdminUser($user); + if($isSubAdmin || $admin) { + return true; + } else { + return false; + } + } + case API::ADMIN_AUTH: + // Check for admin + $user = self::loginUser(); + if(!$user) { + return false; + } else { + return OC_User::isAdminUser($user); + } + default: + // oops looks like invalid level supplied + return false; + } + } + + /** + * http basic auth + * @return string|false (username, or false on failure) + */ + private static function loginUser() { + if(self::$isLoggedIn === true) { + return \OC_User::getUser(); + } + + // reuse existing login + $loggedIn = OC_User::isLoggedIn(); + if ($loggedIn === true) { + $ocsApiRequest = isset($_SERVER['HTTP_OCS_APIREQUEST']) ? $_SERVER['HTTP_OCS_APIREQUEST'] === 'true' : false; + if ($ocsApiRequest) { + + // initialize the user's filesystem + \OC_Util::setUpFS(\OC_User::getUser()); + self::$isLoggedIn = true; + + return OC_User::getUser(); + } + return false; + } + + // basic auth - because OC_User::login will create a new session we shall only try to login + // if user and pass are set + if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']) ) { + $authUser = $_SERVER['PHP_AUTH_USER']; + $authPw = $_SERVER['PHP_AUTH_PW']; + $return = OC_User::login($authUser, $authPw); + if ($return === true) { + self::$logoutRequired = true; + + // initialize the user's filesystem + \OC_Util::setUpFS(\OC_User::getUser()); + self::$isLoggedIn = true; + + /** + * Add DAV authenticated. This should in an ideal world not be + * necessary but the iOS App reads cookies from anywhere instead + * only the DAV endpoint. + * This makes sure that the cookies will be valid for the whole scope + * @see https://github.com/owncloud/core/issues/22893 + */ + \OC::$server->getSession()->set( + \OCA\DAV\Connector\Sabre\Auth::DAV_AUTHENTICATED, + \OC::$server->getUserSession()->getUser()->getUID() + ); + + return \OC_User::getUser(); + } + } + + return false; + } + + /** + * respond to a call + * @param OC_OCS_Result $result + * @param string $format the format xml|json + */ + public static function respond($result, $format='xml') { + $request = \OC::$server->getRequest(); + + // Send 401 headers if unauthorised + if($result->getStatusCode() === API::RESPOND_UNAUTHORISED) { + // If request comes from JS return dummy auth request + if($request->getHeader('X-Requested-With') === 'XMLHttpRequest') { + header('WWW-Authenticate: DummyBasic realm="Authorisation Required"'); + } else { + header('WWW-Authenticate: Basic realm="Authorisation Required"'); + } + header('HTTP/1.0 401 Unauthorized'); + } + + foreach($result->getHeaders() as $name => $value) { + header($name . ': ' . $value); + } + + $meta = $result->getMeta(); + $data = $result->getData(); + if (self::isV2($request)) { + $statusCode = self::mapStatusCodes($result->getStatusCode()); + if (!is_null($statusCode)) { + $meta['statuscode'] = $statusCode; + OC_Response::setStatus($statusCode); + } + } + + self::setContentType($format); + $body = self::renderResult($format, $meta, $data); + echo $body; + } + + /** + * @param XMLWriter $writer + */ + private static function toXML($array, $writer) { + foreach($array as $k => $v) { + if ($k[0] === '@') { + $writer->writeAttribute(substr($k, 1), $v); + continue; + } else if (is_numeric($k)) { + $k = 'element'; + } + if(is_array($v)) { + $writer->startElement($k); + self::toXML($v, $writer); + $writer->endElement(); + } else { + $writer->writeElement($k, $v); + } + } + } + + /** + * @return string + */ + public static function requestedFormat() { + $formats = array('json', 'xml'); + + $format = !empty($_GET['format']) && in_array($_GET['format'], $formats) ? $_GET['format'] : 'xml'; + return $format; + } + + /** + * Based on the requested format the response content type is set + * @param string $format + */ + public static function setContentType($format = null) { + $format = is_null($format) ? self::requestedFormat() : $format; + if ($format === 'xml') { + header('Content-type: text/xml; charset=UTF-8'); + return; + } + + if ($format === 'json') { + header('Content-Type: application/json; charset=utf-8'); + return; + } + + header('Content-Type: application/octet-stream; charset=utf-8'); + } + + /** + * @param \OCP\IRequest $request + * @return bool + */ + protected static function isV2(\OCP\IRequest $request) { + $script = $request->getScriptName(); + + return substr($script, -11) === '/ocs/v2.php'; + } + + /** + * @param integer $sc + * @return int + */ + public static function mapStatusCodes($sc) { + switch ($sc) { + case API::RESPOND_NOT_FOUND: + return Http::STATUS_NOT_FOUND; + case API::RESPOND_SERVER_ERROR: + return Http::STATUS_INTERNAL_SERVER_ERROR; + case API::RESPOND_UNKNOWN_ERROR: + return Http::STATUS_INTERNAL_SERVER_ERROR; + case API::RESPOND_UNAUTHORISED: + // already handled for v1 + return null; + case 100: + return Http::STATUS_OK; + } + // any 2xx, 4xx and 5xx will be used as is + if ($sc >= 200 && $sc < 600) { + return $sc; + } + + return Http::STATUS_BAD_REQUEST; + } + + /** + * @param string $format + * @return string + */ + public static function renderResult($format, $meta, $data) { + $response = array( + 'ocs' => array( + 'meta' => $meta, + 'data' => $data, + ), + ); + if ($format == 'json') { + return OC_JSON::encode($response); + } + + $writer = new XMLWriter(); + $writer->openMemory(); + $writer->setIndent(true); + $writer->startDocument(); + self::toXML($response, $writer); + $writer->endDocument(); + return $writer->outputMemory(true); + } +} diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php new file mode 100644 index 00000000000..246bf97ee91 --- /dev/null +++ b/lib/private/legacy/app.php @@ -0,0 +1,1284 @@ + + * @author Bart Visscher + * @author Bernhard Posselt + * @author Björn Schießle + * @author Borjan Tchakaloff + * @author Brice Maron + * @author Christopher Schäpers + * @author Felix Moeller + * @author Frank Karlitschek + * @author Georg Ehrke + * @author Jakob Sack + * @author Jan-Christoph Borchardt + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Kamil Domanski + * @author Lukas Reschke + * @author Markus Goetz + * @author Morris Jobke + * @author RealRancor + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Sam Tuke + * @author Thomas Müller + * @author Thomas Tanghus + * @author Tom Needham + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +use OC\App\DependencyAnalyzer; +use OC\App\Platform; +use OC\OCSClient; +use OC\Repair; + +/** + * This class manages the apps. It allows them to register and integrate in the + * ownCloud ecosystem. Furthermore, this class is responsible for installing, + * upgrading and removing apps. + */ +class OC_App { + static private $appVersion = []; + static private $adminForms = array(); + static private $personalForms = array(); + static private $appInfo = array(); + static private $appTypes = array(); + static private $loadedApps = array(); + static private $altLogin = array(); + const officialApp = 200; + + /** + * clean the appId + * + * @param string|boolean $app AppId that needs to be cleaned + * @return string + */ + public static function cleanAppId($app) { + return str_replace(array('\0', '/', '\\', '..'), '', $app); + } + + /** + * Check if an app is loaded + * + * @param string $app + * @return bool + */ + public static function isAppLoaded($app) { + return in_array($app, self::$loadedApps, true); + } + + /** + * loads all apps + * + * @param string[] | string | null $types + * @return bool + * + * This function walks through the ownCloud directory and loads all apps + * it can find. A directory contains an app if the file /appinfo/info.xml + * exists. + * + * if $types is set, only apps of those types will be loaded + */ + public static function loadApps($types = null) { + if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) { + return false; + } + // Load the enabled apps here + $apps = self::getEnabledApps(); + + // Add each apps' folder as allowed class path + foreach($apps as $app) { + $path = self::getAppPath($app); + if($path !== false) { + \OC::$loader->addValidRoot($path); + } + } + + // prevent app.php from printing output + ob_start(); + foreach ($apps as $app) { + if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) { + self::loadApp($app); + } + } + ob_end_clean(); + + return true; + } + + /** + * load a single app + * + * @param string $app + * @param bool $checkUpgrade whether an upgrade check should be done + * @throws \OC\NeedsUpdateException + */ + public static function loadApp($app, $checkUpgrade = true) { + self::$loadedApps[] = $app; + $appPath = self::getAppPath($app); + if($appPath === false) { + return; + } + \OC::$loader->addValidRoot($appPath); // in case someone calls loadApp() directly + if (is_file($appPath . '/appinfo/app.php')) { + \OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app); + if ($checkUpgrade and self::shouldUpgrade($app)) { + throw new \OC\NeedsUpdateException(); + } + self::requireAppFile($app); + if (self::isType($app, array('authentication'))) { + // since authentication apps affect the "is app enabled for group" check, + // the enabled apps cache needs to be cleared to make sure that the + // next time getEnableApps() is called it will also include apps that were + // enabled for groups + self::$enabledAppsCache = array(); + } + \OC::$server->getEventLogger()->end('load_app_' . $app); + } + } + + /** + * Load app.php from the given app + * + * @param string $app app name + */ + private static function requireAppFile($app) { + try { + // encapsulated here to avoid variable scope conflicts + require_once $app . '/appinfo/app.php'; + } catch (Error $ex) { + \OC::$server->getLogger()->logException($ex); + $blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps(); + if (!in_array($app, $blacklist)) { + self::disable($app); + } + } + } + + /** + * check if an app is of a specific type + * + * @param string $app + * @param string|array $types + * @return bool + */ + public static function isType($app, $types) { + if (is_string($types)) { + $types = array($types); + } + $appTypes = self::getAppTypes($app); + foreach ($types as $type) { + if (array_search($type, $appTypes) !== false) { + return true; + } + } + return false; + } + + /** + * get the types of an app + * + * @param string $app + * @return array + */ + private static function getAppTypes($app) { + //load the cache + if (count(self::$appTypes) == 0) { + self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types'); + } + + if (isset(self::$appTypes[$app])) { + return explode(',', self::$appTypes[$app]); + } else { + return array(); + } + } + + /** + * read app types from info.xml and cache them in the database + */ + public static function setAppTypes($app) { + $appData = self::getAppInfo($app); + if(!is_array($appData)) { + return; + } + + if (isset($appData['types'])) { + $appTypes = implode(',', $appData['types']); + } else { + $appTypes = ''; + } + + \OC::$server->getAppConfig()->setValue($app, 'types', $appTypes); + } + + /** + * check if app is shipped + * + * @param string $appId the id of the app to check + * @return bool + * + * Check if an app that is installed is a shipped app or installed from the appstore. + */ + public static function isShipped($appId) { + return \OC::$server->getAppManager()->isShipped($appId); + } + + /** + * get all enabled apps + */ + protected static $enabledAppsCache = array(); + + /** + * Returns apps enabled for the current user. + * + * @param bool $forceRefresh whether to refresh the cache + * @param bool $all whether to return apps for all users, not only the + * currently logged in one + * @return string[] + */ + public static function getEnabledApps($forceRefresh = false, $all = false) { + if (!\OC::$server->getSystemConfig()->getValue('installed', false)) { + return array(); + } + // in incognito mode or when logged out, $user will be false, + // which is also the case during an upgrade + $appManager = \OC::$server->getAppManager(); + if ($all) { + $user = null; + } else { + $user = \OC::$server->getUserSession()->getUser(); + } + + if (is_null($user)) { + $apps = $appManager->getInstalledApps(); + } else { + $apps = $appManager->getEnabledAppsForUser($user); + } + $apps = array_filter($apps, function ($app) { + return $app !== 'files';//we add this manually + }); + sort($apps); + array_unshift($apps, 'files'); + return $apps; + } + + /** + * checks whether or not an app is enabled + * + * @param string $app app + * @return bool + * + * This function checks whether or not an app is enabled. + */ + public static function isEnabled($app) { + return \OC::$server->getAppManager()->isEnabledForUser($app); + } + + /** + * enables an app + * + * @param mixed $app app + * @param array $groups (optional) when set, only these groups will have access to the app + * @throws \Exception + * @return void + * + * This function set an app as enabled in appconfig. + */ + public static function enable($app, $groups = null) { + self::$enabledAppsCache = array(); // flush + if (!OC_Installer::isInstalled($app)) { + $app = self::installApp($app); + } + + $appManager = \OC::$server->getAppManager(); + if (!is_null($groups)) { + $groupManager = \OC::$server->getGroupManager(); + $groupsList = []; + foreach ($groups as $group) { + $groupItem = $groupManager->get($group); + if ($groupItem instanceof \OCP\IGroup) { + $groupsList[] = $groupManager->get($group); + } + } + $appManager->enableAppForGroups($app, $groupsList); + } else { + $appManager->enableApp($app); + } + } + + /** + * @param string $app + * @return int + */ + private static function downloadApp($app) { + $ocsClient = new OCSClient( + \OC::$server->getHTTPClientService(), + \OC::$server->getConfig(), + \OC::$server->getLogger() + ); + $appData = $ocsClient->getApplication($app, \OCP\Util::getVersion()); + $download = $ocsClient->getApplicationDownload($app, \OCP\Util::getVersion()); + if(isset($download['downloadlink']) and $download['downloadlink']!='') { + // Replace spaces in download link without encoding entire URL + $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); + $info = array('source' => 'http', 'href' => $download['downloadlink'], 'appdata' => $appData); + $app = OC_Installer::installApp($info); + } + return $app; + } + + /** + * @param string $app + * @return bool + */ + public static function removeApp($app) { + if (self::isShipped($app)) { + return false; + } + + return OC_Installer::removeApp($app); + } + + /** + * This function set an app as disabled in appconfig. + * + * @param string $app app + * @throws Exception + */ + public static function disable($app) { + // Convert OCS ID to regular application identifier + if(self::getInternalAppIdByOcs($app) !== false) { + $app = self::getInternalAppIdByOcs($app); + } + + self::$enabledAppsCache = array(); // flush + // check if app is a shipped app or not. if not delete + \OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app)); + $appManager = \OC::$server->getAppManager(); + $appManager->disableApp($app); + } + + /** + * Returns the Settings Navigation + * + * @return string[] + * + * This function returns an array containing all settings pages added. The + * entries are sorted by the key 'order' ascending. + */ + public static function getSettingsNavigation() { + $l = \OC::$server->getL10N('lib'); + $urlGenerator = \OC::$server->getURLGenerator(); + + $settings = array(); + // by default, settings only contain the help menu + if (OC_Util::getEditionString() === '' && + \OC::$server->getSystemConfig()->getValue('knowledgebaseenabled', true) == true + ) { + $settings = array( + array( + "id" => "help", + "order" => 1000, + "href" => $urlGenerator->linkToRoute('settings_help'), + "name" => $l->t("Help"), + "icon" => $urlGenerator->imagePath("settings", "help.svg") + ) + ); + } + + // if the user is logged-in + if (OC_User::isLoggedIn()) { + // personal menu + $settings[] = array( + "id" => "personal", + "order" => 1, + "href" => $urlGenerator->linkToRoute('settings_personal'), + "name" => $l->t("Personal"), + "icon" => $urlGenerator->imagePath("settings", "personal.svg") + ); + + //SubAdmins are also allowed to access user management + $userObject = \OC::$server->getUserSession()->getUser(); + $isSubAdmin = false; + if($userObject !== null) { + $isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject); + } + if ($isSubAdmin) { + // admin users menu + $settings[] = array( + "id" => "core_users", + "order" => 2, + "href" => $urlGenerator->linkToRoute('settings_users'), + "name" => $l->t("Users"), + "icon" => $urlGenerator->imagePath("settings", "users.svg") + ); + } + + // if the user is an admin + if (OC_User::isAdminUser(OC_User::getUser())) { + // admin settings + $settings[] = array( + "id" => "admin", + "order" => 1000, + "href" => $urlGenerator->linkToRoute('settings_admin'), + "name" => $l->t("Admin"), + "icon" => $urlGenerator->imagePath("settings", "admin.svg") + ); + } + } + + $navigation = self::proceedNavigation($settings); + return $navigation; + } + + // This is private as well. It simply works, so don't ask for more details + private static function proceedNavigation($list) { + $activeApp = OC::$server->getNavigationManager()->getActiveEntry(); + foreach ($list as &$navEntry) { + if ($navEntry['id'] == $activeApp) { + $navEntry['active'] = true; + } else { + $navEntry['active'] = false; + } + } + unset($navEntry); + + usort($list, create_function('$a, $b', 'if( $a["order"] == $b["order"] ) {return 0;}elseif( $a["order"] < $b["order"] ) {return -1;}else{return 1;}')); + + return $list; + } + + /** + * Get the path where to install apps + * + * @return string|false + */ + public static function getInstallPath() { + if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) { + return false; + } + + foreach (OC::$APPSROOTS as $dir) { + if (isset($dir['writable']) && $dir['writable'] === true) { + return $dir['path']; + } + } + + \OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR); + return null; + } + + + /** + * search for an app in all app-directories + * + * @param string $appId + * @return false|string + */ + protected static function findAppInDirectories($appId) { + $sanitizedAppId = self::cleanAppId($appId); + if($sanitizedAppId !== $appId) { + return false; + } + static $app_dir = array(); + + if (isset($app_dir[$appId])) { + return $app_dir[$appId]; + } + + $possibleApps = array(); + foreach (OC::$APPSROOTS as $dir) { + if (file_exists($dir['path'] . '/' . $appId)) { + $possibleApps[] = $dir; + } + } + + if (empty($possibleApps)) { + return false; + } elseif (count($possibleApps) === 1) { + $dir = array_shift($possibleApps); + $app_dir[$appId] = $dir; + return $dir; + } else { + $versionToLoad = array(); + foreach ($possibleApps as $possibleApp) { + $version = self::getAppVersionByPath($possibleApp['path']); + if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) { + $versionToLoad = array( + 'dir' => $possibleApp, + 'version' => $version, + ); + } + } + $app_dir[$appId] = $versionToLoad['dir']; + return $versionToLoad['dir']; + //TODO - write test + } + } + + /** + * Get the directory for the given app. + * If the app is defined in multiple directories, the first one is taken. (false if not found) + * + * @param string $appId + * @return string|false + */ + public static function getAppPath($appId) { + if ($appId === null || trim($appId) === '') { + return false; + } + + if (($dir = self::findAppInDirectories($appId)) != false) { + return $dir['path'] . '/' . $appId; + } + return false; + } + + + /** + * check if an app's directory is writable + * + * @param string $appId + * @return bool + */ + public static function isAppDirWritable($appId) { + $path = self::getAppPath($appId); + return ($path !== false) ? is_writable($path) : false; + } + + /** + * Get the path for the given app on the access + * If the app is defined in multiple directories, the first one is taken. (false if not found) + * + * @param string $appId + * @return string|false + */ + public static function getAppWebPath($appId) { + if (($dir = self::findAppInDirectories($appId)) != false) { + return OC::$WEBROOT . $dir['url'] . '/' . $appId; + } + return false; + } + + /** + * get the last version of the app from appinfo/info.xml + * + * @param string $appId + * @return string + */ + public static function getAppVersion($appId) { + if (!isset(self::$appVersion[$appId])) { + $file = self::getAppPath($appId); + self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0'; + } + return self::$appVersion[$appId]; + } + + /** + * get app's version based on it's path + * + * @param string $path + * @return string + */ + public static function getAppVersionByPath($path) { + $infoFile = $path . '/appinfo/info.xml'; + $appData = self::getAppInfo($infoFile, true); + return isset($appData['version']) ? $appData['version'] : ''; + } + + + /** + * Read all app metadata from the info.xml file + * + * @param string $appId id of the app or the path of the info.xml file + * @param boolean $path (optional) + * @return array|null + * @note all data is read from info.xml, not just pre-defined fields + */ + public static function getAppInfo($appId, $path = false) { + if ($path) { + $file = $appId; + } else { + if (isset(self::$appInfo[$appId])) { + return self::$appInfo[$appId]; + } + $appPath = self::getAppPath($appId); + if($appPath === false) { + return null; + } + $file = $appPath . '/appinfo/info.xml'; + } + + $parser = new \OC\App\InfoParser(\OC::$server->getURLGenerator()); + $data = $parser->parse($file); + + if (is_array($data)) { + $data = OC_App::parseAppInfo($data); + } + if(isset($data['ocsid'])) { + $storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid'); + if($storedId !== '' && $storedId !== $data['ocsid']) { + $data['ocsid'] = $storedId; + } + } + + self::$appInfo[$appId] = $data; + + return $data; + } + + /** + * Returns the navigation + * + * @return array + * + * This function returns an array containing all entries added. The + * entries are sorted by the key 'order' ascending. Additional to the keys + * given for each app the following keys exist: + * - active: boolean, signals if the user is on this navigation entry + */ + public static function getNavigation() { + $entries = OC::$server->getNavigationManager()->getAll(); + $navigation = self::proceedNavigation($entries); + return $navigation; + } + + /** + * get the id of loaded app + * + * @return string + */ + public static function getCurrentApp() { + $request = \OC::$server->getRequest(); + $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1); + $topFolder = substr($script, 0, strpos($script, '/')); + if (empty($topFolder)) { + $path_info = $request->getPathInfo(); + if ($path_info) { + $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1); + } + } + if ($topFolder == 'apps') { + $length = strlen($topFolder); + return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1); + } else { + return $topFolder; + } + } + + /** + * @param string $type + * @return array + */ + public static function getForms($type) { + $forms = array(); + switch ($type) { + case 'admin': + $source = self::$adminForms; + break; + case 'personal': + $source = self::$personalForms; + break; + default: + return array(); + } + foreach ($source as $form) { + $forms[] = include $form; + } + return $forms; + } + + /** + * register an admin form to be shown + * + * @param string $app + * @param string $page + */ + public static function registerAdmin($app, $page) { + self::$adminForms[] = $app . '/' . $page . '.php'; + } + + /** + * register a personal form to be shown + * @param string $app + * @param string $page + */ + public static function registerPersonal($app, $page) { + self::$personalForms[] = $app . '/' . $page . '.php'; + } + + /** + * @param array $entry + */ + public static function registerLogIn(array $entry) { + self::$altLogin[] = $entry; + } + + /** + * @return array + */ + public static function getAlternativeLogIns() { + return self::$altLogin; + } + + /** + * get a list of all apps in the apps folder + * + * @return array an array of app names (string IDs) + * @todo: change the name of this method to getInstalledApps, which is more accurate + */ + public static function getAllApps() { + + $apps = array(); + + foreach (OC::$APPSROOTS as $apps_dir) { + if (!is_readable($apps_dir['path'])) { + \OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN); + continue; + } + $dh = opendir($apps_dir['path']); + + if (is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + + if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) { + + $apps[] = $file; + } + } + } + } + + return $apps; + } + + /** + * List all apps, this is used in apps.php + * + * @param bool $onlyLocal + * @param bool $includeUpdateInfo Should we check whether there is an update + * in the app store? + * @param OCSClient $ocsClient + * @return array + */ + public static function listAllApps($onlyLocal = false, + $includeUpdateInfo = true, + OCSClient $ocsClient) { + $installedApps = OC_App::getAllApps(); + + //TODO which apps do we want to blacklist and how do we integrate + // blacklisting with the multi apps folder feature? + + //we don't want to show configuration for these + $blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps(); + $appList = array(); + + foreach ($installedApps as $app) { + if (array_search($app, $blacklist) === false) { + + $info = OC_App::getAppInfo($app); + if (!is_array($info)) { + \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR); + continue; + } + + if (!isset($info['name'])) { + \OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR); + continue; + } + + $enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'no'); + $info['groups'] = null; + if ($enabled === 'yes') { + $active = true; + } else if ($enabled === 'no') { + $active = false; + } else { + $active = true; + $info['groups'] = $enabled; + } + + $info['active'] = $active; + + if (self::isShipped($app)) { + $info['internal'] = true; + $info['level'] = self::officialApp; + $info['removable'] = false; + } else { + $info['internal'] = false; + $info['removable'] = true; + } + + $info['update'] = ($includeUpdateInfo) ? OC_Installer::isUpdateAvailable($app) : null; + + $appPath = self::getAppPath($app); + if($appPath !== false) { + $appIcon = $appPath . '/img/' . $app . '.svg'; + if (file_exists($appIcon)) { + $info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg'); + $info['previewAsIcon'] = true; + } else { + $appIcon = $appPath . '/img/app.svg'; + if (file_exists($appIcon)) { + $info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg'); + $info['previewAsIcon'] = true; + } + } + } + $info['version'] = OC_App::getAppVersion($app); + $appList[] = $info; + } + } + if ($onlyLocal) { + $remoteApps = []; + } else { + $remoteApps = OC_App::getAppstoreApps('approved', null, $ocsClient); + } + if ($remoteApps) { + // Remove duplicates + foreach ($appList as $app) { + foreach ($remoteApps AS $key => $remote) { + if ($app['name'] === $remote['name'] || + (isset($app['ocsid']) && + $app['ocsid'] === $remote['id']) + ) { + unset($remoteApps[$key]); + } + } + } + $combinedApps = array_merge($appList, $remoteApps); + } else { + $combinedApps = $appList; + } + + return $combinedApps; + } + + /** + * Returns the internal app ID or false + * @param string $ocsID + * @return string|false + */ + public static function getInternalAppIdByOcs($ocsID) { + if(is_numeric($ocsID)) { + $idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid'); + if(array_search($ocsID, $idArray)) { + return array_search($ocsID, $idArray); + } + } + return false; + } + + /** + * Get a list of all apps on the appstore + * @param string $filter + * @param string|null $category + * @param OCSClient $ocsClient + * @return array|bool multi-dimensional array of apps. + * Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description + */ + public static function getAppstoreApps($filter = 'approved', + $category = null, + OCSClient $ocsClient) { + $categories = [$category]; + + if (is_null($category)) { + $categoryNames = $ocsClient->getCategories(\OCP\Util::getVersion()); + if (is_array($categoryNames)) { + // Check that categories of apps were retrieved correctly + if (!$categories = array_keys($categoryNames)) { + return false; + } + } else { + return false; + } + } + + $page = 0; + $remoteApps = $ocsClient->getApplications($categories, $page, $filter, \OCP\Util::getVersion()); + $apps = []; + $i = 0; + $l = \OC::$server->getL10N('core'); + foreach ($remoteApps as $app) { + $potentialCleanId = self::getInternalAppIdByOcs($app['id']); + // enhance app info (for example the description) + $apps[$i] = OC_App::parseAppInfo($app); + $apps[$i]['author'] = $app['personid']; + $apps[$i]['ocs_id'] = $app['id']; + $apps[$i]['internal'] = 0; + $apps[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false; + $apps[$i]['update'] = false; + $apps[$i]['groups'] = false; + $apps[$i]['score'] = $app['score']; + $apps[$i]['removable'] = false; + if ($app['label'] == 'recommended') { + $apps[$i]['internallabel'] = (string)$l->t('Recommended'); + $apps[$i]['internalclass'] = 'recommendedapp'; + } + + // Apps from the appstore are always assumed to be compatible with the + // the current release as the initial filtering is done on the appstore + $apps[$i]['dependencies']['owncloud']['@attributes']['min-version'] = implode('.', \OCP\Util::getVersion()); + $apps[$i]['dependencies']['owncloud']['@attributes']['max-version'] = implode('.', \OCP\Util::getVersion()); + + $i++; + } + + + + if (empty($apps)) { + return false; + } else { + return $apps; + } + } + + public static function shouldUpgrade($app) { + $versions = self::getAppVersions(); + $currentVersion = OC_App::getAppVersion($app); + if ($currentVersion && isset($versions[$app])) { + $installedVersion = $versions[$app]; + if (version_compare($currentVersion, $installedVersion, '>')) { + return true; + } + } + return false; + } + + /** + * Adjust the number of version parts of $version1 to match + * the number of version parts of $version2. + * + * @param string $version1 version to adjust + * @param string $version2 version to take the number of parts from + * @return string shortened $version1 + */ + private static function adjustVersionParts($version1, $version2) { + $version1 = explode('.', $version1); + $version2 = explode('.', $version2); + // reduce $version1 to match the number of parts in $version2 + while (count($version1) > count($version2)) { + array_pop($version1); + } + // if $version1 does not have enough parts, add some + while (count($version1) < count($version2)) { + $version1[] = '0'; + } + return implode('.', $version1); + } + + /** + * Check whether the current ownCloud version matches the given + * application's version requirements. + * + * The comparison is made based on the number of parts that the + * app info version has. For example for ownCloud 6.0.3 if the + * app info version is expecting version 6.0, the comparison is + * made on the first two parts of the ownCloud version. + * This means that it's possible to specify "requiremin" => 6 + * and "requiremax" => 6 and it will still match ownCloud 6.0.3. + * + * @param string $ocVersion ownCloud version to check against + * @param array $appInfo app info (from xml) + * + * @return boolean true if compatible, otherwise false + */ + public static function isAppCompatible($ocVersion, $appInfo) { + $requireMin = ''; + $requireMax = ''; + if (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) { + $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version']; + } else if (isset($appInfo['requiremin'])) { + $requireMin = $appInfo['requiremin']; + } else if (isset($appInfo['require'])) { + $requireMin = $appInfo['require']; + } + + if (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) { + $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version']; + } else if (isset($appInfo['requiremax'])) { + $requireMax = $appInfo['requiremax']; + } + + if (is_array($ocVersion)) { + $ocVersion = implode('.', $ocVersion); + } + + if (!empty($requireMin) + && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<') + ) { + + return false; + } + + if (!empty($requireMax) + && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>') + ) { + return false; + } + + return true; + } + + /** + * get the installed version of all apps + */ + public static function getAppVersions() { + static $versions; + + if(!$versions) { + $appConfig = \OC::$server->getAppConfig(); + $versions = $appConfig->getValues(false, 'installed_version'); + } + return $versions; + } + + /** + * @param string $app + * @return bool + * @throws Exception if app is not compatible with this version of ownCloud + * @throws Exception if no app-name was specified + */ + public static function installApp($app) { + $l = \OC::$server->getL10N('core'); + $config = \OC::$server->getConfig(); + $ocsClient = new OCSClient( + \OC::$server->getHTTPClientService(), + $config, + \OC::$server->getLogger() + ); + $appData = $ocsClient->getApplication($app, \OCP\Util::getVersion()); + + // check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string + if (!is_numeric($app)) { + $shippedVersion = self::getAppVersion($app); + if ($appData && version_compare($shippedVersion, $appData['version'], '<')) { + $app = self::downloadApp($app); + } else { + $app = OC_Installer::installShippedApp($app); + } + } else { + // Maybe the app is already installed - compare the version in this + // case and use the local already installed one. + // FIXME: This is a horrible hack. I feel sad. The god of code cleanness may forgive me. + $internalAppId = self::getInternalAppIdByOcs($app); + if($internalAppId !== false) { + if($appData && version_compare(\OC_App::getAppVersion($internalAppId), $appData['version'], '<')) { + $app = self::downloadApp($app); + } else { + self::enable($internalAppId); + $app = $internalAppId; + } + } else { + $app = self::downloadApp($app); + } + } + + if ($app !== false) { + // check if the app is compatible with this version of ownCloud + $info = self::getAppInfo($app); + if(!is_array($info)) { + throw new \Exception( + $l->t('App "%s" cannot be installed because appinfo file cannot be read.', + [$info['name']] + ) + ); + } + + $version = \OCP\Util::getVersion(); + if (!self::isAppCompatible($version, $info)) { + throw new \Exception( + $l->t('App "%s" cannot be installed because it is not compatible with this version of ownCloud.', + array($info['name']) + ) + ); + } + + // check for required dependencies + $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l); + $missing = $dependencyAnalyzer->analyze($info); + if (!empty($missing)) { + $missingMsg = join(PHP_EOL, $missing); + throw new \Exception( + $l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s', + array($info['name'], $missingMsg) + ) + ); + } + + $config->setAppValue($app, 'enabled', 'yes'); + if (isset($appData['id'])) { + $config->setAppValue($app, 'ocsid', $appData['id']); + } + \OC_Hook::emit('OC_App', 'post_enable', array('app' => $app)); + } else { + throw new \Exception($l->t("No app name specified")); + } + + return $app; + } + + /** + * update the database for the app and call the update script + * + * @param string $appId + * @return bool + */ + public static function updateApp($appId) { + $appPath = self::getAppPath($appId); + if($appPath === false) { + return false; + } + $appData = self::getAppInfo($appId); + self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']); + if (file_exists($appPath . '/appinfo/database.xml')) { + OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml'); + } + self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']); + self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']); + unset(self::$appVersion[$appId]); + // run upgrade code + if (file_exists($appPath . '/appinfo/update.php')) { + self::loadApp($appId, false); + include $appPath . '/appinfo/update.php'; + } + + //set remote/public handlers + if (array_key_exists('ocsid', $appData)) { + \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']); + } elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) { + \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid'); + } + foreach ($appData['remote'] as $name => $path) { + \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path); + } + foreach ($appData['public'] as $name => $path) { + \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path); + } + + self::setAppTypes($appId); + + $version = \OC_App::getAppVersion($appId); + \OC::$server->getAppConfig()->setValue($appId, 'installed_version', $version); + + return true; + } + + /** + * @param string $appId + * @param string[] $steps + * @throws \OC\NeedsUpdateException + */ + private static function executeRepairSteps($appId, array $steps) { + if (empty($steps)) { + return; + } + // load the app + self::loadApp($appId, false); + + $dispatcher = OC::$server->getEventDispatcher(); + + // load the steps + $r = new Repair([], $dispatcher); + foreach ($steps as $step) { + try { + $r->addStep($step); + } catch (Exception $ex) { + $r->emit('\OC\Repair', 'error', [$ex->getMessage()]); + \OC::$server->getLogger()->logException($ex); + } + } + // run the steps + $r->run(); + } + + /** + * @param string $appId + * @param string[] $steps + */ + private static function setupLiveMigrations($appId, array $steps) { + $queue = \OC::$server->getJobList(); + foreach ($steps as $step) { + $queue->add('OC\Migration\BackgroundRepair', [ + 'app' => $appId, + 'step' => $step]); + } + } + + /** + * @param string $appId + * @return \OC\Files\View|false + */ + public static function getStorage($appId) { + if (OC_App::isEnabled($appId)) { //sanity check + if (OC_User::isLoggedIn()) { + $view = new \OC\Files\View('/' . OC_User::getUser()); + if (!$view->file_exists($appId)) { + $view->mkdir($appId); + } + return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId); + } else { + \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR); + return false; + } + } else { + \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR); + return false; + } + } + + /** + * parses the app data array and enhanced the 'description' value + * + * @param array $data the app data + * @return array improved app data + */ + public static function parseAppInfo(array $data) { + + // just modify the description if it is available + // otherwise this will create a $data element with an empty 'description' + if (isset($data['description'])) { + if (is_string($data['description'])) { + // sometimes the description contains line breaks and they are then also + // shown in this way in the app management which isn't wanted as HTML + // manages line breaks itself + + // first of all we split on empty lines + $paragraphs = preg_split("!\n[[:space:]]*\n!mu", $data['description']); + + $result = []; + foreach ($paragraphs as $value) { + // replace multiple whitespace (tabs, space, newlines) inside a paragraph + // with a single space - also trims whitespace + $result[] = trim(preg_replace('![[:space:]]+!mu', ' ', $value)); + } + + // join the single paragraphs with a empty line in between + $data['description'] = implode("\n\n", $result); + + } else { + $data['description'] = ''; + } + } + + return $data; + } +} diff --git a/lib/private/legacy/archive.php b/lib/private/legacy/archive.php new file mode 100644 index 00000000000..62512d1448a --- /dev/null +++ b/lib/private/legacy/archive.php @@ -0,0 +1,158 @@ + + * @author Bart Visscher + * @author Christopher Schäpers + * @author Felix Moeller + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +abstract class OC_Archive{ + /** + * Open any of the supported archive types + * + * @param string $path + * @return OC_Archive|void + */ + public static function open($path) { + $mime = \OC::$server->getMimeTypeDetector()->detect($path); + + switch($mime) { + case 'application/zip': + return new OC_Archive_ZIP($path); + case 'application/x-gzip': + return new OC_Archive_TAR($path); + case 'application/x-bzip2': + return new OC_Archive_TAR($path); + } + } + + /** + * @param $source + */ + abstract function __construct($source); + /** + * add an empty folder to the archive + * @param string $path + * @return bool + */ + abstract function addFolder($path); + /** + * add a file to the archive + * @param string $path + * @param string $source either a local file or string data + * @return bool + */ + abstract function addFile($path, $source=''); + /** + * rename a file or folder in the archive + * @param string $source + * @param string $dest + * @return bool + */ + abstract function rename($source, $dest); + /** + * get the uncompressed size of a file in the archive + * @param string $path + * @return int + */ + abstract function filesize($path); + /** + * get the last modified time of a file in the archive + * @param string $path + * @return int + */ + abstract function mtime($path); + /** + * get the files in a folder + * @param string $path + * @return array + */ + abstract function getFolder($path); + /** + * get all files in the archive + * @return array + */ + abstract function getFiles(); + /** + * get the content of a file + * @param string $path + * @return string + */ + abstract function getFile($path); + /** + * extract a single file from the archive + * @param string $path + * @param string $dest + * @return bool + */ + abstract function extractFile($path, $dest); + /** + * extract the archive + * @param string $dest + * @return bool + */ + abstract function extract($dest); + /** + * check if a file or folder exists in the archive + * @param string $path + * @return bool + */ + abstract function fileExists($path); + /** + * remove a file or folder from the archive + * @param string $path + * @return bool + */ + abstract function remove($path); + /** + * get a file handler + * @param string $path + * @param string $mode + * @return resource + */ + abstract function getStream($path, $mode); + /** + * add a folder and all its content + * @param string $path + * @param string $source + * @return boolean|null + */ + function addRecursive($path, $source) { + $dh = opendir($source); + if(is_resource($dh)) { + $this->addFolder($path); + while (($file = readdir($dh)) !== false) { + if($file=='.' or $file=='..') { + continue; + } + if(is_dir($source.'/'.$file)) { + $this->addRecursive($path.'/'.$file, $source.'/'.$file); + }else{ + $this->addFile($path.'/'.$file, $source.'/'.$file); + } + } + } + } +} diff --git a/lib/private/legacy/db.php b/lib/private/legacy/db.php new file mode 100644 index 00000000000..5d612312919 --- /dev/null +++ b/lib/private/legacy/db.php @@ -0,0 +1,258 @@ + + * @author Bart Visscher + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * This class manages the access to the database. It basically is a wrapper for + * Doctrine with some adaptions. + */ +class OC_DB { + + /** + * get MDB2 schema manager + * + * @return \OC\DB\MDB2SchemaManager + */ + private static function getMDB2SchemaManager() { + return new \OC\DB\MDB2SchemaManager(\OC::$server->getDatabaseConnection()); + } + + /** + * Prepare a SQL query + * @param string $query Query string + * @param int $limit + * @param int $offset + * @param bool $isManipulation + * @throws \OC\DatabaseException + * @return OC_DB_StatementWrapper prepared SQL query + * + * SQL query via Doctrine prepare(), needs to be execute()'d! + */ + static public function prepare( $query , $limit = null, $offset = null, $isManipulation = null) { + $connection = \OC::$server->getDatabaseConnection(); + + if ($isManipulation === null) { + //try to guess, so we return the number of rows on manipulations + $isManipulation = self::isManipulation($query); + } + + // return the result + try { + $result =$connection->prepare($query, $limit, $offset); + } catch (\Doctrine\DBAL\DBALException $e) { + throw new \OC\DatabaseException($e->getMessage(), $query); + } + // differentiate between query and manipulation + $result = new OC_DB_StatementWrapper($result, $isManipulation); + return $result; + } + + /** + * tries to guess the type of statement based on the first 10 characters + * the current check allows some whitespace but does not work with IF EXISTS or other more complex statements + * + * @param string $sql + * @return bool + */ + static public function isManipulation( $sql ) { + $selectOccurrence = stripos($sql, 'SELECT'); + if ($selectOccurrence !== false && $selectOccurrence < 10) { + return false; + } + $insertOccurrence = stripos($sql, 'INSERT'); + if ($insertOccurrence !== false && $insertOccurrence < 10) { + return true; + } + $updateOccurrence = stripos($sql, 'UPDATE'); + if ($updateOccurrence !== false && $updateOccurrence < 10) { + return true; + } + $deleteOccurrence = stripos($sql, 'DELETE'); + if ($deleteOccurrence !== false && $deleteOccurrence < 10) { + return true; + } + return false; + } + + /** + * execute a prepared statement, on error write log and throw exception + * @param mixed $stmt OC_DB_StatementWrapper, + * an array with 'sql' and optionally 'limit' and 'offset' keys + * .. or a simple sql query string + * @param array $parameters + * @return OC_DB_StatementWrapper + * @throws \OC\DatabaseException + */ + static public function executeAudited( $stmt, array $parameters = null) { + if (is_string($stmt)) { + // convert to an array with 'sql' + if (stripos($stmt, 'LIMIT') !== false) { //OFFSET requires LIMIT, so we only need to check for LIMIT + // TODO try to convert LIMIT OFFSET notation to parameters + $message = 'LIMIT and OFFSET are forbidden for portability reasons,' + . ' pass an array with \'limit\' and \'offset\' instead'; + throw new \OC\DatabaseException($message); + } + $stmt = array('sql' => $stmt, 'limit' => null, 'offset' => null); + } + if (is_array($stmt)) { + // convert to prepared statement + if ( ! array_key_exists('sql', $stmt) ) { + $message = 'statement array must at least contain key \'sql\''; + throw new \OC\DatabaseException($message); + } + if ( ! array_key_exists('limit', $stmt) ) { + $stmt['limit'] = null; + } + if ( ! array_key_exists('limit', $stmt) ) { + $stmt['offset'] = null; + } + $stmt = self::prepare($stmt['sql'], $stmt['limit'], $stmt['offset']); + } + self::raiseExceptionOnError($stmt, 'Could not prepare statement'); + if ($stmt instanceof OC_DB_StatementWrapper) { + $result = $stmt->execute($parameters); + self::raiseExceptionOnError($result, 'Could not execute statement'); + } else { + if (is_object($stmt)) { + $message = 'Expected a prepared statement or array got ' . get_class($stmt); + } else { + $message = 'Expected a prepared statement or array got ' . gettype($stmt); + } + throw new \OC\DatabaseException($message); + } + return $result; + } + + /** + * saves database schema to xml file + * @param string $file name of file + * @param int $mode + * @return bool + * + * TODO: write more documentation + */ + public static function getDbStructure($file) { + $schemaManager = self::getMDB2SchemaManager(); + return $schemaManager->getDbStructure($file); + } + + /** + * Creates tables from XML file + * @param string $file file to read structure from + * @return bool + * + * TODO: write more documentation + */ + public static function createDbFromStructure( $file ) { + $schemaManager = self::getMDB2SchemaManager(); + $result = $schemaManager->createDbFromStructure($file); + return $result; + } + + /** + * update the database schema + * @param string $file file to read structure from + * @throws Exception + * @return string|boolean + */ + public static function updateDbFromStructure($file) { + $schemaManager = self::getMDB2SchemaManager(); + try { + $result = $schemaManager->updateDbFromStructure($file); + } catch (Exception $e) { + \OCP\Util::writeLog('core', 'Failed to update database structure ('.$e.')', \OCP\Util::FATAL); + throw $e; + } + return $result; + } + + /** + * simulate the database schema update + * @param string $file file to read structure from + * @throws Exception + * @return string|boolean + */ + public static function simulateUpdateDbFromStructure($file) { + $schemaManager = self::getMDB2SchemaManager(); + try { + $result = $schemaManager->simulateUpdateDbFromStructure($file); + } catch (Exception $e) { + \OCP\Util::writeLog('core', 'Simulated database structure update failed ('.$e.')', \OCP\Util::FATAL); + throw $e; + } + return $result; + } + + /** + * remove all tables defined in a database structure xml file + * @param string $file the xml file describing the tables + */ + public static function removeDBStructure($file) { + $schemaManager = self::getMDB2SchemaManager(); + $schemaManager->removeDBStructure($file); + } + + /** + * check if a result is an error and throws an exception, works with \Doctrine\DBAL\DBALException + * @param mixed $result + * @param string $message + * @return void + * @throws \OC\DatabaseException + */ + public static function raiseExceptionOnError($result, $message = null) { + if($result === false) { + if ($message === null) { + $message = self::getErrorMessage(); + } else { + $message .= ', Root cause:' . self::getErrorMessage(); + } + throw new \OC\DatabaseException($message, \OC::$server->getDatabaseConnection()->errorCode()); + } + } + + /** + * returns the error code and message as a string for logging + * works with DoctrineException + * @return string + */ + public static function getErrorMessage() { + $connection = \OC::$server->getDatabaseConnection(); + return $connection->getError(); + } + + /** + * Checks if a table exists in the database - the database prefix will be prepended + * + * @param string $table + * @return bool + * @throws \OC\DatabaseException + */ + public static function tableExists($table) { + $connection = \OC::$server->getDatabaseConnection(); + return $connection->tableExists($table); + } +} diff --git a/lib/private/legacy/defaults.php b/lib/private/legacy/defaults.php new file mode 100644 index 00000000000..43e8c8082cc --- /dev/null +++ b/lib/private/legacy/defaults.php @@ -0,0 +1,286 @@ + + * @author Jan-Christoph Borchardt + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Pascal de Bruijn + * @author Robin Appelman + * @author Robin McCorkell + * @author scolebrook + * @author Thomas Müller + * @author Volkan Gezer + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +class OC_Defaults { + + private $theme; + private $l; + + private $defaultEntity; + private $defaultName; + private $defaultTitle; + private $defaultBaseUrl; + private $defaultSyncClientUrl; + private $defaultiOSClientUrl; + private $defaultiTunesAppId; + private $defaultAndroidClientUrl; + private $defaultDocBaseUrl; + private $defaultDocVersion; + private $defaultSlogan; + private $defaultLogoClaim; + private $defaultMailHeaderColor; + + function __construct() { + $this->l = \OC::$server->getL10N('lib'); + $version = \OCP\Util::getVersion(); + + $this->defaultEntity = 'ownCloud'; /* e.g. company name, used for footers and copyright notices */ + $this->defaultName = 'ownCloud'; /* short name, used when referring to the software */ + $this->defaultTitle = 'ownCloud'; /* can be a longer name, for titles */ + $this->defaultBaseUrl = 'https://owncloud.org'; + $this->defaultSyncClientUrl = 'https://owncloud.org/sync-clients/'; + $this->defaultiOSClientUrl = 'https://itunes.apple.com/us/app/owncloud/id543672169?mt=8'; + $this->defaultiTunesAppId = '543672169'; + $this->defaultAndroidClientUrl = 'https://play.google.com/store/apps/details?id=com.owncloud.android'; + $this->defaultDocBaseUrl = 'https://doc.owncloud.org'; + $this->defaultDocVersion = $version[0] . '.' . $version[1]; // used to generate doc links + $this->defaultSlogan = $this->l->t('web services under your control'); + $this->defaultLogoClaim = ''; + $this->defaultMailHeaderColor = '#1d2d44'; /* header color of mail notifications */ + + $themePath = OC::$SERVERROOT . '/themes/' . OC_Util::getTheme() . '/defaults.php'; + if (file_exists($themePath)) { + // prevent defaults.php from printing output + ob_start(); + require_once $themePath; + ob_end_clean(); + if (class_exists('OC_Theme')) { + $this->theme = new OC_Theme(); + } + } + } + + /** + * @param string $method + */ + private function themeExist($method) { + if (isset($this->theme) && method_exists($this->theme, $method)) { + return true; + } + return false; + } + + /** + * Returns the base URL + * @return string URL + */ + public function getBaseUrl() { + if ($this->themeExist('getBaseUrl')) { + return $this->theme->getBaseUrl(); + } else { + return $this->defaultBaseUrl; + } + } + + /** + * Returns the URL where the sync clients are listed + * @return string URL + */ + public function getSyncClientUrl() { + if ($this->themeExist('getSyncClientUrl')) { + return $this->theme->getSyncClientUrl(); + } else { + return $this->defaultSyncClientUrl; + } + } + + /** + * Returns the URL to the App Store for the iOS Client + * @return string URL + */ + public function getiOSClientUrl() { + if ($this->themeExist('getiOSClientUrl')) { + return $this->theme->getiOSClientUrl(); + } else { + return $this->defaultiOSClientUrl; + } + } + + /** + * Returns the AppId for the App Store for the iOS Client + * @return string AppId + */ + public function getiTunesAppId() { + if ($this->themeExist('getiTunesAppId')) { + return $this->theme->getiTunesAppId(); + } else { + return $this->defaultiTunesAppId; + } + } + + /** + * Returns the URL to Google Play for the Android Client + * @return string URL + */ + public function getAndroidClientUrl() { + if ($this->themeExist('getAndroidClientUrl')) { + return $this->theme->getAndroidClientUrl(); + } else { + return $this->defaultAndroidClientUrl; + } + } + + /** + * Returns the documentation URL + * @return string URL + */ + public function getDocBaseUrl() { + if ($this->themeExist('getDocBaseUrl')) { + return $this->theme->getDocBaseUrl(); + } else { + return $this->defaultDocBaseUrl; + } + } + + /** + * Returns the title + * @return string title + */ + public function getTitle() { + if ($this->themeExist('getTitle')) { + return $this->theme->getTitle(); + } else { + return $this->defaultTitle; + } + } + + /** + * Returns the short name of the software + * @return string title + */ + public function getName() { + if ($this->themeExist('getName')) { + return $this->theme->getName(); + } else { + return $this->defaultName; + } + } + + /** + * Returns the short name of the software containing HTML strings + * @return string title + */ + public function getHTMLName() { + if ($this->themeExist('getHTMLName')) { + return $this->theme->getHTMLName(); + } else { + return $this->defaultName; + } + } + + /** + * Returns entity (e.g. company name) - used for footer, copyright + * @return string entity name + */ + public function getEntity() { + if ($this->themeExist('getEntity')) { + return $this->theme->getEntity(); + } else { + return $this->defaultEntity; + } + } + + /** + * Returns slogan + * @return string slogan + */ + public function getSlogan() { + if ($this->themeExist('getSlogan')) { + return $this->theme->getSlogan(); + } else { + return $this->defaultSlogan; + } + } + + /** + * Returns logo claim + * @return string logo claim + */ + public function getLogoClaim() { + if ($this->themeExist('getLogoClaim')) { + return $this->theme->getLogoClaim(); + } else { + return $this->defaultLogoClaim; + } + } + + /** + * Returns short version of the footer + * @return string short footer + */ + public function getShortFooter() { + if ($this->themeExist('getShortFooter')) { + $footer = $this->theme->getShortFooter(); + } else { + $footer = '' .$this->getEntity() . ''. + ' – ' . $this->getSlogan(); + } + + return $footer; + } + + /** + * Returns long version of the footer + * @return string long footer + */ + public function getLongFooter() { + if ($this->themeExist('getLongFooter')) { + $footer = $this->theme->getLongFooter(); + } else { + $footer = $this->getShortFooter(); + } + + return $footer; + } + + /** + * @param string $key + */ + public function buildDocLinkToKey($key) { + if ($this->themeExist('buildDocLinkToKey')) { + return $this->theme->buildDocLinkToKey($key); + } + return $this->getDocBaseUrl() . '/server/' . $this->defaultDocVersion . '/go.php?to=' . $key; + } + + /** + * Returns mail header color + * @return string + */ + public function getMailHeaderColor() { + if ($this->themeExist('getMailHeaderColor')) { + return $this->theme->getMailHeaderColor(); + } else { + return $this->defaultMailHeaderColor; + } + } + +} diff --git a/lib/private/legacy/eventsource.php b/lib/private/legacy/eventsource.php new file mode 100644 index 00000000000..f567d1e6ca5 --- /dev/null +++ b/lib/private/legacy/eventsource.php @@ -0,0 +1,125 @@ + + * @author Felix Moeller + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * wrapper for server side events (http://en.wikipedia.org/wiki/Server-sent_events) + * includes a fallback for older browsers and IE + * + * use server side events with caution, to many open requests can hang the server + */ +class OC_EventSource implements \OCP\IEventSource { + /** + * @var bool + */ + private $fallback; + + /** + * @var int + */ + private $fallBackId = 0; + + /** + * @var bool + */ + private $started = false; + + protected function init() { + if ($this->started) { + return; + } + $this->started = true; + + // prevent php output buffering, caching and nginx buffering + OC_Util::obEnd(); + header('Cache-Control: no-cache'); + header('X-Accel-Buffering: no'); + $this->fallback = isset($_GET['fallback']) and $_GET['fallback'] == 'true'; + if ($this->fallback) { + $this->fallBackId = (int)$_GET['fallback_id']; + /** + * FIXME: The default content-security-policy of ownCloud forbids inline + * JavaScript for security reasons. IE starting on Windows 10 will + * however also obey the CSP which will break the event source fallback. + * + * As a workaround thus we set a custom policy which allows the execution + * of inline JavaScript. + * + * @link https://github.com/owncloud/core/issues/14286 + */ + header("Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline'"); + header("Content-Type: text/html"); + echo str_repeat('' . PHP_EOL, 10); //dummy data to keep IE happy + } else { + header("Content-Type: text/event-stream"); + } + if (!(\OC::$server->getRequest()->passesCSRFCheck())) { + $this->send('error', 'Possible CSRF attack. Connection will be closed.'); + $this->close(); + exit(); + } + flush(); + } + + /** + * send a message to the client + * + * @param string $type + * @param mixed $data + * + * @throws \BadMethodCallException + * if only one parameter is given, a typeless message will be send with that parameter as data + */ + public function send($type, $data = null) { + if ($data and !preg_match('/^[A-Za-z0-9_]+$/', $type)) { + throw new BadMethodCallException('Type needs to be alphanumeric ('. $type .')'); + } + $this->init(); + if (is_null($data)) { + $data = $type; + $type = null; + } + if ($this->fallback) { + $response = '' . PHP_EOL; + echo $response; + } else { + if ($type) { + echo 'event: ' . $type . PHP_EOL; + } + echo 'data: ' . OCP\JSON::encode($data) . PHP_EOL; + } + echo PHP_EOL; + flush(); + } + + /** + * close the connection of the event source + */ + public function close() { + $this->send('__internal__', 'close'); //server side closing can be an issue, let the client do it + } +} diff --git a/lib/private/legacy/filechunking.php b/lib/private/legacy/filechunking.php new file mode 100644 index 00000000000..f2cef275458 --- /dev/null +++ b/lib/private/legacy/filechunking.php @@ -0,0 +1,175 @@ + + * @author Felix Moeller + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Thomas Tanghus + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + + +class OC_FileChunking { + protected $info; + protected $cache; + + static public function decodeName($name) { + preg_match('/(?P.*)-chunking-(?P\d+)-(?P\d+)-(?P\d+)/', $name, $matches); + return $matches; + } + + /** + * @param string[] $info + */ + public function __construct($info) { + $this->info = $info; + } + + public function getPrefix() { + $name = $this->info['name']; + $transferid = $this->info['transferid']; + + return $name.'-chunking-'.$transferid.'-'; + } + + protected function getCache() { + if (!isset($this->cache)) { + $this->cache = new \OC\Cache\File(); + } + return $this->cache; + } + + /** + * Stores the given $data under the given $key - the number of stored bytes is returned + * + * @param string $index + * @param resource $data + * @return int + */ + public function store($index, $data) { + $cache = $this->getCache(); + $name = $this->getPrefix().$index; + $cache->set($name, $data); + + return $cache->size($name); + } + + public function isComplete() { + $prefix = $this->getPrefix(); + $cache = $this->getCache(); + $chunkcount = (int)$this->info['chunkcount']; + + for($i=($chunkcount-1); $i >= 0; $i--) { + if (!$cache->hasKey($prefix.$i)) { + return false; + } + } + + return true; + } + + /** + * Assembles the chunks into the file specified by the path. + * Chunks are deleted afterwards. + * + * @param resource $f target path + * + * @return integer assembled file size + * + * @throws \OC\InsufficientStorageException when file could not be fully + * assembled due to lack of free space + */ + public function assemble($f) { + $cache = $this->getCache(); + $prefix = $this->getPrefix(); + $count = 0; + for ($i = 0; $i < $this->info['chunkcount']; $i++) { + $chunk = $cache->get($prefix.$i); + // remove after reading to directly save space + $cache->remove($prefix.$i); + $count += fwrite($f, $chunk); + // let php release the memory to work around memory exhausted error with php 5.6 + $chunk = null; + } + + return $count; + } + + /** + * Returns the size of the chunks already present + * @return integer size in bytes + */ + public function getCurrentSize() { + $cache = $this->getCache(); + $prefix = $this->getPrefix(); + $total = 0; + for ($i = 0; $i < $this->info['chunkcount']; $i++) { + $total += $cache->size($prefix.$i); + } + return $total; + } + + /** + * Removes all chunks which belong to this transmission + */ + public function cleanup() { + $cache = $this->getCache(); + $prefix = $this->getPrefix(); + for($i=0; $i < $this->info['chunkcount']; $i++) { + $cache->remove($prefix.$i); + } + } + + /** + * Removes one specific chunk + * @param string $index + */ + public function remove($index) { + $cache = $this->getCache(); + $prefix = $this->getPrefix(); + $cache->remove($prefix.$index); + } + + /** + * Assembles the chunks into the file specified by the path. + * Also triggers the relevant hooks and proxies. + * + * @param \OC\Files\Storage\Storage $storage storage + * @param string $path target path relative to the storage + * @return bool true on success or false if file could not be created + * + * @throws \OC\ServerNotAvailableException + */ + public function file_assemble($storage, $path) { + // use file_put_contents as method because that best matches what this function does + if (\OC\Files\Filesystem::isValidPath($path)) { + $target = $storage->fopen($path, 'w'); + if ($target) { + $count = $this->assemble($target); + fclose($target); + return $count > 0; + } else { + return false; + } + } + return false; + } +} diff --git a/lib/private/legacy/files.php b/lib/private/legacy/files.php new file mode 100644 index 00000000000..9b6a1a4465f --- /dev/null +++ b/lib/private/legacy/files.php @@ -0,0 +1,316 @@ + + * @author Bart Visscher + * @author Björn Schießle + * @author Clark Tomlinson + * @author Frank Karlitschek + * @author Jakob Sack + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Michael Gapczynski + * @author Nicolai Ehemann + * @author Robin Appelman + * @author Robin McCorkell + * @author Thibaut GRIDEL + * @author Thomas Müller + * @author Victor Dubiniuk + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use OC\Files\View; +use OC\Streamer; +use OCP\Lock\ILockingProvider; + +/** + * Class for file server access + * + */ +class OC_Files { + const FILE = 1; + const ZIP_FILES = 2; + const ZIP_DIR = 3; + + const UPLOAD_MIN_LIMIT_BYTES = 1048576; // 1 MiB + + /** + * @param string $filename + * @param string $name + */ + private static function sendHeaders($filename, $name) { + OC_Response::setContentDispositionHeader($name, 'attachment'); + header('Content-Transfer-Encoding: binary'); + OC_Response::disableCaching(); + $fileSize = \OC\Files\Filesystem::filesize($filename); + $type = \OC::$server->getMimeTypeDetector()->getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename)); + header('Content-Type: '.$type); + if ($fileSize > -1) { + OC_Response::setContentLengthHeader($fileSize); + } + } + + /** + * return the content of a file or return a zip file containing multiple files + * + * @param string $dir + * @param string $files ; separated list of files to download + * @param boolean $onlyHeader ; boolean to only send header of the request + */ + public static function get($dir, $files, $onlyHeader = false) { + + $view = \OC\Files\Filesystem::getView(); + $getType = self::FILE; + $filename = $dir; + try { + + if (is_array($files) && count($files) === 1) { + $files = $files[0]; + } + + if (!is_array($files)) { + $filename = $dir . '/' . $files; + if (!$view->is_dir($filename)) { + self::getSingleFile($view, $dir, $files, $onlyHeader); + return; + } + } + + $name = 'download'; + if (is_array($files)) { + $getType = self::ZIP_FILES; + $basename = basename($dir); + if ($basename) { + $name = $basename; + } + + $filename = $dir . '/' . $name; + } else { + $filename = $dir . '/' . $files; + $getType = self::ZIP_DIR; + // downloading root ? + if ($files !== '') { + $name = $files; + } + } + + $streamer = new Streamer(); + OC_Util::obEnd(); + + self::lockFiles($view, $dir, $files); + + $streamer->sendHeaders($name); + $executionTime = intval(OC::$server->getIniWrapper()->getNumeric('max_execution_time')); + set_time_limit(0); + if ($getType === self::ZIP_FILES) { + foreach ($files as $file) { + $file = $dir . '/' . $file; + if (\OC\Files\Filesystem::is_file($file)) { + $fileSize = \OC\Files\Filesystem::filesize($file); + $fh = \OC\Files\Filesystem::fopen($file, 'r'); + $streamer->addFileFromStream($fh, basename($file), $fileSize); + fclose($fh); + } elseif (\OC\Files\Filesystem::is_dir($file)) { + $streamer->addDirRecursive($file); + } + } + } elseif ($getType === self::ZIP_DIR) { + $file = $dir . '/' . $files; + $streamer->addDirRecursive($file); + } + $streamer->finalize(); + set_time_limit($executionTime); + self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); + } catch (\OCP\Lock\LockedException $ex) { + self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); + OC::$server->getLogger()->logException($ex); + $l = \OC::$server->getL10N('core'); + $hint = method_exists($ex, 'getHint') ? $ex->getHint() : ''; + \OC_Template::printErrorPage($l->t('File is currently busy, please try again later'), $hint); + } catch (\OCP\Files\ForbiddenException $ex) { + self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); + OC::$server->getLogger()->logException($ex); + $l = \OC::$server->getL10N('core'); + \OC_Template::printErrorPage($l->t('Can\'t read file'), $ex->getMessage()); + } catch (\Exception $ex) { + self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); + OC::$server->getLogger()->logException($ex); + $l = \OC::$server->getL10N('core'); + $hint = method_exists($ex, 'getHint') ? $ex->getHint() : ''; + \OC_Template::printErrorPage($l->t('Can\'t read file'), $hint); + } + } + + /** + * @param View $view + * @param string $name + * @param string $dir + * @param boolean $onlyHeader + */ + private static function getSingleFile($view, $dir, $name, $onlyHeader) { + $filename = $dir . '/' . $name; + OC_Util::obEnd(); + $view->lockFile($filename, ILockingProvider::LOCK_SHARED); + + if (\OC\Files\Filesystem::isReadable($filename)) { + self::sendHeaders($filename, $name); + } elseif (!\OC\Files\Filesystem::file_exists($filename)) { + header("HTTP/1.0 404 Not Found"); + $tmpl = new OC_Template('', '404', 'guest'); + $tmpl->printPage(); + exit(); + } else { + header("HTTP/1.0 403 Forbidden"); + die('403 Forbidden'); + } + if ($onlyHeader) { + return; + } + $view->readfile($filename); + } + + /** + * @param View $view + * @param string $dir + * @param string[]|string $files + */ + public static function lockFiles($view, $dir, $files) { + if (!is_array($files)) { + $file = $dir . '/' . $files; + $files = [$file]; + } + foreach ($files as $file) { + $file = $dir . '/' . $file; + $view->lockFile($file, ILockingProvider::LOCK_SHARED); + if ($view->is_dir($file)) { + $contents = $view->getDirectoryContent($file); + $contents = array_map(function($fileInfo) use ($file) { + /** @var \OCP\Files\FileInfo $fileInfo */ + return $file . '/' . $fileInfo->getName(); + }, $contents); + self::lockFiles($view, $dir, $contents); + } + } + } + + /** + * set the maximum upload size limit for apache hosts using .htaccess + * + * @param int $size file size in bytes + * @param array $files override '.htaccess' and '.user.ini' locations + * @return bool false on failure, size on success + */ + public static function setUploadLimit($size, $files = []) { + //don't allow user to break his config + $size = intval($size); + if ($size < self::UPLOAD_MIN_LIMIT_BYTES) { + return false; + } + $size = OC_Helper::phpFileSize($size); + + $phpValueKeys = array( + 'upload_max_filesize', + 'post_max_size' + ); + + // default locations if not overridden by $files + $files = array_merge([ + '.htaccess' => OC::$SERVERROOT . '/.htaccess', + '.user.ini' => OC::$SERVERROOT . '/.user.ini' + ], $files); + + $updateFiles = [ + $files['.htaccess'] => [ + 'pattern' => '/php_value %1$s (\S)*/', + 'setting' => 'php_value %1$s %2$s' + ], + $files['.user.ini'] => [ + 'pattern' => '/%1$s=(\S)*/', + 'setting' => '%1$s=%2$s' + ] + ]; + + $success = true; + + foreach ($updateFiles as $filename => $patternMap) { + // suppress warnings from fopen() + $handle = @fopen($filename, 'r+'); + if (!$handle) { + \OCP\Util::writeLog('files', + 'Can\'t write upload limit to ' . $filename . '. Please check the file permissions', + \OCP\Util::WARN); + $success = false; + continue; // try to update as many files as possible + } + + $content = ''; + while (!feof($handle)) { + $content .= fread($handle, 1000); + } + + foreach ($phpValueKeys as $key) { + $pattern = vsprintf($patternMap['pattern'], [$key]); + $setting = vsprintf($patternMap['setting'], [$key, $size]); + $hasReplaced = 0; + $newContent = preg_replace($pattern, $setting, $content, 2, $hasReplaced); + if ($newContent !== null) { + $content = $newContent; + } + if ($hasReplaced === 0) { + $content .= "\n" . $setting; + } + } + + // write file back + ftruncate($handle, 0); + rewind($handle); + fwrite($handle, $content); + + fclose($handle); + } + + if ($success) { + return OC_Helper::computerFileSize($size); + } + return false; + } + + /** + * @param string $dir + * @param $files + * @param integer $getType + * @param View $view + * @param string $filename + */ + private static function unlockAllTheFiles($dir, $files, $getType, $view, $filename) { + if ($getType === self::FILE) { + $view->unlockFile($filename, ILockingProvider::LOCK_SHARED); + } + if ($getType === self::ZIP_FILES) { + foreach ($files as $file) { + $file = $dir . '/' . $file; + $view->unlockFile($file, ILockingProvider::LOCK_SHARED); + } + } + if ($getType === self::ZIP_DIR) { + $file = $dir . '/' . $files; + $view->unlockFile($file, ILockingProvider::LOCK_SHARED); + } + } + +} diff --git a/lib/private/legacy/group.php b/lib/private/legacy/group.php new file mode 100644 index 00000000000..f1b84069a38 --- /dev/null +++ b/lib/private/legacy/group.php @@ -0,0 +1,300 @@ + + * @author Bart Visscher + * @author Björn Schießle + * @author Georg Ehrke + * @author goodkiller + * @author Jakob Sack + * @author Lukas Reschke + * @author macjohnny + * @author Michael Gapczynski + * @author Morris Jobke + * @author Qingping Hou + * @author Robin Appelman + * @author Robin McCorkell + * @author Thomas Müller + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * This class provides all methods needed for managing groups. + * + * Note that &run is deprecated and won't work anymore. + * Hooks provided: + * pre_createGroup(&run, gid) + * post_createGroup(gid) + * pre_deleteGroup(&run, gid) + * post_deleteGroup(gid) + * pre_addToGroup(&run, uid, gid) + * post_addToGroup(uid, gid) + * pre_removeFromGroup(&run, uid, gid) + * post_removeFromGroup(uid, gid) + */ +class OC_Group { + + /** + * @return \OC\Group\Manager + * @deprecated Use \OC::$server->getGroupManager(); + */ + public static function getManager() { + return \OC::$server->getGroupManager(); + } + + /** + * @return \OC\User\Manager + * @deprecated Use \OC::$server->getUserManager() + */ + private static function getUserManager() { + return \OC::$server->getUserManager(); + } + + /** + * set the group backend + * @param \OC_Group_Backend $backend The backend to use for user management + * @return bool + */ + public static function useBackend($backend) { + self::getManager()->addBackend($backend); + return true; + } + + /** + * remove all used backends + */ + public static function clearBackends() { + self::getManager()->clearBackends(); + } + + /** + * Try to create a new group + * @param string $gid The name of the group to create + * @return bool + * + * Tries to create a new group. If the group name already exists, false will + * be returned. Basic checking of Group name + * @deprecated Use \OC::$server->getGroupManager()->createGroup() instead + */ + public static function createGroup($gid) { + if (self::getManager()->createGroup($gid)) { + return true; + } else { + return false; + } + } + + /** + * delete a group + * @param string $gid gid of the group to delete + * @return bool + * + * Deletes a group and removes it from the group_user-table + * @deprecated Use \OC::$server->getGroupManager()->delete() instead + */ + public static function deleteGroup($gid) { + $group = self::getManager()->get($gid); + if ($group) { + if ($group->delete()) { + return true; + } + } + return false; + } + + /** + * is user in group? + * @param string $uid uid of the user + * @param string $gid gid of the group + * @return bool + * + * Checks whether the user is member of a group or not. + * @deprecated Use \OC::$server->getGroupManager->inGroup($user); + */ + public static function inGroup($uid, $gid) { + $group = self::getManager()->get($gid); + $user = self::getUserManager()->get($uid); + if ($group and $user) { + return $group->inGroup($user); + } + return false; + } + + /** + * Add a user to a group + * @param string $uid Name of the user to add to group + * @param string $gid Name of the group in which add the user + * @return bool + * + * Adds a user to a group. + * @deprecated Use \OC::$server->getGroupManager->addUser(); + */ + public static function addToGroup($uid, $gid) { + $group = self::getManager()->get($gid); + $user = self::getUserManager()->get($uid); + if ($group and $user) { + $group->addUser($user); + return true; + } else { + return false; + } + } + + /** + * Removes a user from a group + * @param string $uid Name of the user to remove from group + * @param string $gid Name of the group from which remove the user + * @return bool + * + * removes the user from a group. + */ + public static function removeFromGroup($uid, $gid) { + $group = self::getManager()->get($gid); + $user = self::getUserManager()->get($uid); + if ($group and $user) { + OC_Hook::emit("OC_Group", "pre_removeFromGroup", array("run" => true, "uid" => $uid, "gid" => $gid)); + $group->removeUser($user); + OC_Hook::emit("OC_User", "post_removeFromGroup", array("uid" => $uid, "gid" => $gid)); + return true; + } else { + return false; + } + } + + /** + * Get all groups a user belongs to + * @param string $uid Name of the user + * @return array an array of group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + * @deprecated Use \OC::$server->getGroupManager->getUserGroupIds($user) + */ + public static function getUserGroups($uid) { + $user = self::getUserManager()->get($uid); + if ($user) { + return self::getManager()->getUserGroupIds($user); + } else { + return array(); + } + } + + /** + * get a list of all groups + * @param string $search + * @param int|null $limit + * @param int|null $offset + * @return array an array of group names + * + * Returns a list with all groups + */ + public static function getGroups($search = '', $limit = null, $offset = null) { + $groups = self::getManager()->search($search, $limit, $offset); + $groupIds = array(); + foreach ($groups as $group) { + $groupIds[] = $group->getGID(); + } + return $groupIds; + } + + /** + * check if a group exists + * + * @param string $gid + * @return bool + * @deprecated Use \OC::$server->getGroupManager->groupExists($gid) + */ + public static function groupExists($gid) { + return self::getManager()->groupExists($gid); + } + + /** + * get a list of all users in a group + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array an array of user ids + */ + public static function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { + $group = self::getManager()->get($gid); + if ($group) { + $users = $group->searchUsers($search, $limit, $offset); + $userIds = array(); + foreach ($users as $user) { + $userIds[] = $user->getUID(); + } + return $userIds; + } else { + return array(); + } + } + + /** + * get a list of all users in several groups + * @param string[] $gids + * @param string $search + * @param int $limit + * @param int $offset + * @return array an array of user ids + */ + public static function usersInGroups($gids, $search = '', $limit = -1, $offset = 0) { + $users = array(); + foreach ($gids as $gid) { + // TODO Need to apply limits to groups as total + $users = array_merge(array_diff(self::usersInGroup($gid, $search, $limit, $offset), $users), $users); + } + return $users; + } + + /** + * get a list of all display names in a group + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array an array of display names (value) and user ids(key) + * @deprecated Use \OC::$server->getGroupManager->displayNamesInGroup($gid, $search, $limit, $offset) + */ + public static function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0) { + return self::getManager()->displayNamesInGroup($gid, $search, $limit, $offset); + } + + /** + * get a list of all display names in several groups + * @param array $gids + * @param string $search + * @param int $limit + * @param int $offset + * @return array an array of display names (Key) user ids (value) + */ + public static function displayNamesInGroups($gids, $search = '', $limit = -1, $offset = 0) { + $displayNames = array(); + foreach ($gids as $gid) { + // TODO Need to apply limits to groups as total + $diff = array_diff( + self::displayNamesInGroup($gid, $search, $limit, $offset), + $displayNames + ); + if ($diff) { + // A fix for LDAP users. array_merge loses keys... + $displayNames = $diff + $displayNames; + } + } + return $displayNames; + } +} diff --git a/lib/private/legacy/helper.php b/lib/private/legacy/helper.php new file mode 100644 index 00000000000..e6aaed0fd15 --- /dev/null +++ b/lib/private/legacy/helper.php @@ -0,0 +1,703 @@ + + * @author Bart Visscher + * @author Björn Schießle + * @author Christopher Schäpers + * @author Clark Tomlinson + * @author Fabian Henze + * @author Felix Moeller + * @author Georg Ehrke + * @author Jakob Sack + * @author Jan-Christoph Borchardt + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Olivier Paroz + * @author Pellaeon Lin + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Simon Könnecke + * @author Thomas Müller + * @author Thomas Tanghus + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +use Symfony\Component\Process\ExecutableFinder; + +/** + * Collection of useful functions + */ +class OC_Helper { + private static $templateManager; + + /** + * Creates an absolute url for public use + * @param string $service id + * @param bool $add_slash + * @return string the url + * + * Returns a absolute url to the given service. + */ + public static function linkToPublic($service, $add_slash = false) { + if ($service === 'files') { + $url = OC::$server->getURLGenerator()->getAbsoluteURL('/s'); + } else { + $url = OC::$server->getURLGenerator()->getAbsoluteURL(OC::$server->getURLGenerator()->linkTo('', 'public.php').'?service='.$service); + } + return $url . (($add_slash && $service[strlen($service) - 1] != '/') ? '/' : ''); + } + + /** + * get path to preview of file + * @param string $path path + * @return string the url + * + * Returns the path to the preview of the file. + */ + public static function previewIcon($path) { + return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_preview', ['x' => 32, 'y' => 32, 'file' => $path]); + } + + public static function publicPreviewIcon( $path, $token ) { + return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_public_preview', ['x' => 32, 'y' => 32, 'file' => $path, 't' => $token]); + } + + /** + * Make a human file size + * @param int $bytes file size in bytes + * @return string a human readable file size + * + * Makes 2048 to 2 kB. + */ + public static function humanFileSize($bytes) { + if ($bytes < 0) { + return "?"; + } + if ($bytes < 1024) { + return "$bytes B"; + } + $bytes = round($bytes / 1024, 0); + if ($bytes < 1024) { + return "$bytes KB"; + } + $bytes = round($bytes / 1024, 1); + if ($bytes < 1024) { + return "$bytes MB"; + } + $bytes = round($bytes / 1024, 1); + if ($bytes < 1024) { + return "$bytes GB"; + } + $bytes = round($bytes / 1024, 1); + if ($bytes < 1024) { + return "$bytes TB"; + } + + $bytes = round($bytes / 1024, 1); + return "$bytes PB"; + } + + /** + * Make a php file size + * @param int $bytes file size in bytes + * @return string a php parseable file size + * + * Makes 2048 to 2k and 2^41 to 2048G + */ + public static function phpFileSize($bytes) { + if ($bytes < 0) { + return "?"; + } + if ($bytes < 1024) { + return $bytes . "B"; + } + $bytes = round($bytes / 1024, 1); + if ($bytes < 1024) { + return $bytes . "K"; + } + $bytes = round($bytes / 1024, 1); + if ($bytes < 1024) { + return $bytes . "M"; + } + $bytes = round($bytes / 1024, 1); + return $bytes . "G"; + } + + /** + * Make a computer file size + * @param string $str file size in human readable format + * @return float a file size in bytes + * + * Makes 2kB to 2048. + * + * Inspired by: http://www.php.net/manual/en/function.filesize.php#92418 + */ + public static function computerFileSize($str) { + $str = strtolower($str); + if (is_numeric($str)) { + return floatval($str); + } + + $bytes_array = array( + 'b' => 1, + 'k' => 1024, + 'kb' => 1024, + 'mb' => 1024 * 1024, + 'm' => 1024 * 1024, + 'gb' => 1024 * 1024 * 1024, + 'g' => 1024 * 1024 * 1024, + 'tb' => 1024 * 1024 * 1024 * 1024, + 't' => 1024 * 1024 * 1024 * 1024, + 'pb' => 1024 * 1024 * 1024 * 1024 * 1024, + 'p' => 1024 * 1024 * 1024 * 1024 * 1024, + ); + + $bytes = floatval($str); + + if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && !empty($bytes_array[$matches[1]])) { + $bytes *= $bytes_array[$matches[1]]; + } else { + return false; + } + + $bytes = round($bytes); + + return $bytes; + } + + /** + * Recursive copying of folders + * @param string $src source folder + * @param string $dest target folder + * + */ + static function copyr($src, $dest) { + if (is_dir($src)) { + if (!is_dir($dest)) { + mkdir($dest); + } + $files = scandir($src); + foreach ($files as $file) { + if ($file != "." && $file != "..") { + self::copyr("$src/$file", "$dest/$file"); + } + } + } elseif (file_exists($src) && !\OC\Files\Filesystem::isFileBlacklisted($src)) { + copy($src, $dest); + } + } + + /** + * Recursive deletion of folders + * @param string $dir path to the folder + * @param bool $deleteSelf if set to false only the content of the folder will be deleted + * @return bool + */ + static function rmdirr($dir, $deleteSelf = true) { + if (is_dir($dir)) { + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($files as $fileInfo) { + /** @var SplFileInfo $fileInfo */ + if ($fileInfo->isDir()) { + rmdir($fileInfo->getRealPath()); + } else { + unlink($fileInfo->getRealPath()); + } + } + if ($deleteSelf) { + rmdir($dir); + } + } elseif (file_exists($dir)) { + if ($deleteSelf) { + unlink($dir); + } + } + if (!$deleteSelf) { + return true; + } + + return !file_exists($dir); + } + + /** + * @return \OC\Files\Type\TemplateManager + */ + static public function getFileTemplateManager() { + if (!self::$templateManager) { + self::$templateManager = new \OC\Files\Type\TemplateManager(); + } + return self::$templateManager; + } + + /** + * detect if a given program is found in the search PATH + * + * @param string $name + * @param bool $path + * @internal param string $program name + * @internal param string $optional search path, defaults to $PATH + * @return bool true if executable program found in path + */ + public static function canExecute($name, $path = false) { + // path defaults to PATH from environment if not set + if ($path === false) { + $path = getenv("PATH"); + } + // check method depends on operating system + if (!strncmp(PHP_OS, "WIN", 3)) { + // on Windows an appropriate COM or EXE file needs to exist + $exts = array(".exe", ".com"); + $check_fn = "file_exists"; + } else { + // anywhere else we look for an executable file of that name + $exts = array(""); + $check_fn = "is_executable"; + } + // Default check will be done with $path directories : + $dirs = explode(PATH_SEPARATOR, $path); + // WARNING : We have to check if open_basedir is enabled : + $obd = OC::$server->getIniWrapper()->getString('open_basedir'); + if ($obd != "none") { + $obd_values = explode(PATH_SEPARATOR, $obd); + if (count($obd_values) > 0 and $obd_values[0]) { + // open_basedir is in effect ! + // We need to check if the program is in one of these dirs : + $dirs = $obd_values; + } + } + foreach ($dirs as $dir) { + foreach ($exts as $ext) { + if ($check_fn("$dir/$name" . $ext)) + return true; + } + } + return false; + } + + /** + * copy the contents of one stream to another + * + * @param resource $source + * @param resource $target + * @return array the number of bytes copied and result + */ + public static function streamCopy($source, $target) { + if (!$source or !$target) { + return array(0, false); + } + $bufSize = 8192; + $result = true; + $count = 0; + while (!feof($source)) { + $buf = fread($source, $bufSize); + $bytesWritten = fwrite($target, $buf); + if ($bytesWritten !== false) { + $count += $bytesWritten; + } + // note: strlen is expensive so only use it when necessary, + // on the last block + if ($bytesWritten === false + || ($bytesWritten < $bufSize && $bytesWritten < strlen($buf)) + ) { + // write error, could be disk full ? + $result = false; + break; + } + } + return array($count, $result); + } + + /** + * Adds a suffix to the name in case the file exists + * + * @param string $path + * @param string $filename + * @return string + */ + public static function buildNotExistingFileName($path, $filename) { + $view = \OC\Files\Filesystem::getView(); + return self::buildNotExistingFileNameForView($path, $filename, $view); + } + + /** + * Adds a suffix to the name in case the file exists + * + * @param string $path + * @param string $filename + * @return string + */ + public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) { + if ($path === '/') { + $path = ''; + } + if ($pos = strrpos($filename, '.')) { + $name = substr($filename, 0, $pos); + $ext = substr($filename, $pos); + } else { + $name = $filename; + $ext = ''; + } + + $newpath = $path . '/' . $filename; + if ($view->file_exists($newpath)) { + if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) { + //Replace the last "(number)" with "(number+1)" + $last_match = count($matches[0]) - 1; + $counter = $matches[1][$last_match][0] + 1; + $offset = $matches[0][$last_match][1]; + $match_length = strlen($matches[0][$last_match][0]); + } else { + $counter = 2; + $match_length = 0; + $offset = false; + } + do { + if ($offset) { + //Replace the last "(number)" with "(number+1)" + $newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length); + } else { + $newname = $name . ' (' . $counter . ')'; + } + $newpath = $path . '/' . $newname . $ext; + $counter++; + } while ($view->file_exists($newpath)); + } + + return $newpath; + } + + /** + * Checks if $sub is a subdirectory of $parent + * + * @param string $sub + * @param string $parent + * @return bool + */ + public static function isSubDirectory($sub, $parent) { + $realpathSub = realpath($sub); + $realpathParent = realpath($parent); + + // realpath() may return false in case the directory does not exist + // since we can not be sure how different PHP versions may behave here + // we do an additional check whether realpath returned false + if($realpathSub === false || $realpathParent === false) { + return false; + } + + // Check whether $sub is a subdirectory of $parent + if (strpos($realpathSub, $realpathParent) === 0) { + return true; + } + + return false; + } + + /** + * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is. + * + * @param array $input The array to work on + * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default) + * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8 + * @return array + * + * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is. + * based on http://www.php.net/manual/en/function.array-change-key-case.php#107715 + * + */ + public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') { + $case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER; + $ret = array(); + foreach ($input as $k => $v) { + $ret[mb_convert_case($k, $case, $encoding)] = $v; + } + return $ret; + } + + /** + * performs a search in a nested array + * @param array $haystack the array to be searched + * @param string $needle the search string + * @param string $index optional, only search this key name + * @return mixed the key of the matching field, otherwise false + * + * performs a search in a nested array + * + * taken from http://www.php.net/manual/en/function.array-search.php#97645 + */ + public static function recursiveArraySearch($haystack, $needle, $index = null) { + $aIt = new RecursiveArrayIterator($haystack); + $it = new RecursiveIteratorIterator($aIt); + + while ($it->valid()) { + if (((isset($index) AND ($it->key() == $index)) OR (!isset($index))) AND ($it->current() == $needle)) { + return $aIt->key(); + } + + $it->next(); + } + + return false; + } + + /** + * Shortens str to maxlen by replacing characters in the middle with '...', eg. + * ellipsis('a very long string with lots of useless info to make a better example', 14) becomes 'a very ...example' + * + * @param string $str the string + * @param string $maxlen the maximum length of the result + * @return string with at most maxlen characters + */ + public static function ellipsis($str, $maxlen) { + if (strlen($str) > $maxlen) { + $characters = floor($maxlen / 2); + return substr($str, 0, $characters) . '...' . substr($str, -1 * $characters); + } + return $str; + } + + /** + * calculates the maximum upload size respecting system settings, free space and user quota + * + * @param string $dir the current folder where the user currently operates + * @param int $freeSpace the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly + * @return int number of bytes representing + */ + public static function maxUploadFilesize($dir, $freeSpace = null) { + if (is_null($freeSpace) || $freeSpace < 0){ + $freeSpace = self::freeSpace($dir); + } + return min($freeSpace, self::uploadLimit()); + } + + /** + * Calculate free space left within user quota + * + * @param string $dir the current folder where the user currently operates + * @return int number of bytes representing + */ + public static function freeSpace($dir) { + $freeSpace = \OC\Files\Filesystem::free_space($dir); + if ($freeSpace !== \OCP\Files\FileInfo::SPACE_UNKNOWN) { + $freeSpace = max($freeSpace, 0); + return $freeSpace; + } else { + return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188 + } + } + + /** + * Calculate PHP upload limit + * + * @return int PHP upload file size limit + */ + public static function uploadLimit() { + $ini = \OC::$server->getIniWrapper(); + $upload_max_filesize = OCP\Util::computerFileSize($ini->get('upload_max_filesize')); + $post_max_size = OCP\Util::computerFileSize($ini->get('post_max_size')); + if ((int)$upload_max_filesize === 0 and (int)$post_max_size === 0) { + return INF; + } elseif ((int)$upload_max_filesize === 0 or (int)$post_max_size === 0) { + return max($upload_max_filesize, $post_max_size); //only the non 0 value counts + } else { + return min($upload_max_filesize, $post_max_size); + } + } + + /** + * Checks if a function is available + * + * @param string $function_name + * @return bool + */ + public static function is_function_enabled($function_name) { + if (!function_exists($function_name)) { + return false; + } + $ini = \OC::$server->getIniWrapper(); + $disabled = explode(',', $ini->get('disable_functions')); + $disabled = array_map('trim', $disabled); + if (in_array($function_name, $disabled)) { + return false; + } + $disabled = explode(',', $ini->get('suhosin.executor.func.blacklist')); + $disabled = array_map('trim', $disabled); + if (in_array($function_name, $disabled)) { + return false; + } + return true; + } + + /** + * Try to find a program + * Note: currently windows is not supported + * + * @param string $program + * @return null|string + */ + public static function findBinaryPath($program) { + $memcache = \OC::$server->getMemCacheFactory()->create('findBinaryPath'); + if ($memcache->hasKey($program)) { + return $memcache->get($program); + } + $result = null; + if (!\OC_Util::runningOnWindows() && self::is_function_enabled('exec')) { + $exeSniffer = new ExecutableFinder(); + // Returns null if nothing is found + $result = $exeSniffer->find($program); + if (empty($result)) { + $paths = getenv('PATH'); + if (empty($paths)) { + $paths = '/usr/local/bin /usr/bin /opt/bin /bin'; + } else { + $paths = str_replace(':',' ',getenv('PATH')); + } + $command = 'find ' . $paths . ' -name ' . escapeshellarg($program) . ' 2> /dev/null'; + exec($command, $output, $returnCode); + if (count($output) > 0) { + $result = escapeshellcmd($output[0]); + } + } + } + // store the value for 5 minutes + $memcache->set($program, $result, 300); + return $result; + } + + /** + * Calculate the disc space for the given path + * + * @param string $path + * @param \OCP\Files\FileInfo $rootInfo (optional) + * @return array + * @throws \OCP\Files\NotFoundException + */ + public static function getStorageInfo($path, $rootInfo = null) { + // return storage info without adding mount points + $includeExtStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false); + + if (!$rootInfo) { + $rootInfo = \OC\Files\Filesystem::getFileInfo($path, false); + } + if (!$rootInfo instanceof \OCP\Files\FileInfo) { + throw new \OCP\Files\NotFoundException(); + } + $used = $rootInfo->getSize(); + if ($used < 0) { + $used = 0; + } + $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED; + $storage = $rootInfo->getStorage(); + $sourceStorage = $storage; + if ($storage->instanceOfStorage('\OC\Files\Storage\Shared')) { + $includeExtStorage = false; + $sourceStorage = $storage->getSourceStorage(); + } + if ($includeExtStorage) { + $quota = OC_Util::getUserQuota(\OCP\User::getUser()); + if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { + // always get free space / total space from root + mount points + return self::getGlobalStorageInfo(); + } + } + + // TODO: need a better way to get total space from storage + if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) { + /** @var \OC\Files\Storage\Wrapper\Quota $storage */ + $quota = $sourceStorage->getQuota(); + } + $free = $sourceStorage->free_space(''); + if ($free >= 0) { + $total = $free + $used; + } else { + $total = $free; //either unknown or unlimited + } + if ($total > 0) { + if ($quota > 0 && $total > $quota) { + $total = $quota; + } + // prevent division by zero or error codes (negative values) + $relative = round(($used / $total) * 10000) / 100; + } else { + $relative = 0; + } + + $ownerId = $storage->getOwner($path); + $ownerDisplayName = ''; + $owner = \OC::$server->getUserManager()->get($ownerId); + if($owner) { + $ownerDisplayName = $owner->getDisplayName(); + } + + return [ + 'free' => $free, + 'used' => $used, + 'quota' => $quota, + 'total' => $total, + 'relative' => $relative, + 'owner' => $ownerId, + 'ownerDisplayName' => $ownerDisplayName, + ]; + } + + /** + * Get storage info including all mount points and quota + * + * @return array + */ + private static function getGlobalStorageInfo() { + $quota = OC_Util::getUserQuota(\OCP\User::getUser()); + + $rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext'); + $used = $rootInfo['size']; + if ($used < 0) { + $used = 0; + } + + $total = $quota; + $free = $quota - $used; + + if ($total > 0) { + if ($quota > 0 && $total > $quota) { + $total = $quota; + } + // prevent division by zero or error codes (negative values) + $relative = round(($used / $total) * 10000) / 100; + } else { + $relative = 0; + } + + return array('free' => $free, 'used' => $used, 'total' => $total, 'relative' => $relative); + + } + + /** + * Returns whether the config file is set manually to read-only + * @return bool + */ + public static function isReadOnlyConfigEnabled() { + return \OC::$server->getConfig()->getSystemValue('config_is_read_only', false); + } +} diff --git a/lib/private/legacy/hook.php b/lib/private/legacy/hook.php new file mode 100644 index 00000000000..76f3b657eb9 --- /dev/null +++ b/lib/private/legacy/hook.php @@ -0,0 +1,145 @@ + + * @author Jakob Sack + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Sam Tuke + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +class OC_Hook{ + public static $thrownExceptions = []; + + static private $registered = array(); + + /** + * connects a function to a hook + * + * @param string $signalClass class name of emitter + * @param string $signalName name of signal + * @param string|object $slotClass class name of slot + * @param string $slotName name of slot + * @return bool + * + * This function makes it very easy to connect to use hooks. + * + * TODO: write example + */ + static public function connect($signalClass, $signalName, $slotClass, $slotName ) { + // If we're trying to connect to an emitting class that isn't + // yet registered, register it + if( !array_key_exists($signalClass, self::$registered )) { + self::$registered[$signalClass] = array(); + } + // If we're trying to connect to an emitting method that isn't + // yet registered, register it with the emitting class + if( !array_key_exists( $signalName, self::$registered[$signalClass] )) { + self::$registered[$signalClass][$signalName] = array(); + } + + // don't connect hooks twice + foreach (self::$registered[$signalClass][$signalName] as $hook) { + if ($hook['class'] === $slotClass and $hook['name'] === $slotName) { + return false; + } + } + // Connect the hook handler to the requested emitter + self::$registered[$signalClass][$signalName][] = array( + "class" => $slotClass, + "name" => $slotName + ); + + // No chance for failure ;-) + return true; + } + + /** + * emits a signal + * + * @param string $signalClass class name of emitter + * @param string $signalName name of signal + * @param mixed $params default: array() array with additional data + * @return bool true if slots exists or false if not + * @throws \OC\HintException + * @throws \OC\ServerNotAvailableException Emits a signal. To get data from the slot use references! + * + * TODO: write example + */ + static public function emit($signalClass, $signalName, $params = []) { + + // Return false if no hook handlers are listening to this + // emitting class + if( !array_key_exists($signalClass, self::$registered )) { + return false; + } + + // Return false if no hook handlers are listening to this + // emitting method + if( !array_key_exists( $signalName, self::$registered[$signalClass] )) { + return false; + } + + // Call all slots + foreach( self::$registered[$signalClass][$signalName] as $i ) { + try { + call_user_func( array( $i["class"], $i["name"] ), $params ); + } catch (Exception $e){ + self::$thrownExceptions[] = $e; + \OC::$server->getLogger()->logException($e); + if($e instanceof \OC\HintException) { + throw $e; + } + if($e instanceof \OC\ServerNotAvailableException) { + throw $e; + } + } + } + + return true; + } + + /** + * clear hooks + * @param string $signalClass + * @param string $signalName + */ + static public function clear($signalClass='', $signalName='') { + if ($signalClass) { + if ($signalName) { + self::$registered[$signalClass][$signalName]=array(); + }else{ + self::$registered[$signalClass]=array(); + } + }else{ + self::$registered=array(); + } + } + + /** + * DO NOT USE! + * For unit tests ONLY! + */ + static public function getHooks() { + return self::$registered; + } +} diff --git a/lib/private/legacy/image.php b/lib/private/legacy/image.php new file mode 100644 index 00000000000..a97347df623 --- /dev/null +++ b/lib/private/legacy/image.php @@ -0,0 +1,1147 @@ + + * @author Bart Visscher + * @author Bartek Przybylski + * @author Björn Schießle + * @author Byron Marohn + * @author Christopher Schäpers + * @author Georg Ehrke + * @author j-ed + * @author Joas Schilling + * @author Johannes Willnecker + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Olivier Paroz + * @author Robin Appelman + * @author Thomas Müller + * @author Thomas Tanghus + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * Class for basic image manipulation + */ +class OC_Image implements \OCP\IImage { + /** @var false|resource */ + protected $resource = false; // tmp resource. + /** @var int */ + protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident. + /** @var string */ + protected $mimeType = 'image/png'; // Default to png + /** @var int */ + protected $bitDepth = 24; + /** @var null|string */ + protected $filePath = null; + /** @var finfo */ + private $fileInfo; + /** @var \OCP\ILogger */ + private $logger; + + /** + * Get mime type for an image file. + * + * @param string|null $filePath The path to a local image file. + * @return string The mime type if the it could be determined, otherwise an empty string. + */ + static public function getMimeTypeForFile($filePath) { + // exif_imagetype throws "read error!" if file is less than 12 byte + if ($filePath !== null && filesize($filePath) > 11) { + $imageType = exif_imagetype($filePath); + } else { + $imageType = false; + } + return $imageType ? image_type_to_mime_type($imageType) : ''; + } + + /** + * Constructor. + * + * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by + * an imagecreate* function. + * @param \OCP\ILogger $logger + */ + public function __construct($imageRef = null, $logger = null) { + $this->logger = $logger; + if (is_null($logger)) { + $this->logger = \OC::$server->getLogger(); + } + + if (!extension_loaded('gd') || !function_exists('gd_info')) { + $this->logger->error(__METHOD__ . '(): GD module not installed', array('app' => 'core')); + return false; + } + + if (\OC_Util::fileInfoLoaded()) { + $this->fileInfo = new finfo(FILEINFO_MIME_TYPE); + } + + if (!is_null($imageRef)) { + $this->load($imageRef); + } + } + + /** + * Determine whether the object contains an image resource. + * + * @return bool + */ + public function valid() { // apparently you can't name a method 'empty'... + return is_resource($this->resource); + } + + /** + * Returns the MIME type of the image or an empty string if no image is loaded. + * + * @return string + */ + public function mimeType() { + return $this->valid() ? $this->mimeType : ''; + } + + /** + * Returns the width of the image or -1 if no image is loaded. + * + * @return int + */ + public function width() { + return $this->valid() ? imagesx($this->resource) : -1; + } + + /** + * Returns the height of the image or -1 if no image is loaded. + * + * @return int + */ + public function height() { + return $this->valid() ? imagesy($this->resource) : -1; + } + + /** + * Returns the width when the image orientation is top-left. + * + * @return int + */ + public function widthTopLeft() { + $o = $this->getOrientation(); + $this->logger->debug('OC_Image->widthTopLeft() Orientation: ' . $o, array('app' => 'core')); + switch ($o) { + case -1: + case 1: + case 2: // Not tested + case 3: + case 4: // Not tested + return $this->width(); + case 5: // Not tested + case 6: + case 7: // Not tested + case 8: + return $this->height(); + } + return $this->width(); + } + + /** + * Returns the height when the image orientation is top-left. + * + * @return int + */ + public function heightTopLeft() { + $o = $this->getOrientation(); + $this->logger->debug('OC_Image->heightTopLeft() Orientation: ' . $o, array('app' => 'core')); + switch ($o) { + case -1: + case 1: + case 2: // Not tested + case 3: + case 4: // Not tested + return $this->height(); + case 5: // Not tested + case 6: + case 7: // Not tested + case 8: + return $this->width(); + } + return $this->height(); + } + + /** + * Outputs the image. + * + * @param string $mimeType + * @return bool + */ + public function show($mimeType = null) { + if ($mimeType === null) { + $mimeType = $this->mimeType(); + } + header('Content-Type: ' . $mimeType); + return $this->_output(null, $mimeType); + } + + /** + * Saves the image. + * + * @param string $filePath + * @param string $mimeType + * @return bool + */ + + public function save($filePath = null, $mimeType = null) { + if ($mimeType === null) { + $mimeType = $this->mimeType(); + } + if ($filePath === null && $this->filePath === null) { + $this->logger->error(__METHOD__ . '(): called with no path.', array('app' => 'core')); + return false; + } elseif ($filePath === null && $this->filePath !== null) { + $filePath = $this->filePath; + } + return $this->_output($filePath, $mimeType); + } + + /** + * Outputs/saves the image. + * + * @param string $filePath + * @param string $mimeType + * @return bool + * @throws Exception + */ + private function _output($filePath = null, $mimeType = null) { + if ($filePath) { + if (!file_exists(dirname($filePath))) + mkdir(dirname($filePath), 0777, true); + if (!is_writable(dirname($filePath))) { + $this->logger->error(__METHOD__ . '(): Directory \'' . dirname($filePath) . '\' is not writable.', array('app' => 'core')); + return false; + } elseif (is_writable(dirname($filePath)) && file_exists($filePath) && !is_writable($filePath)) { + $this->logger->error(__METHOD__ . '(): File \'' . $filePath . '\' is not writable.', array('app' => 'core')); + return false; + } + } + if (!$this->valid()) { + return false; + } + + $imageType = $this->imageType; + if ($mimeType !== null) { + switch ($mimeType) { + case 'image/gif': + $imageType = IMAGETYPE_GIF; + break; + case 'image/jpeg': + $imageType = IMAGETYPE_JPEG; + break; + case 'image/png': + $imageType = IMAGETYPE_PNG; + break; + case 'image/x-xbitmap': + $imageType = IMAGETYPE_XBM; + break; + case 'image/bmp': + case 'image/x-ms-bmp': + $imageType = IMAGETYPE_BMP; + break; + default: + throw new Exception('\OC_Image::_output(): "' . $mimeType . '" is not supported when forcing a specific output format'); + } + } + + switch ($imageType) { + case IMAGETYPE_GIF: + $retVal = imagegif($this->resource, $filePath); + break; + case IMAGETYPE_JPEG: + $retVal = imagejpeg($this->resource, $filePath); + break; + case IMAGETYPE_PNG: + $retVal = imagepng($this->resource, $filePath); + break; + case IMAGETYPE_XBM: + if (function_exists('imagexbm')) { + $retVal = imagexbm($this->resource, $filePath); + } else { + throw new Exception('\OC_Image::_output(): imagexbm() is not supported.'); + } + + break; + case IMAGETYPE_WBMP: + $retVal = imagewbmp($this->resource, $filePath); + break; + case IMAGETYPE_BMP: + $retVal = imagebmp($this->resource, $filePath, $this->bitDepth); + break; + default: + $retVal = imagepng($this->resource, $filePath); + } + return $retVal; + } + + /** + * Prints the image when called as $image(). + */ + public function __invoke() { + return $this->show(); + } + + /** + * @return resource Returns the image resource in any. + */ + public function resource() { + return $this->resource; + } + + /** + * @return null|string Returns the raw image data. + */ + public function data() { + if (!$this->valid()) { + return null; + } + ob_start(); + switch ($this->mimeType) { + case "image/png": + $res = imagepng($this->resource); + break; + case "image/jpeg": + $res = imagejpeg($this->resource); + break; + case "image/gif": + $res = imagegif($this->resource); + break; + default: + $res = imagepng($this->resource); + $this->logger->info('OC_Image->data. Could not guess mime-type, defaulting to png', array('app' => 'core')); + break; + } + if (!$res) { + $this->logger->error('OC_Image->data. Error getting image data.', array('app' => 'core')); + } + return ob_get_clean(); + } + + /** + * @return string - base64 encoded, which is suitable for embedding in a VCard. + */ + function __toString() { + return base64_encode($this->data()); + } + + /** + * (I'm open for suggestions on better method name ;) + * Get the orientation based on EXIF data. + * + * @return int The orientation or -1 if no EXIF data is available. + */ + public function getOrientation() { + if ($this->imageType !== IMAGETYPE_JPEG) { + $this->logger->debug('OC_Image->fixOrientation() Image is not a JPEG.', array('app' => 'core')); + return -1; + } + if (!is_callable('exif_read_data')) { + $this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', array('app' => 'core')); + return -1; + } + if (!$this->valid()) { + $this->logger->debug('OC_Image->fixOrientation() No image loaded.', array('app' => 'core')); + return -1; + } + if (is_null($this->filePath) || !is_readable($this->filePath)) { + $this->logger->debug('OC_Image->fixOrientation() No readable file path set.', array('app' => 'core')); + return -1; + } + $exif = @exif_read_data($this->filePath, 'IFD0'); + if (!$exif) { + return -1; + } + if (!isset($exif['Orientation'])) { + return -1; + } + return $exif['Orientation']; + } + + /** + * (I'm open for suggestions on better method name ;) + * Fixes orientation based on EXIF data. + * + * @return bool. + */ + public function fixOrientation() { + $o = $this->getOrientation(); + $this->logger->debug('OC_Image->fixOrientation() Orientation: ' . $o, array('app' => 'core')); + $rotate = 0; + $flip = false; + switch ($o) { + case -1: + return false; //Nothing to fix + case 1: + $rotate = 0; + break; + case 2: + $rotate = 0; + $flip = true; + break; + case 3: + $rotate = 180; + break; + case 4: + $rotate = 180; + $flip = true; + break; + case 5: + $rotate = 90; + $flip = true; + break; + case 6: + $rotate = 270; + break; + case 7: + $rotate = 270; + $flip = true; + break; + case 8: + $rotate = 90; + break; + } + if($flip && function_exists('imageflip')) { + imageflip($this->resource, IMG_FLIP_HORIZONTAL); + } + if ($rotate) { + $res = imagerotate($this->resource, $rotate, 0); + if ($res) { + if (imagealphablending($res, true)) { + if (imagesavealpha($res, true)) { + imagedestroy($this->resource); + $this->resource = $res; + return true; + } else { + $this->logger->debug('OC_Image->fixOrientation() Error during alpha-saving', array('app' => 'core')); + return false; + } + } else { + $this->logger->debug('OC_Image->fixOrientation() Error during alpha-blending', array('app' => 'core')); + return false; + } + } else { + $this->logger->debug('OC_Image->fixOrientation() Error during orientation fixing', array('app' => 'core')); + return false; + } + } + return false; + } + + /** + * Loads an image from a local file, a base64 encoded string or a resource created by an imagecreate* function. + * + * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by an imagecreate* function or a file resource (file handle ). + * @return resource|false An image resource or false on error + */ + public function load($imageRef) { + if (is_resource($imageRef)) { + if (get_resource_type($imageRef) == 'gd') { + $this->resource = $imageRef; + return $this->resource; + } elseif (in_array(get_resource_type($imageRef), array('file', 'stream'))) { + return $this->loadFromFileHandle($imageRef); + } + } elseif ($this->loadFromBase64($imageRef) !== false) { + return $this->resource; + } elseif ($this->loadFromFile($imageRef) !== false) { + return $this->resource; + } elseif ($this->loadFromData($imageRef) !== false) { + return $this->resource; + } + $this->logger->debug(__METHOD__ . '(): could not load anything. Giving up!', array('app' => 'core')); + return false; + } + + /** + * Loads an image from an open file handle. + * It is the responsibility of the caller to position the pointer at the correct place and to close the handle again. + * + * @param resource $handle + * @return resource|false An image resource or false on error + */ + public function loadFromFileHandle($handle) { + $contents = stream_get_contents($handle); + if ($this->loadFromData($contents)) { + return $this->resource; + } + return false; + } + + /** + * Loads an image from a local file. + * + * @param bool|string $imagePath The path to a local file. + * @return bool|resource An image resource or false on error + */ + public function loadFromFile($imagePath = false) { + // exif_imagetype throws "read error!" if file is less than 12 byte + if (!@is_file($imagePath) || !file_exists($imagePath) || filesize($imagePath) < 12 || !is_readable($imagePath)) { + return false; + } + $iType = exif_imagetype($imagePath); + switch ($iType) { + case IMAGETYPE_GIF: + if (imagetypes() & IMG_GIF) { + $this->resource = imagecreatefromgif($imagePath); + // Preserve transparency + imagealphablending($this->resource, true); + imagesavealpha($this->resource, true); + } else { + $this->logger->debug('OC_Image->loadFromFile, GIF images not supported: ' . $imagePath, array('app' => 'core')); + } + break; + case IMAGETYPE_JPEG: + if (imagetypes() & IMG_JPG) { + $this->resource = imagecreatefromjpeg($imagePath); + } else { + $this->logger->debug('OC_Image->loadFromFile, JPG images not supported: ' . $imagePath, array('app' => 'core')); + } + break; + case IMAGETYPE_PNG: + if (imagetypes() & IMG_PNG) { + $this->resource = imagecreatefrompng($imagePath); + // Preserve transparency + imagealphablending($this->resource, true); + imagesavealpha($this->resource, true); + } else { + $this->logger->debug('OC_Image->loadFromFile, PNG images not supported: ' . $imagePath, array('app' => 'core')); + } + break; + case IMAGETYPE_XBM: + if (imagetypes() & IMG_XPM) { + $this->resource = imagecreatefromxbm($imagePath); + } else { + $this->logger->debug('OC_Image->loadFromFile, XBM/XPM images not supported: ' . $imagePath, array('app' => 'core')); + } + break; + case IMAGETYPE_WBMP: + if (imagetypes() & IMG_WBMP) { + $this->resource = imagecreatefromwbmp($imagePath); + } else { + $this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, array('app' => 'core')); + } + break; + case IMAGETYPE_BMP: + $this->resource = $this->imagecreatefrombmp($imagePath); + break; + /* + case IMAGETYPE_TIFF_II: // (intel byte order) + break; + case IMAGETYPE_TIFF_MM: // (motorola byte order) + break; + case IMAGETYPE_JPC: + break; + case IMAGETYPE_JP2: + break; + case IMAGETYPE_JPX: + break; + case IMAGETYPE_JB2: + break; + case IMAGETYPE_SWC: + break; + case IMAGETYPE_IFF: + break; + case IMAGETYPE_ICO: + break; + case IMAGETYPE_SWF: + break; + case IMAGETYPE_PSD: + break; + */ + default: + + // this is mostly file created from encrypted file + $this->resource = imagecreatefromstring(\OC\Files\Filesystem::file_get_contents(\OC\Files\Filesystem::getLocalPath($imagePath))); + $iType = IMAGETYPE_PNG; + $this->logger->debug('OC_Image->loadFromFile, Default', array('app' => 'core')); + break; + } + if ($this->valid()) { + $this->imageType = $iType; + $this->mimeType = image_type_to_mime_type($iType); + $this->filePath = $imagePath; + } + return $this->resource; + } + + /** + * Loads an image from a string of data. + * + * @param string $str A string of image data as read from a file. + * @return bool|resource An image resource or false on error + */ + public function loadFromData($str) { + if (is_resource($str)) { + return false; + } + $this->resource = @imagecreatefromstring($str); + if ($this->fileInfo) { + $this->mimeType = $this->fileInfo->buffer($str); + } + if (is_resource($this->resource)) { + imagealphablending($this->resource, false); + imagesavealpha($this->resource, true); + } + + if (!$this->resource) { + $this->logger->debug('OC_Image->loadFromFile, could not load', array('app' => 'core')); + return false; + } + return $this->resource; + } + + /** + * Loads an image from a base64 encoded string. + * + * @param string $str A string base64 encoded string of image data. + * @return bool|resource An image resource or false on error + */ + public function loadFromBase64($str) { + if (!is_string($str)) { + return false; + } + $data = base64_decode($str); + if ($data) { // try to load from string data + $this->resource = @imagecreatefromstring($data); + if ($this->fileInfo) { + $this->mimeType = $this->fileInfo->buffer($data); + } + if (!$this->resource) { + $this->logger->debug('OC_Image->loadFromBase64, could not load', array('app' => 'core')); + return false; + } + return $this->resource; + } else { + return false; + } + } + + /** + * Create a new image from file or URL + * + * @link http://www.programmierer-forum.de/function-imagecreatefrombmp-laeuft-mit-allen-bitraten-t143137.htm + * @version 1.00 + * @param string $fileName

+ * Path to the BMP image. + *

+ * @return bool|resource an image resource identifier on success, FALSE on errors. + */ + private function imagecreatefrombmp($fileName) { + if (!($fh = fopen($fileName, 'rb'))) { + $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName, array('app' => 'core')); + return false; + } + // read file header + $meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14)); + // check for bitmap + if ($meta['type'] != 19778) { + fclose($fh); + $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName . ' is not a bitmap!', array('app' => 'core')); + return false; + } + // read image header + $meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40)); + // read additional 16bit header + if ($meta['bits'] == 16) { + $meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12)); + } + // set bytes and padding + $meta['bytes'] = $meta['bits'] / 8; + $this->bitDepth = $meta['bits']; //remember the bit depth for the imagebmp call + $meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4))); + if ($meta['decal'] == 4) { + $meta['decal'] = 0; + } + // obtain imagesize + if ($meta['imagesize'] < 1) { + $meta['imagesize'] = $meta['filesize'] - $meta['offset']; + // in rare cases filesize is equal to offset so we need to read physical size + if ($meta['imagesize'] < 1) { + $meta['imagesize'] = @filesize($fileName) - $meta['offset']; + if ($meta['imagesize'] < 1) { + fclose($fh); + $this->logger->warning('imagecreatefrombmp: Can not obtain file size of ' . $fileName . ' is not a bitmap!', array('app' => 'core')); + return false; + } + } + } + // calculate colors + $meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors']; + // read color palette + $palette = array(); + if ($meta['bits'] < 16) { + $palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4)); + // in rare cases the color value is signed + if ($palette[1] < 0) { + foreach ($palette as $i => $color) { + $palette[$i] = $color + 16777216; + } + } + } + // create gd image + $im = imagecreatetruecolor($meta['width'], $meta['height']); + if ($im == false) { + fclose($fh); + $this->logger->warning( + 'imagecreatefrombmp: imagecreatetruecolor failed for file "' . $fileName . '" with dimensions ' . $meta['width'] . 'x' . $meta['height'], + array('app' => 'core')); + return false; + } + + $data = fread($fh, $meta['imagesize']); + $p = 0; + $vide = chr(0); + $y = $meta['height'] - 1; + $error = 'imagecreatefrombmp: ' . $fileName . ' has not enough data!'; + // loop through the image data beginning with the lower left corner + while ($y >= 0) { + $x = 0; + while ($x < $meta['width']) { + switch ($meta['bits']) { + case 32: + case 24: + if (!($part = substr($data, $p, 3))) { + $this->logger->warning($error, array('app' => 'core')); + return $im; + } + $color = unpack('V', $part . $vide); + break; + case 16: + if (!($part = substr($data, $p, 2))) { + fclose($fh); + $this->logger->warning($error, array('app' => 'core')); + return $im; + } + $color = unpack('v', $part); + $color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3); + break; + case 8: + $color = unpack('n', $vide . substr($data, $p, 1)); + $color[1] = $palette[$color[1] + 1]; + break; + case 4: + $color = unpack('n', $vide . substr($data, floor($p), 1)); + $color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F; + $color[1] = $palette[$color[1] + 1]; + break; + case 1: + $color = unpack('n', $vide . substr($data, floor($p), 1)); + switch (($p * 8) % 8) { + case 0: + $color[1] = $color[1] >> 7; + break; + case 1: + $color[1] = ($color[1] & 0x40) >> 6; + break; + case 2: + $color[1] = ($color[1] & 0x20) >> 5; + break; + case 3: + $color[1] = ($color[1] & 0x10) >> 4; + break; + case 4: + $color[1] = ($color[1] & 0x8) >> 3; + break; + case 5: + $color[1] = ($color[1] & 0x4) >> 2; + break; + case 6: + $color[1] = ($color[1] & 0x2) >> 1; + break; + case 7: + $color[1] = ($color[1] & 0x1); + break; + } + $color[1] = $palette[$color[1] + 1]; + break; + default: + fclose($fh); + $this->logger->warning('imagecreatefrombmp: ' . $fileName . ' has ' . $meta['bits'] . ' bits and this is not supported!', array('app' => 'core')); + return false; + } + imagesetpixel($im, $x, $y, $color[1]); + $x++; + $p += $meta['bytes']; + } + $y--; + $p += $meta['decal']; + } + fclose($fh); + return $im; + } + + /** + * Resizes the image preserving ratio. + * + * @param integer $maxSize The maximum size of either the width or height. + * @return bool + */ + public function resize($maxSize) { + if (!$this->valid()) { + $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core')); + return false; + } + $widthOrig = imageSX($this->resource); + $heightOrig = imageSY($this->resource); + $ratioOrig = $widthOrig / $heightOrig; + + if ($ratioOrig > 1) { + $newHeight = round($maxSize / $ratioOrig); + $newWidth = $maxSize; + } else { + $newWidth = round($maxSize * $ratioOrig); + $newHeight = $maxSize; + } + + $this->preciseResize(round($newWidth), round($newHeight)); + return true; + } + + /** + * @param int $width + * @param int $height + * @return bool + */ + public function preciseResize($width, $height) { + if (!$this->valid()) { + $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core')); + return false; + } + $widthOrig = imageSX($this->resource); + $heightOrig = imageSY($this->resource); + $process = imagecreatetruecolor($width, $height); + + if ($process == false) { + $this->logger->error(__METHOD__ . '(): Error creating true color image', array('app' => 'core')); + imagedestroy($process); + return false; + } + + // preserve transparency + if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) { + imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127)); + imagealphablending($process, false); + imagesavealpha($process, true); + } + + imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig); + if ($process == false) { + $this->logger->error(__METHOD__ . '(): Error re-sampling process image', array('app' => 'core')); + imagedestroy($process); + return false; + } + imagedestroy($this->resource); + $this->resource = $process; + return true; + } + + /** + * Crops the image to the middle square. If the image is already square it just returns. + * + * @param int $size maximum size for the result (optional) + * @return bool for success or failure + */ + public function centerCrop($size = 0) { + if (!$this->valid()) { + $this->logger->error('OC_Image->centerCrop, No image loaded', array('app' => 'core')); + return false; + } + $widthOrig = imageSX($this->resource); + $heightOrig = imageSY($this->resource); + if ($widthOrig === $heightOrig and $size == 0) { + return true; + } + $ratioOrig = $widthOrig / $heightOrig; + $width = $height = min($widthOrig, $heightOrig); + + if ($ratioOrig > 1) { + $x = ($widthOrig / 2) - ($width / 2); + $y = 0; + } else { + $y = ($heightOrig / 2) - ($height / 2); + $x = 0; + } + if ($size > 0) { + $targetWidth = $size; + $targetHeight = $size; + } else { + $targetWidth = $width; + $targetHeight = $height; + } + $process = imagecreatetruecolor($targetWidth, $targetHeight); + if ($process == false) { + $this->logger->error('OC_Image->centerCrop, Error creating true color image', array('app' => 'core')); + imagedestroy($process); + return false; + } + + // preserve transparency + if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) { + imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127)); + imagealphablending($process, false); + imagesavealpha($process, true); + } + + imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height); + if ($process == false) { + $this->logger->error('OC_Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height, array('app' => 'core')); + imagedestroy($process); + return false; + } + imagedestroy($this->resource); + $this->resource = $process; + return true; + } + + /** + * Crops the image from point $x$y with dimension $wx$h. + * + * @param int $x Horizontal position + * @param int $y Vertical position + * @param int $w Width + * @param int $h Height + * @return bool for success or failure + */ + public function crop($x, $y, $w, $h) { + if (!$this->valid()) { + $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core')); + return false; + } + $process = imagecreatetruecolor($w, $h); + if ($process == false) { + $this->logger->error(__METHOD__ . '(): Error creating true color image', array('app' => 'core')); + imagedestroy($process); + return false; + } + + // preserve transparency + if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) { + imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127)); + imagealphablending($process, false); + imagesavealpha($process, true); + } + + imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h); + if ($process == false) { + $this->logger->error(__METHOD__ . '(): Error re-sampling process image ' . $w . 'x' . $h, array('app' => 'core')); + imagedestroy($process); + return false; + } + imagedestroy($this->resource); + $this->resource = $process; + return true; + } + + /** + * Resizes the image to fit within a boundary while preserving ratio. + * + * Warning: Images smaller than $maxWidth x $maxHeight will end up being scaled up + * + * @param integer $maxWidth + * @param integer $maxHeight + * @return bool + */ + public function fitIn($maxWidth, $maxHeight) { + if (!$this->valid()) { + $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core')); + return false; + } + $widthOrig = imageSX($this->resource); + $heightOrig = imageSY($this->resource); + $ratio = $widthOrig / $heightOrig; + + $newWidth = min($maxWidth, $ratio * $maxHeight); + $newHeight = min($maxHeight, $maxWidth / $ratio); + + $this->preciseResize(round($newWidth), round($newHeight)); + return true; + } + + /** + * Shrinks larger images to fit within specified boundaries while preserving ratio. + * + * @param integer $maxWidth + * @param integer $maxHeight + * @return bool + */ + public function scaleDownToFit($maxWidth, $maxHeight) { + if (!$this->valid()) { + $this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core')); + return false; + } + $widthOrig = imageSX($this->resource); + $heightOrig = imageSY($this->resource); + + if ($widthOrig > $maxWidth || $heightOrig > $maxHeight) { + return $this->fitIn($maxWidth, $maxHeight); + } + + return false; + } + + /** + * Destroys the current image and resets the object + */ + public function destroy() { + if ($this->valid()) { + imagedestroy($this->resource); + } + $this->resource = null; + } + + public function __destruct() { + $this->destroy(); + } +} + +if (!function_exists('imagebmp')) { + /** + * Output a BMP image to either the browser or a file + * + * @link http://www.ugia.cn/wp-data/imagebmp.php + * @author legend + * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm + * @author mgutt + * @version 1.00 + * @param string $fileName [optional]

The path to save the file to.

+ * @param int $bit [optional]

Bit depth, (default is 24).

+ * @param int $compression [optional] + * @return bool TRUE on success or FALSE on failure. + */ + function imagebmp($im, $fileName = '', $bit = 24, $compression = 0) { + if (!in_array($bit, array(1, 4, 8, 16, 24, 32))) { + $bit = 24; + } else if ($bit == 32) { + $bit = 24; + } + $bits = pow(2, $bit); + imagetruecolortopalette($im, true, $bits); + $width = imagesx($im); + $height = imagesy($im); + $colorsNum = imagecolorstotal($im); + $rgbQuad = ''; + if ($bit <= 8) { + for ($i = 0; $i < $colorsNum; $i++) { + $colors = imagecolorsforindex($im, $i); + $rgbQuad .= chr($colors['blue']) . chr($colors['green']) . chr($colors['red']) . "\0"; + } + $bmpData = ''; + if ($compression == 0 || $bit < 8) { + $compression = 0; + $extra = ''; + $padding = 4 - ceil($width / (8 / $bit)) % 4; + if ($padding % 4 != 0) { + $extra = str_repeat("\0", $padding); + } + for ($j = $height - 1; $j >= 0; $j--) { + $i = 0; + while ($i < $width) { + $bin = 0; + $limit = $width - $i < 8 / $bit ? (8 / $bit - $width + $i) * $bit : 0; + for ($k = 8 - $bit; $k >= $limit; $k -= $bit) { + $index = imagecolorat($im, $i, $j); + $bin |= $index << $k; + $i++; + } + $bmpData .= chr($bin); + } + $bmpData .= $extra; + } + } // RLE8 + else if ($compression == 1 && $bit == 8) { + for ($j = $height - 1; $j >= 0; $j--) { + $lastIndex = "\0"; + $sameNum = 0; + for ($i = 0; $i <= $width; $i++) { + $index = imagecolorat($im, $i, $j); + if ($index !== $lastIndex || $sameNum > 255) { + if ($sameNum != 0) { + $bmpData .= chr($sameNum) . chr($lastIndex); + } + $lastIndex = $index; + $sameNum = 1; + } else { + $sameNum++; + } + } + $bmpData .= "\0\0"; + } + $bmpData .= "\0\1"; + } + $sizeQuad = strlen($rgbQuad); + $sizeData = strlen($bmpData); + } else { + $extra = ''; + $padding = 4 - ($width * ($bit / 8)) % 4; + if ($padding % 4 != 0) { + $extra = str_repeat("\0", $padding); + } + $bmpData = ''; + for ($j = $height - 1; $j >= 0; $j--) { + for ($i = 0; $i < $width; $i++) { + $index = imagecolorat($im, $i, $j); + $colors = imagecolorsforindex($im, $index); + if ($bit == 16) { + $bin = 0 << $bit; + $bin |= ($colors['red'] >> 3) << 10; + $bin |= ($colors['green'] >> 3) << 5; + $bin |= $colors['blue'] >> 3; + $bmpData .= pack("v", $bin); + } else { + $bmpData .= pack("c*", $colors['blue'], $colors['green'], $colors['red']); + } + } + $bmpData .= $extra; + } + $sizeQuad = 0; + $sizeData = strlen($bmpData); + $colorsNum = 0; + } + $fileHeader = 'BM' . pack('V3', 54 + $sizeQuad + $sizeData, 0, 54 + $sizeQuad); + $infoHeader = pack('V3v2V*', 0x28, $width, $height, 1, $bit, $compression, $sizeData, 0, 0, $colorsNum, 0); + if ($fileName != '') { + $fp = fopen($fileName, 'wb'); + fwrite($fp, $fileHeader . $infoHeader . $rgbQuad . $bmpData); + fclose($fp); + return true; + } + echo $fileHeader . $infoHeader . $rgbQuad . $bmpData; + return true; + } +} + +if (!function_exists('exif_imagetype')) { + /** + * Workaround if exif_imagetype does not exist + * + * @link http://www.php.net/manual/en/function.exif-imagetype.php#80383 + * @param string $fileName + * @return string|boolean + */ + function exif_imagetype($fileName) { + if (($info = getimagesize($fileName)) !== false) { + return $info[2]; + } + return false; + } +} diff --git a/lib/private/legacy/installer.php b/lib/private/legacy/installer.php new file mode 100644 index 00000000000..24c79b2dd8c --- /dev/null +++ b/lib/private/legacy/installer.php @@ -0,0 +1,638 @@ + + * @author Bart Visscher + * @author Brice Maron + * @author Christian Weiske + * @author Christopher Schäpers + * @author Frank Karlitschek + * @author Georg Ehrke + * @author Jakob Sack + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Kamil Domanski + * @author Lukas Reschke + * @author michag86 + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author root + * @author Thomas Müller + * @author Thomas Tanghus + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use OC\App\CodeChecker\CodeChecker; +use OC\App\CodeChecker\EmptyCheck; +use OC\App\CodeChecker\PrivateCheck; +use OC\OCSClient; + +/** + * This class provides the functionality needed to install, update and remove plugins/apps + */ +class OC_Installer{ + + /** + * + * This function installs an app. All information needed are passed in the + * associative array $data. + * The following keys are required: + * - source: string, can be "path" or "http" + * + * One of the following keys is required: + * - path: path to the file containing the app + * - href: link to the downloadable file containing the app + * + * The following keys are optional: + * - pretend: boolean, if set true the system won't do anything + * - noinstall: boolean, if true appinfo/install.php won't be loaded + * - inactive: boolean, if set true the appconfig/app.sample.php won't be + * renamed + * + * This function works as follows + * -# fetching the file + * -# unzipping it + * -# check the code + * -# installing the database at appinfo/database.xml + * -# including appinfo/install.php + * -# setting the installed version + * + * It is the task of oc_app_install to create the tables and do whatever is + * needed to get the app working. + * + * Installs an app + * @param array $data with all information + * @throws \Exception + * @return integer + */ + public static function installApp( $data = array()) { + $l = \OC::$server->getL10N('lib'); + + list($extractDir, $path) = self::downloadApp($data); + + $info = self::checkAppsIntegrity($data, $extractDir, $path); + $appId = OC_App::cleanAppId($info['id']); + $basedir = OC_App::getInstallPath().'/'.$appId; + //check if the destination directory already exists + if(is_dir($basedir)) { + OC_Helper::rmdirr($extractDir); + if($data['source']=='http') { + unlink($path); + } + throw new \Exception($l->t("App directory already exists")); + } + + if(!empty($data['pretent'])) { + return false; + } + + //copy the app to the correct place + if(@!mkdir($basedir)) { + OC_Helper::rmdirr($extractDir); + if($data['source']=='http') { + unlink($path); + } + throw new \Exception($l->t("Can't create app folder. Please fix permissions. %s", array($basedir))); + } + + $extractDir .= '/' . $info['id']; + if(!file_exists($extractDir)) { + OC_Helper::rmdirr($basedir); + throw new \Exception($l->t("Archive does not contain a directory named %s", $info['id'])); + } + OC_Helper::copyr($extractDir, $basedir); + + //remove temporary files + OC_Helper::rmdirr($extractDir); + + //install the database + if(is_file($basedir.'/appinfo/database.xml')) { + if (\OC::$server->getAppConfig()->getValue($info['id'], 'installed_version') === null) { + OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml'); + } else { + OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml'); + } + } + + //run appinfo/install.php + if((!isset($data['noinstall']) or $data['noinstall']==false)) { + self::includeAppScript($basedir . '/appinfo/install.php'); + } + + //set the installed version + \OC::$server->getAppConfig()->setValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'])); + \OC::$server->getAppConfig()->setValue($info['id'], 'enabled', 'no'); + + //set remote/public handelers + foreach($info['remote'] as $name=>$path) { + OCP\CONFIG::setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path); + } + foreach($info['public'] as $name=>$path) { + OCP\CONFIG::setAppValue('core', 'public_'.$name, $info['id'].'/'.$path); + } + + OC_App::setAppTypes($info['id']); + + return $info['id']; + } + + /** + * @brief checks whether or not an app is installed + * @param string $app app + * @returns bool + * + * Checks whether or not an app is installed, i.e. registered in apps table. + */ + public static function isInstalled( $app ) { + return (\OC::$server->getAppConfig()->getValue($app, "installed_version") !== null); + } + + /** + * @brief Update an application + * @param array $info + * @param bool $isShipped + * @throws Exception + * @return bool + * + * This function could work like described below, but currently it disables and then + * enables the app again. This does result in an updated app. + * + * + * This function installs an app. All information needed are passed in the + * associative array $info. + * The following keys are required: + * - source: string, can be "path" or "http" + * + * One of the following keys is required: + * - path: path to the file containing the app + * - href: link to the downloadable file containing the app + * + * The following keys are optional: + * - pretend: boolean, if set true the system won't do anything + * - noupgrade: boolean, if true appinfo/upgrade.php won't be loaded + * + * This function works as follows + * -# fetching the file + * -# removing the old files + * -# unzipping new file + * -# including appinfo/upgrade.php + * -# setting the installed version + * + * upgrade.php can determine the current installed version of the app using + * "\OC::$server->getAppConfig()->getValue($appid, 'installed_version')" + */ + public static function updateApp($info=array(), $isShipped=false) { + list($extractDir, $path) = self::downloadApp($info); + $info = self::checkAppsIntegrity($info, $extractDir, $path, $isShipped); + + $currentDir = OC_App::getAppPath($info['id']); + $basedir = OC_App::getInstallPath(); + $basedir .= '/'; + $basedir .= $info['id']; + + if($currentDir !== false && is_writable($currentDir)) { + $basedir = $currentDir; + } + if(is_dir($basedir)) { + OC_Helper::rmdirr($basedir); + } + + $appInExtractDir = $extractDir; + if (substr($extractDir, -1) !== '/') { + $appInExtractDir .= '/'; + } + + $appInExtractDir .= $info['id']; + OC_Helper::copyr($appInExtractDir, $basedir); + OC_Helper::rmdirr($extractDir); + + return OC_App::updateApp($info['id']); + } + + /** + * update an app by it's id + * + * @param integer $ocsId + * @return bool + * @throws Exception + */ + public static function updateAppByOCSId($ocsId) { + $ocsClient = new OCSClient( + \OC::$server->getHTTPClientService(), + \OC::$server->getConfig(), + \OC::$server->getLogger() + ); + $appData = $ocsClient->getApplication($ocsId, \OCP\Util::getVersion()); + $download = $ocsClient->getApplicationDownload($ocsId, \OCP\Util::getVersion()); + + if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') { + $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); + $info = array( + 'source' => 'http', + 'href' => $download['downloadlink'], + 'appdata' => $appData + ); + } else { + throw new \Exception('Could not fetch app info!'); + } + + return self::updateApp($info); + } + + /** + * @param array $data + * @return array + * @throws Exception + */ + public static function downloadApp($data = array()) { + $l = \OC::$server->getL10N('lib'); + + if(!isset($data['source'])) { + throw new \Exception($l->t("No source specified when installing app")); + } + + //download the file if necessary + if($data['source']=='http') { + $pathInfo = pathinfo($data['href']); + $extension = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : ''; + $path = \OC::$server->getTempManager()->getTemporaryFile($extension); + if(!isset($data['href'])) { + throw new \Exception($l->t("No href specified when installing app from http")); + } + $client = \OC::$server->getHTTPClientService()->newClient(); + $client->get($data['href'], ['save_to' => $path]); + } else { + if(!isset($data['path'])) { + throw new \Exception($l->t("No path specified when installing app from local file")); + } + $path=$data['path']; + } + + //detect the archive type + $mime = \OC::$server->getMimeTypeDetector()->detect($path); + if ($mime !=='application/zip' && $mime !== 'application/x-gzip' && $mime !== 'application/x-bzip2') { + throw new \Exception($l->t("Archives of type %s are not supported", array($mime))); + } + + //extract the archive in a temporary folder + $extractDir = \OC::$server->getTempManager()->getTemporaryFolder(); + OC_Helper::rmdirr($extractDir); + mkdir($extractDir); + if($archive=OC_Archive::open($path)) { + $archive->extract($extractDir); + } else { + OC_Helper::rmdirr($extractDir); + if($data['source']=='http') { + unlink($path); + } + throw new \Exception($l->t("Failed to open archive when installing app")); + } + + return array( + $extractDir, + $path + ); + } + + /** + * check an app's integrity + * @param array $data + * @param string $extractDir + * @param string $path + * @param bool $isShipped + * @return array + * @throws \Exception + */ + public static function checkAppsIntegrity($data, $extractDir, $path, $isShipped = false) { + $l = \OC::$server->getL10N('lib'); + //load the info.xml file of the app + if(!is_file($extractDir.'/appinfo/info.xml')) { + //try to find it in a subdir + $dh=opendir($extractDir); + if(is_resource($dh)) { + while (($folder = readdir($dh)) !== false) { + if($folder[0]!='.' and is_dir($extractDir.'/'.$folder)) { + if(is_file($extractDir.'/'.$folder.'/appinfo/info.xml')) { + $extractDir.='/'.$folder; + } + } + } + } + } + if(!is_file($extractDir.'/appinfo/info.xml')) { + OC_Helper::rmdirr($extractDir); + if($data['source'] === 'http') { + unlink($path); + } + throw new \Exception($l->t("App does not provide an info.xml file")); + } + + $info = OC_App::getAppInfo($extractDir.'/appinfo/info.xml', true); + if(!is_array($info)) { + throw new \Exception($l->t('App cannot be installed because appinfo file cannot be read.')); + } + + // We can't trust the parsed info.xml file as it may have been tampered + // with by an attacker and thus we need to use the local data to check + // whether the application needs to be signed. + $appId = OC_App::cleanAppId($data['appdata']['id']); + $appBelongingToId = OC_App::getInternalAppIdByOcs($appId); + if(is_string($appBelongingToId)) { + $previouslySigned = \OC::$server->getConfig()->getAppValue($appBelongingToId, 'signed', 'false'); + } else { + $appBelongingToId = $info['id']; + $previouslySigned = 'false'; + } + if($data['appdata']['level'] === OC_App::officialApp || $previouslySigned === 'true') { + \OC::$server->getConfig()->setAppValue($appBelongingToId, 'signed', 'true'); + $integrityResult = \OC::$server->getIntegrityCodeChecker()->verifyAppSignature( + $appBelongingToId, + $extractDir + ); + if($integrityResult !== []) { + $e = new \Exception( + $l->t( + 'Signature could not get checked. Please contact the app developer and check your admin screen.' + ) + ); + throw $e; + } + } + + // check the code for not allowed calls + if(!$isShipped && !OC_Installer::checkCode($extractDir)) { + OC_Helper::rmdirr($extractDir); + throw new \Exception($l->t("App can't be installed because of not allowed code in the App")); + } + + // check if the app is compatible with this version of ownCloud + if(!OC_App::isAppCompatible(\OCP\Util::getVersion(), $info)) { + OC_Helper::rmdirr($extractDir); + throw new \Exception($l->t("App can't be installed because it is not compatible with this version of ownCloud")); + } + + // check if shipped tag is set which is only allowed for apps that are shipped with ownCloud + if(!$isShipped && isset($info['shipped']) && ($info['shipped']=='true')) { + OC_Helper::rmdirr($extractDir); + throw new \Exception($l->t("App can't be installed because it contains the true tag which is not allowed for non shipped apps")); + } + + // check if the ocs version is the same as the version in info.xml/version + $version = trim($info['version']); + + if(isset($data['appdata']['version']) && $version<>trim($data['appdata']['version'])) { + OC_Helper::rmdirr($extractDir); + throw new \Exception($l->t("App can't be installed because the version in info.xml is not the same as the version reported from the app store")); + } + + return $info; + } + + /** + * Check if an update for the app is available + * @param string $app + * @return string|false false or the version number of the update + * + * The function will check if an update for a version is available + */ + public static function isUpdateAvailable( $app ) { + static $isInstanceReadyForUpdates = null; + + if ($isInstanceReadyForUpdates === null) { + $installPath = OC_App::getInstallPath(); + if ($installPath === false || $installPath === null) { + $isInstanceReadyForUpdates = false; + } else { + $isInstanceReadyForUpdates = true; + } + } + + if ($isInstanceReadyForUpdates === false) { + return false; + } + + $ocsid=\OC::$server->getAppConfig()->getValue( $app, 'ocsid', ''); + + if($ocsid<>'') { + $ocsClient = new OCSClient( + \OC::$server->getHTTPClientService(), + \OC::$server->getConfig(), + \OC::$server->getLogger() + ); + $ocsdata = $ocsClient->getApplication($ocsid, \OCP\Util::getVersion()); + $ocsversion= (string) $ocsdata['version']; + $currentversion=OC_App::getAppVersion($app); + if (version_compare($ocsversion, $currentversion, '>')) { + return($ocsversion); + }else{ + return false; + } + + }else{ + return false; + } + + } + + /** + * Check if app is already downloaded + * @param string $name name of the application to remove + * @return boolean + * + * The function will check if the app is already downloaded in the apps repository + */ + public static function isDownloaded( $name ) { + foreach(OC::$APPSROOTS as $dir) { + $dirToTest = $dir['path']; + $dirToTest .= '/'; + $dirToTest .= $name; + $dirToTest .= '/'; + + if (is_dir($dirToTest)) { + return true; + } + } + + return false; + } + + /** + * Removes an app + * @param string $name name of the application to remove + * @param array $options options + * @return boolean + * + * This function removes an app. $options is an associative array. The + * following keys are optional:ja + * - keeppreferences: boolean, if true the user preferences won't be deleted + * - keepappconfig: boolean, if true the config will be kept + * - keeptables: boolean, if true the database will be kept + * - keepfiles: boolean, if true the user files will be kept + * + * This function works as follows + * -# including appinfo/remove.php + * -# removing the files + * + * The function will not delete preferences, tables and the configuration, + * this has to be done by the function oc_app_uninstall(). + */ + public static function removeApp( $name, $options = array()) { + + if(isset($options['keeppreferences']) and $options['keeppreferences']==false ) { + // todo + // remove preferences + } + + if(isset($options['keepappconfig']) and $options['keepappconfig']==false ) { + // todo + // remove app config + } + + if(isset($options['keeptables']) and $options['keeptables']==false ) { + // todo + // remove app database tables + } + + if(isset($options['keepfiles']) and $options['keepfiles']==false ) { + // todo + // remove user files + } + + if(OC_Installer::isDownloaded( $name )) { + $appdir=OC_App::getInstallPath().'/'.$name; + OC_Helper::rmdirr($appdir); + + return true; + }else{ + \OCP\Util::writeLog('core', 'can\'t remove app '.$name.'. It is not installed.', \OCP\Util::ERROR); + + return false; + } + + } + + /** + * Installs shipped apps + * + * This function installs all apps found in the 'apps' directory that should be enabled by default; + * @param bool $softErrors When updating we ignore errors and simply log them, better to have a + * working ownCloud at the end instead of an aborted update. + * @return array Array of error messages (appid => Exception) + */ + public static function installShippedApps($softErrors = false) { + $errors = []; + foreach(OC::$APPSROOTS as $app_dir) { + if($dir = opendir( $app_dir['path'] )) { + while( false !== ( $filename = readdir( $dir ))) { + if( substr( $filename, 0, 1 ) != '.' and is_dir($app_dir['path']."/$filename") ) { + if( file_exists( $app_dir['path']."/$filename/appinfo/info.xml" )) { + if(!OC_Installer::isInstalled($filename)) { + $info=OC_App::getAppInfo($filename); + $enabled = isset($info['default_enable']); + if (($enabled || in_array($filename, \OC::$server->getAppManager()->getAlwaysEnabledApps())) + && \OC::$server->getConfig()->getAppValue($filename, 'enabled') !== 'no') { + if ($softErrors) { + try { + OC_Installer::installShippedApp($filename); + } catch (\Doctrine\DBAL\Exception\TableExistsException $e) { + $errors[$filename] = $e; + continue; + } + } else { + OC_Installer::installShippedApp($filename); + } + \OC::$server->getConfig()->setAppValue($filename, 'enabled', 'yes'); + } + } + } + } + } + closedir( $dir ); + } + } + + return $errors; + } + + /** + * install an app already placed in the app folder + * @param string $app id of the app to install + * @return integer + */ + public static function installShippedApp($app) { + //install the database + $appPath = OC_App::getAppPath($app); + if(is_file("$appPath/appinfo/database.xml")) { + OC_DB::createDbFromStructure("$appPath/appinfo/database.xml"); + } + + //run appinfo/install.php + \OC::$loader->addValidRoot($appPath); + self::includeAppScript("$appPath/appinfo/install.php"); + + $info = OC_App::getAppInfo($app); + if (is_null($info)) { + return false; + } + + $config = \OC::$server->getConfig(); + + $config->setAppValue($app, 'installed_version', OC_App::getAppVersion($app)); + if (array_key_exists('ocsid', $info)) { + $config->setAppValue($app, 'ocsid', $info['ocsid']); + } + + //set remote/public handlers + foreach($info['remote'] as $name=>$path) { + $config->setAppValue('core', 'remote_'.$name, $app.'/'.$path); + } + foreach($info['public'] as $name=>$path) { + $config->setAppValue('core', 'public_'.$name, $app.'/'.$path); + } + + OC_App::setAppTypes($info['id']); + + return $info['id']; + } + + /** + * check the code of an app with some static code checks + * @param string $folder the folder of the app to check + * @return boolean true for app is o.k. and false for app is not o.k. + */ + public static function checkCode($folder) { + // is the code checker enabled? + if(!\OC::$server->getConfig()->getSystemValue('appcodechecker', false)) { + return true; + } + + $codeChecker = new CodeChecker(new PrivateCheck(new EmptyCheck())); + $errors = $codeChecker->analyseFolder($folder); + + return empty($errors); + } + + /** + * @param $basedir + */ + private static function includeAppScript($script) { + if ( file_exists($script) ){ + include $script; + } + } +} diff --git a/lib/private/legacy/json.php b/lib/private/legacy/json.php new file mode 100644 index 00000000000..74aebd476fb --- /dev/null +++ b/lib/private/legacy/json.php @@ -0,0 +1,179 @@ + + * @author Bernhard Posselt + * @author Felix Moeller + * @author Georg Ehrke + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Thomas Tanghus + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * Class OC_JSON + * @deprecated Use a AppFramework JSONResponse instead + */ +class OC_JSON{ + static protected $send_content_type_header = false; + /** + * set Content-Type header to jsonrequest + * @deprecated Use a AppFramework JSONResponse instead + */ + public static function setContentTypeHeader($type='application/json') { + if (!self::$send_content_type_header) { + // We send json data + header( 'Content-Type: '.$type . '; charset=utf-8'); + self::$send_content_type_header = true; + } + } + + /** + * Check if the app is enabled, send json error msg if not + * @param string $app + * @deprecated Use the AppFramework instead. It will automatically check if the app is enabled. + */ + public static function checkAppEnabled($app) { + if( !OC_App::isEnabled($app)) { + $l = \OC::$server->getL10N('lib'); + self::error(array( 'data' => array( 'message' => $l->t('Application is not enabled'), 'error' => 'application_not_enabled' ))); + exit(); + } + } + + /** + * Check if the user is logged in, send json error msg if not + * @deprecated Use annotation based ACLs from the AppFramework instead + */ + public static function checkLoggedIn() { + if( !OC_User::isLoggedIn()) { + $l = \OC::$server->getL10N('lib'); + http_response_code(\OCP\AppFramework\Http::STATUS_UNAUTHORIZED); + self::error(array( 'data' => array( 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ))); + exit(); + } + } + + /** + * Check an ajax get/post call if the request token is valid, send json error msg if not. + * @deprecated Use annotation based CSRF checks from the AppFramework instead + */ + public static function callCheck() { + if( !(\OC::$server->getRequest()->passesCSRFCheck())) { + $l = \OC::$server->getL10N('lib'); + self::error(array( 'data' => array( 'message' => $l->t('Token expired. Please reload page.'), 'error' => 'token_expired' ))); + exit(); + } + } + + /** + * Check if the user is a admin, send json error msg if not. + * @deprecated Use annotation based ACLs from the AppFramework instead + */ + public static function checkAdminUser() { + if( !OC_User::isAdminUser(OC_User::getUser())) { + $l = \OC::$server->getL10N('lib'); + self::error(array( 'data' => array( 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ))); + exit(); + } + } + + /** + * Check is a given user exists - send json error msg if not + * @param string $user + * @deprecated Use a AppFramework JSONResponse instead + */ + public static function checkUserExists($user) { + if (!OCP\User::userExists($user)) { + $l = \OC::$server->getL10N('lib'); + OCP\JSON::error(array('data' => array('message' => $l->t('Unknown user'), 'error' => 'unknown_user' ))); + exit; + } + } + + + /** + * Check if the user is a subadmin, send json error msg if not + * @deprecated Use annotation based ACLs from the AppFramework instead + */ + public static function checkSubAdminUser() { + $userObject = \OC::$server->getUserSession()->getUser(); + $isSubAdmin = false; + if($userObject !== null) { + $isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject); + } + + if(!$isSubAdmin) { + $l = \OC::$server->getL10N('lib'); + self::error(array( 'data' => array( 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ))); + exit(); + } + } + + /** + * Send json error msg + * @deprecated Use a AppFramework JSONResponse instead + */ + public static function error($data = array()) { + $data['status'] = 'error'; + self::encodedPrint($data); + } + + /** + * Send json success msg + * @deprecated Use a AppFramework JSONResponse instead + */ + public static function success($data = array()) { + $data['status'] = 'success'; + self::encodedPrint($data); + } + + /** + * Convert OC_L10N_String to string, for use in json encodings + */ + protected static function to_string(&$value) { + if ($value instanceof OC_L10N_String) { + $value = (string)$value; + } + } + + /** + * Encode and print $data in json format + * @deprecated Use a AppFramework JSONResponse instead + */ + public static function encodedPrint($data, $setContentType=true) { + if($setContentType) { + self::setContentTypeHeader(); + } + echo self::encode($data); + } + + /** + * Encode JSON + * @deprecated Use a AppFramework JSONResponse instead + */ + public static function encode($data) { + if (is_array($data)) { + array_walk_recursive($data, array('OC_JSON', 'to_string')); + } + return json_encode($data, JSON_HEX_TAG); + } +} diff --git a/lib/private/legacy/ocs.php b/lib/private/legacy/ocs.php new file mode 100644 index 00000000000..f48879a98b1 --- /dev/null +++ b/lib/private/legacy/ocs.php @@ -0,0 +1,42 @@ + + * @author Frank Karlitschek + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +use OCP\API; + +/** + * Class to handle open collaboration services API requests + */ +class OC_OCS { + /** + * Called when a not existing OCS endpoint has been called + */ + public static function notFound() { + $format = \OC::$server->getRequest()->getParam('format', 'xml'); + $txt='Invalid query, please check the syntax. API specifications are here:' + .' http://www.freedesktop.org/wiki/Specifications/open-collaboration-services. DEBUG OUTPUT:'."\n"; + OC_API::respond(new OC_OCS_Result(null, API::RESPOND_UNKNOWN_ERROR, $txt), $format); + } + +} diff --git a/lib/private/legacy/response.php b/lib/private/legacy/response.php new file mode 100644 index 00000000000..51e0ff75e6a --- /dev/null +++ b/lib/private/legacy/response.php @@ -0,0 +1,268 @@ + + * @author Bart Visscher + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin McCorkell + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +class OC_Response { + const STATUS_FOUND = 304; + const STATUS_NOT_MODIFIED = 304; + const STATUS_TEMPORARY_REDIRECT = 307; + const STATUS_BAD_REQUEST = 400; + const STATUS_NOT_FOUND = 404; + const STATUS_INTERNAL_SERVER_ERROR = 500; + const STATUS_SERVICE_UNAVAILABLE = 503; + + /** + * Enable response caching by sending correct HTTP headers + * @param integer $cache_time time to cache the response + * >0 cache time in seconds + * 0 and <0 enable default browser caching + * null cache indefinitely + */ + static public function enableCaching($cache_time = null) { + if (is_numeric($cache_time)) { + header('Pragma: public');// enable caching in IE + if ($cache_time > 0) { + self::setExpiresHeader('PT'.$cache_time.'S'); + header('Cache-Control: max-age='.$cache_time.', must-revalidate'); + } + else { + self::setExpiresHeader(0); + header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); + } + } + else { + header('Cache-Control: cache'); + header('Pragma: cache'); + } + + } + + /** + * disable browser caching + * @see enableCaching with cache_time = 0 + */ + static public function disableCaching() { + self::enableCaching(0); + } + + /** + * Set response status + * @param int $status a HTTP status code, see also the STATUS constants + */ + static public function setStatus($status) { + $protocol = \OC::$server->getRequest()->getHttpProtocol(); + switch($status) { + case self::STATUS_NOT_MODIFIED: + $status = $status . ' Not Modified'; + break; + case self::STATUS_TEMPORARY_REDIRECT: + if ($protocol == 'HTTP/1.1') { + $status = $status . ' Temporary Redirect'; + break; + } else { + $status = self::STATUS_FOUND; + // fallthrough + } + case self::STATUS_FOUND; + $status = $status . ' Found'; + break; + case self::STATUS_NOT_FOUND; + $status = $status . ' Not Found'; + break; + case self::STATUS_INTERNAL_SERVER_ERROR; + $status = $status . ' Internal Server Error'; + break; + case self::STATUS_SERVICE_UNAVAILABLE; + $status = $status . ' Service Unavailable'; + break; + } + header($protocol.' '.$status); + } + + /** + * Send redirect response + * @param string $location to redirect to + */ + static public function redirect($location) { + self::setStatus(self::STATUS_TEMPORARY_REDIRECT); + header('Location: '.$location); + } + + /** + * Set response expire time + * @param string|DateTime $expires date-time when the response expires + * string for DateInterval from now + * DateTime object when to expire response + */ + static public function setExpiresHeader($expires) { + if (is_string($expires) && $expires[0] == 'P') { + $interval = $expires; + $expires = new DateTime('now'); + $expires->add(new DateInterval($interval)); + } + if ($expires instanceof DateTime) { + $expires->setTimezone(new DateTimeZone('GMT')); + $expires = $expires->format(DateTime::RFC2822); + } + header('Expires: '.$expires); + } + + /** + * Checks and set ETag header, when the request matches sends a + * 'not modified' response + * @param string $etag token to use for modification check + */ + static public function setETagHeader($etag) { + if (empty($etag)) { + return; + } + $etag = '"'.$etag.'"'; + if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && + trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) { + self::setStatus(self::STATUS_NOT_MODIFIED); + exit; + } + header('ETag: '.$etag); + } + + /** + * Checks and set Last-Modified header, when the request matches sends a + * 'not modified' response + * @param int|DateTime|string $lastModified time when the response was last modified + */ + static public function setLastModifiedHeader($lastModified) { + if (empty($lastModified)) { + return; + } + if (is_int($lastModified)) { + $lastModified = gmdate(DateTime::RFC2822, $lastModified); + } + if ($lastModified instanceof DateTime) { + $lastModified = $lastModified->format(DateTime::RFC2822); + } + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && + trim($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModified) { + self::setStatus(self::STATUS_NOT_MODIFIED); + exit; + } + header('Last-Modified: '.$lastModified); + } + + /** + * Sets the content disposition header (with possible workarounds) + * @param string $filename file name + * @param string $type disposition type, either 'attachment' or 'inline' + */ + static public function setContentDispositionHeader( $filename, $type = 'attachment' ) { + if (\OC::$server->getRequest()->isUserAgent( + [ + \OC\AppFramework\Http\Request::USER_AGENT_IE, + \OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME, + \OC\AppFramework\Http\Request::USER_AGENT_FREEBOX, + ])) { + header( 'Content-Disposition: ' . rawurlencode($type) . '; filename="' . rawurlencode( $filename ) . '"' ); + } else { + header( 'Content-Disposition: ' . rawurlencode($type) . '; filename*=UTF-8\'\'' . rawurlencode( $filename ) + . '; filename="' . rawurlencode( $filename ) . '"' ); + } + } + + /** + * Sets the content length header (with possible workarounds) + * @param string|int|float $length Length to be sent + */ + static public function setContentLengthHeader($length) { + if (PHP_INT_SIZE === 4) { + if ($length > PHP_INT_MAX && stripos(PHP_SAPI, 'apache') === 0) { + // Apache PHP SAPI casts Content-Length headers to PHP integers. + // This enforces a limit of PHP_INT_MAX (2147483647 on 32-bit + // platforms). So, if the length is greater than PHP_INT_MAX, + // we just do not send a Content-Length header to prevent + // bodies from being received incompletely. + return; + } + // Convert signed integer or float to unsigned base-10 string. + $lfh = new \OC\LargeFileHelper; + $length = $lfh->formatUnsignedInteger($length); + } + header('Content-Length: '.$length); + } + + /** + * Send file as response, checking and setting caching headers + * @param string $filepath of file to send + * @deprecated 8.1.0 - Use \OCP\AppFramework\Http\StreamResponse or another AppFramework controller instead + */ + static public function sendFile($filepath) { + $fp = fopen($filepath, 'rb'); + if ($fp) { + self::setLastModifiedHeader(filemtime($filepath)); + self::setETagHeader(md5_file($filepath)); + + self::setContentLengthHeader(filesize($filepath)); + fpassthru($fp); + } + else { + self::setStatus(self::STATUS_NOT_FOUND); + } + } + + /** + * This function adds some security related headers to all requests served via base.php + * The implementation of this function has to happen here to ensure that all third-party + * components (e.g. SabreDAV) also benefit from this headers. + */ + public static function addSecurityHeaders() { + /** + * FIXME: Content Security Policy for legacy ownCloud components. This + * can be removed once \OCP\AppFramework\Http\Response from the AppFramework + * is used everywhere. + * @see \OCP\AppFramework\Http\Response::getHeaders + */ + $policy = 'default-src \'self\'; ' + . 'script-src \'self\' \'unsafe-eval\'; ' + . 'style-src \'self\' \'unsafe-inline\'; ' + . 'frame-src *; ' + . 'img-src * data: blob:; ' + . 'font-src \'self\' data:; ' + . 'media-src *; ' + . 'connect-src *'; + header('Content-Security-Policy:' . $policy); + + // Send fallback headers for installations that don't have the possibility to send + // custom headers on the webserver side + if(getenv('modHeadersAvailable') !== 'true') { + header('X-XSS-Protection: 1; mode=block'); // Enforce browser based XSS filters + header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE + header('X-Frame-Options: Sameorigin'); // Disallow iFraming from other domains + header('X-Robots-Tag: none'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag + header('X-Download-Options: noopen'); // https://msdn.microsoft.com/en-us/library/jj542450(v=vs.85).aspx + header('X-Permitted-Cross-Domain-Policies: none'); // https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html + } + } + +} diff --git a/lib/private/legacy/template.php b/lib/private/legacy/template.php new file mode 100644 index 00000000000..84b963e4ab6 --- /dev/null +++ b/lib/private/legacy/template.php @@ -0,0 +1,433 @@ + + * @author Bart Visscher + * @author Björn Schießle + * @author Brice Maron + * @author Frank Karlitschek + * @author Hendrik Leppelsack + * @author Individual IT Services + * @author Jakob Sack + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Raghu Nayyar + * @author Robin Appelman + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use OC\TemplateLayout; + +require_once __DIR__.'/../template/functions.php'; + +/** + * This class provides the templates for ownCloud. + */ +class OC_Template extends \OC\Template\Base { + + /** @var string */ + private $renderAs; // Create a full page? + + /** @var string */ + private $path; // The path to the template + + /** @var array */ + private $headers = array(); //custom headers + + /** @var string */ + protected $app; // app id + + protected static $initTemplateEngineFirstRun = true; + + /** + * Constructor + * + * @param string $app app providing the template + * @param string $name of the template file (without suffix) + * @param string $renderAs If $renderAs is set, OC_Template will try to + * produce a full page in the according layout. For + * now, $renderAs can be set to "guest", "user" or + * "admin". + * @param bool $registerCall = true + */ + public function __construct( $app, $name, $renderAs = "", $registerCall = true ) { + // Read the selected theme from the config file + self::initTemplateEngine($renderAs); + + $theme = OC_Util::getTheme(); + + $requestToken = (OC::$server->getSession() && $registerCall) ? \OCP\Util::callRegister() : ''; + + $parts = explode('/', $app); // fix translation when app is something like core/lostpassword + $l10n = \OC::$server->getL10N($parts[0]); + $themeDefaults = new OC_Defaults(); + + list($path, $template) = $this->findTemplate($theme, $app, $name); + + // Set the private data + $this->renderAs = $renderAs; + $this->path = $path; + $this->app = $app; + + parent::__construct($template, $requestToken, $l10n, $themeDefaults); + } + + /** + * @param string $renderAs + */ + public static function initTemplateEngine($renderAs) { + if (self::$initTemplateEngineFirstRun){ + + //apps that started before the template initialization can load their own scripts/styles + //so to make sure this scripts/styles here are loaded first we use OC_Util::addScript() with $prepend=true + //meaning the last script/style in this list will be loaded first + if (\OC::$server->getSystemConfig()->getValue ('installed', false) && $renderAs !== 'error' && !\OCP\Util::needUpgrade()) { + if (\OC::$server->getConfig ()->getAppValue ( 'core', 'backgroundjobs_mode', 'ajax' ) == 'ajax') { + OC_Util::addScript ( 'backgroundjobs', null, true ); + } + } + + OC_Util::addStyle("tooltip",null,true); + OC_Util::addStyle('jquery-ui-fixes',null,true); + OC_Util::addVendorStyle('jquery-ui/themes/base/jquery-ui',null,true); + OC_Util::addStyle("mobile",null,true); + OC_Util::addStyle("multiselect",null,true); + OC_Util::addStyle("fixes",null,true); + OC_Util::addStyle("global",null,true); + OC_Util::addStyle("apps",null,true); + OC_Util::addStyle("fonts",null,true); + OC_Util::addStyle("icons",null,true); + OC_Util::addStyle("header",null,true); + OC_Util::addStyle("inputs",null,true); + OC_Util::addStyle("styles",null,true); + + // avatars + if (\OC::$server->getSystemConfig()->getValue('enable_avatars', true) === true) { + \OC_Util::addScript('jquery.avatar', null, true); + \OC_Util::addScript('placeholder', null, true); + } + + OC_Util::addScript('oc-backbone', null, true); + OC_Util::addVendorScript('core', 'backbone/backbone', true); + OC_Util::addVendorScript('snapjs/dist/latest/snap', null, true); + OC_Util::addScript('mimetypelist', null, true); + OC_Util::addScript('mimetype', null, true); + OC_Util::addScript("apps", null, true); + OC_Util::addScript("oc-requesttoken", null, true); + OC_Util::addScript('search', 'search', true); + OC_Util::addScript("config", null, true); + OC_Util::addScript("eventsource", null, true); + OC_Util::addScript("octemplate", null, true); + OC_Util::addTranslations("core", null, true); + OC_Util::addScript("l10n", null, true); + OC_Util::addScript("js", null, true); + OC_Util::addScript("oc-dialogs", null, true); + OC_Util::addScript("jquery.ocdialog", null, true); + OC_Util::addStyle("jquery.ocdialog"); + OC_Util::addScript("compatibility", null, true); + OC_Util::addScript("placeholders", null, true); + OC_Util::addScript('files/fileinfo'); + OC_Util::addScript('files/client'); + + // Add the stuff we need always + // following logic will import all vendor libraries that are + // specified in core/js/core.json + $fileContent = file_get_contents(OC::$SERVERROOT . '/core/js/core.json'); + if($fileContent !== false) { + $coreDependencies = json_decode($fileContent, true); + foreach(array_reverse($coreDependencies['vendor']) as $vendorLibrary) { + // remove trailing ".js" as addVendorScript will append it + OC_Util::addVendorScript( + substr($vendorLibrary, 0, strlen($vendorLibrary) - 3),null,true); + } + } else { + throw new \Exception('Cannot read core/js/core.json'); + } + + if (\OC::$server->getRequest()->isUserAgent([\OC\AppFramework\Http\Request::USER_AGENT_IE])) { + // polyfill for btoa/atob for IE friends + OC_Util::addVendorScript('base64/base64'); + // shim for the davclient.js library + \OCP\Util::addScript('files/iedavclient'); + } + + self::$initTemplateEngineFirstRun = false; + } + + } + + + /** + * find the template with the given name + * @param string $name of the template file (without suffix) + * + * Will select the template file for the selected theme. + * Checking all the possible locations. + * @param string $theme + * @param string $app + * @return string[] + */ + protected function findTemplate($theme, $app, $name) { + // Check if it is a app template or not. + if( $app !== '' ) { + $dirs = $this->getAppTemplateDirs($theme, $app, OC::$SERVERROOT, OC_App::getAppPath($app)); + } else { + $dirs = $this->getCoreTemplateDirs($theme, OC::$SERVERROOT); + } + $locator = new \OC\Template\TemplateFileLocator( $dirs ); + $template = $locator->find($name); + $path = $locator->getPath(); + return array($path, $template); + } + + /** + * Add a custom element to the header + * @param string $tag tag name of the element + * @param array $attributes array of attributes for the element + * @param string $text the text content for the element. If $text is null then the + * element will be written as empty element. So use "" to get a closing tag. + */ + public function addHeader($tag, $attributes, $text=null) { + $this->headers[]= array( + 'tag' => $tag, + 'attributes' => $attributes, + 'text' => $text + ); + } + + /** + * Process the template + * @return boolean|string + * + * This function process the template. If $this->renderAs is set, it + * will produce a full page. + */ + public function fetchPage($additionalParams = null) { + $data = parent::fetchPage($additionalParams); + + if( $this->renderAs ) { + $page = new TemplateLayout($this->renderAs, $this->app); + + // Add custom headers + $headers = ''; + foreach(OC_Util::$headers as $header) { + $headers .= '<'.\OCP\Util::sanitizeHTML($header['tag']); + foreach($header['attributes'] as $name=>$value) { + $headers .= ' '.\OCP\Util::sanitizeHTML($name).'="'.\OCP\Util::sanitizeHTML($value).'"'; + } + if ($header['text'] !== null) { + $headers .= '>'.\OCP\Util::sanitizeHTML($header['text']).''; + } else { + $headers .= '/>'; + } + } + + $page->assign('headers', $headers); + + $page->assign('content', $data); + return $page->fetchPage(); + } + + return $data; + } + + /** + * Include template + * + * @param string $file + * @param array|null $additionalParams + * @return string returns content of included template + * + * Includes another template. use inc('template'); ?> to + * do this. + */ + public function inc( $file, $additionalParams = null ) { + return $this->load($this->path.$file.'.php', $additionalParams); + } + + /** + * Shortcut to print a simple page for users + * @param string $application The application we render the template for + * @param string $name Name of the template + * @param array $parameters Parameters for the template + * @return boolean|null + */ + public static function printUserPage( $application, $name, $parameters = array() ) { + $content = new OC_Template( $application, $name, "user" ); + foreach( $parameters as $key => $value ) { + $content->assign( $key, $value ); + } + print $content->printPage(); + } + + /** + * Shortcut to print a simple page for admins + * @param string $application The application we render the template for + * @param string $name Name of the template + * @param array $parameters Parameters for the template + * @return bool + */ + public static function printAdminPage( $application, $name, $parameters = array() ) { + $content = new OC_Template( $application, $name, "admin" ); + foreach( $parameters as $key => $value ) { + $content->assign( $key, $value ); + } + return $content->printPage(); + } + + /** + * Shortcut to print a simple page for guests + * @param string $application The application we render the template for + * @param string $name Name of the template + * @param array|string $parameters Parameters for the template + * @return bool + */ + public static function printGuestPage( $application, $name, $parameters = array() ) { + $content = new OC_Template( $application, $name, "guest" ); + foreach( $parameters as $key => $value ) { + $content->assign( $key, $value ); + } + return $content->printPage(); + } + + /** + * Print a fatal error page and terminates the script + * @param string $error_msg The error message to show + * @param string $hint An optional hint message - needs to be properly escaped + */ + public static function printErrorPage( $error_msg, $hint = '' ) { + try { + $content = new \OC_Template( '', 'error', 'error', false ); + $errors = array(array('error' => $error_msg, 'hint' => $hint)); + $content->assign( 'errors', $errors ); + $content->printPage(); + } catch (\Exception $e) { + $logger = \OC::$server->getLogger(); + $logger->error("$error_msg $hint", ['app' => 'core']); + $logger->logException($e, ['app' => 'core']); + + header(self::getHttpProtocol() . ' 500 Internal Server Error'); + header('Content-Type: text/plain; charset=utf-8'); + print("$error_msg $hint"); + } + die(); + } + + /** + * print error page using Exception details + * @param Exception | Throwable $exception + */ + public static function printExceptionErrorPage($exception, $fetchPage = false) { + try { + $request = \OC::$server->getRequest(); + $content = new \OC_Template('', 'exception', 'error', false); + $content->assign('errorClass', get_class($exception)); + $content->assign('errorMsg', $exception->getMessage()); + $content->assign('errorCode', $exception->getCode()); + $content->assign('file', $exception->getFile()); + $content->assign('line', $exception->getLine()); + $content->assign('trace', $exception->getTraceAsString()); + $content->assign('debugMode', \OC::$server->getSystemConfig()->getValue('debug', false)); + $content->assign('remoteAddr', $request->getRemoteAddress()); + $content->assign('requestID', $request->getId()); + if ($fetchPage) { + return $content->fetchPage(); + } + $content->printPage(); + } catch (\Exception $e) { + $logger = \OC::$server->getLogger(); + $logger->logException($exception, ['app' => 'core']); + $logger->logException($e, ['app' => 'core']); + + header(self::getHttpProtocol() . ' 500 Internal Server Error'); + header('Content-Type: text/plain; charset=utf-8'); + print("Internal Server Error\n\n"); + print("The server encountered an internal error and was unable to complete your request.\n"); + print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n"); + print("More details can be found in the server log.\n"); + } + die(); + } + + /** + * This is only here to reduce the dependencies in case of an exception to + * still be able to print a plain error message. + * + * Returns the used HTTP protocol. + * + * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0. + * @internal Don't use this - use AppFramework\Http\Request->getHttpProtocol instead + */ + protected static function getHttpProtocol() { + $claimedProtocol = strtoupper($_SERVER['SERVER_PROTOCOL']); + $validProtocols = [ + 'HTTP/1.0', + 'HTTP/1.1', + 'HTTP/2', + ]; + if(in_array($claimedProtocol, $validProtocols, true)) { + return $claimedProtocol; + } + return 'HTTP/1.1'; + } + + /** + * @return bool + */ + public static function isAssetPipelineEnabled() { + try { + if (\OCP\Util::needUpgrade()) { + // Don't use the compiled asset when we need to do an update + return false; + } + } catch (\Exception $e) { + // Catch any exception, because this code is also called when displaying + // an exception error page. + return false; + } + + // asset management enabled? + $config = \OC::$server->getConfig(); + $useAssetPipeline = $config->getSystemValue('asset-pipeline.enabled', false); + if (!$useAssetPipeline) { + return false; + } + + // assets folder exists? + $assetDir = $config->getSystemValue('assetdirectory', \OC::$SERVERROOT) . '/assets'; + if (!is_dir($assetDir)) { + if (!mkdir($assetDir)) { + \OCP\Util::writeLog('assets', + "Folder <$assetDir> does not exist and/or could not be generated.", \OCP\Util::ERROR); + return false; + } + } + + // assets folder can be accessed? + if (!touch($assetDir."/.oc")) { + \OCP\Util::writeLog('assets', + "Folder <$assetDir> could not be accessed.", \OCP\Util::ERROR); + return false; + } + return $useAssetPipeline; + } + +} diff --git a/lib/private/legacy/user.php b/lib/private/legacy/user.php new file mode 100644 index 00000000000..11c35daa0de --- /dev/null +++ b/lib/private/legacy/user.php @@ -0,0 +1,639 @@ + + * @author Andreas Fischer + * @author Arthur Schiwon + * @author Bart Visscher + * @author Bartek Przybylski + * @author Björn Schießle + * @author Florian Preinstorfer + * @author Georg Ehrke + * @author Jakob Sack + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author shkdee + * @author Thomas Müller + * @author Tom Needham + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * This class provides wrapper methods for user management. Multiple backends are + * supported. User management operations are delegated to the configured backend for + * execution. + * + * Note that &run is deprecated and won't work anymore. + * + * Hooks provided: + * pre_createUser(&run, uid, password) + * post_createUser(uid, password) + * pre_deleteUser(&run, uid) + * post_deleteUser(uid) + * pre_setPassword(&run, uid, password, recoveryPassword) + * post_setPassword(uid, password, recoveryPassword) + * pre_login(&run, uid, password) + * post_login(uid) + * logout() + */ +class OC_User { + + /** + * @return \OC\User\Session + */ + public static function getUserSession() { + return OC::$server->getUserSession(); + } + + private static $_backends = array(); + + private static $_usedBackends = array(); + + private static $_setupedBackends = array(); + + // bool, stores if a user want to access a resource anonymously, e.g if he opens a public link + private static $incognitoMode = false; + + /** + * Adds the backend to the list of used backends + * + * @param string|\OCP\UserInterface $backend default: database The backend to use for user management + * @return bool + * + * Set the User Authentication Module + */ + public static function useBackend($backend = 'database') { + if ($backend instanceof \OCP\UserInterface) { + self::$_usedBackends[get_class($backend)] = $backend; + \OC::$server->getUserManager()->registerBackend($backend); + } else { + // You'll never know what happens + if (null === $backend OR !is_string($backend)) { + $backend = 'database'; + } + + // Load backend + switch ($backend) { + case 'database': + case 'mysql': + case 'sqlite': + \OCP\Util::writeLog('core', 'Adding user backend ' . $backend . '.', \OCP\Util::DEBUG); + self::$_usedBackends[$backend] = new OC_User_Database(); + \OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]); + break; + case 'dummy': + self::$_usedBackends[$backend] = new \Test\Util\User\Dummy(); + \OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]); + break; + default: + \OCP\Util::writeLog('core', 'Adding default user backend ' . $backend . '.', \OCP\Util::DEBUG); + $className = 'OC_USER_' . strToUpper($backend); + self::$_usedBackends[$backend] = new $className(); + \OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]); + break; + } + } + return true; + } + + /** + * remove all used backends + */ + public static function clearBackends() { + self::$_usedBackends = array(); + \OC::$server->getUserManager()->clearBackends(); + } + + /** + * setup the configured backends in config.php + */ + public static function setupBackends() { + OC_App::loadApps(array('prelogin')); + $backends = \OC::$server->getSystemConfig()->getValue('user_backends', array()); + foreach ($backends as $i => $config) { + $class = $config['class']; + $arguments = $config['arguments']; + if (class_exists($class)) { + if (array_search($i, self::$_setupedBackends) === false) { + // make a reflection object + $reflectionObj = new ReflectionClass($class); + + // use Reflection to create a new instance, using the $args + $backend = $reflectionObj->newInstanceArgs($arguments); + self::useBackend($backend); + self::$_setupedBackends[] = $i; + } else { + \OCP\Util::writeLog('core', 'User backend ' . $class . ' already initialized.', \OCP\Util::DEBUG); + } + } else { + \OCP\Util::writeLog('core', 'User backend ' . $class . ' not found.', \OCP\Util::ERROR); + } + } + } + + /** + * Try to login a user + * + * @param string $loginname The login name of the user to log in + * @param string $password The password of the user + * @return boolean|null + * + * Log in a user and regenerate a new session - if the password is ok + */ + public static function login($loginname, $password) { + $result = self::getUserSession()->login($loginname, $password); + if ($result) { + // Refresh the token + \OC::$server->getCsrfTokenManager()->refreshToken(); + //we need to pass the user name, which may differ from login name + $user = self::getUserSession()->getUser()->getUID(); + OC_Util::setupFS($user); + //trigger creation of user home and /files folder + \OC::$server->getUserFolder($user); + } + return $result; + } + + /** + * Try to login a user using the magic cookie (remember login) + * + * @param string $uid The username of the user to log in + * @param string $token + * @return bool + */ + public static function loginWithCookie($uid, $token) { + return self::getUserSession()->loginWithCookie($uid, $token); + } + + /** + * Try to login a user, assuming authentication + * has already happened (e.g. via Single Sign On). + * + * Log in a user and regenerate a new session. + * + * @param \OCP\Authentication\IApacheBackend $backend + * @return bool + */ + public static function loginWithApache(\OCP\Authentication\IApacheBackend $backend) { + + $uid = $backend->getCurrentUserId(); + $run = true; + OC_Hook::emit("OC_User", "pre_login", array("run" => &$run, "uid" => $uid)); + + if ($uid) { + if (self::getUser() !== $uid) { + self::setUserId($uid); + self::setDisplayName($uid); + self::getUserSession()->setLoginName($uid); + // setup the filesystem + OC_Util::setupFS($uid); + //trigger creation of user home and /files folder + \OC::$server->getUserFolder($uid); + + OC_Hook::emit("OC_User", "post_login", array("uid" => $uid, 'password' => '')); + } + return true; + } + return false; + } + + /** + * Verify with Apache whether user is authenticated. + * + * @return boolean|null + * true: authenticated + * false: not authenticated + * null: not handled / no backend available + */ + public static function handleApacheAuth() { + $backend = self::findFirstActiveUsedBackend(); + if ($backend) { + OC_App::loadApps(); + + //setup extra user backends + self::setupBackends(); + self::unsetMagicInCookie(); + + return self::loginWithApache($backend); + } + + return null; + } + + + /** + * Sets user id for session and triggers emit + */ + public static function setUserId($uid) { + $userSession = \OC::$server->getUserSession(); + $userManager = \OC::$server->getUserManager(); + if ($user = $userManager->get($uid)) { + $userSession->setUser($user); + } else { + \OC::$server->getSession()->set('user_id', $uid); + } + } + + /** + * Sets user display name for session + * + * @param string $uid + * @param string $displayName + * @return bool Whether the display name could get set + */ + public static function setDisplayName($uid, $displayName = null) { + if (is_null($displayName)) { + $displayName = $uid; + } + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + return $user->setDisplayName($displayName); + } else { + return false; + } + } + + /** + * Tries to login the user with HTTP Basic Authentication + */ + public static function tryBasicAuthLogin() { + if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) { + $result = \OC_User::login($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); + if($result === true) { + /** + * Add DAV authenticated. This should in an ideal world not be + * necessary but the iOS App reads cookies from anywhere instead + * only the DAV endpoint. + * This makes sure that the cookies will be valid for the whole scope + * @see https://github.com/owncloud/core/issues/22893 + */ + \OC::$server->getSession()->set( + \OCA\DAV\Connector\Sabre\Auth::DAV_AUTHENTICATED, + \OC::$server->getUserSession()->getUser()->getUID() + ); + } + } + } + + /** + * Check if the user is logged in, considers also the HTTP basic credentials + * + * @return bool + */ + public static function isLoggedIn() { + if (\OC::$server->getSession()->get('user_id') !== null && self::$incognitoMode === false) { + return self::userExists(\OC::$server->getSession()->get('user_id')); + } + + return false; + } + + /** + * set incognito mode, e.g. if a user wants to open a public link + * + * @param bool $status + */ + public static function setIncognitoMode($status) { + self::$incognitoMode = $status; + } + + /** + * get incognito mode status + * + * @return bool + */ + public static function isIncognitoMode() { + return self::$incognitoMode; + } + + /** + * Supplies an attribute to the logout hyperlink. The default behaviour + * is to return an href with '?logout=true' appended. However, it can + * supply any attribute(s) which are valid for . + * + * @return string with one or more HTML attributes. + */ + public static function getLogoutAttribute() { + $backend = self::findFirstActiveUsedBackend(); + if ($backend) { + return $backend->getLogoutAttribute(); + } + + $logoutUrl = \OC::$server->getURLGenerator()->linkToRouteAbsolute( + 'core.login.logout', + [ + 'requesttoken' => \OCP\Util::callRegister(), + ] + ); + + return 'href="'.$logoutUrl.'"'; + } + + /** + * Check if the user is an admin user + * + * @param string $uid uid of the admin + * @return bool + */ + public static function isAdminUser($uid) { + if (OC_Group::inGroup($uid, 'admin') && self::$incognitoMode === false) { + return true; + } + return false; + } + + + /** + * get the user id of the user currently logged in. + * + * @return string|bool uid or false + */ + public static function getUser() { + $uid = \OC::$server->getSession() ? \OC::$server->getSession()->get('user_id') : null; + if (!is_null($uid) && self::$incognitoMode === false) { + return $uid; + } else { + return false; + } + } + + /** + * get the display name of the user currently logged in. + * + * @param string $uid + * @return string uid or false + */ + public static function getDisplayName($uid = null) { + if ($uid) { + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + return $user->getDisplayName(); + } else { + return $uid; + } + } else { + $user = self::getUserSession()->getUser(); + if ($user) { + return $user->getDisplayName(); + } else { + return false; + } + } + } + + /** + * Autogenerate a password + * + * @return string + * + * generates a password + */ + public static function generatePassword() { + return \OC::$server->getSecureRandom()->generate(30); + } + + /** + * Set password + * + * @param string $uid The username + * @param string $password The new password + * @param string $recoveryPassword for the encryption app to reset encryption keys + * @return bool + * + * Change the password of a user + */ + public static function setPassword($uid, $password, $recoveryPassword = null) { + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + return $user->setPassword($password, $recoveryPassword); + } else { + return false; + } + } + + /** + * Check whether user can change his avatar + * + * @param string $uid The username + * @return bool + * + * Check whether a specified user can change his avatar + */ + public static function canUserChangeAvatar($uid) { + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + return $user->canChangeAvatar(); + } else { + return false; + } + } + + /** + * Check whether user can change his password + * + * @param string $uid The username + * @return bool + * + * Check whether a specified user can change his password + */ + public static function canUserChangePassword($uid) { + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + return $user->canChangePassword(); + } else { + return false; + } + } + + /** + * Check whether user can change his display name + * + * @param string $uid The username + * @return bool + * + * Check whether a specified user can change his display name + */ + public static function canUserChangeDisplayName($uid) { + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + return $user->canChangeDisplayName(); + } else { + return false; + } + } + + /** + * Check if the password is correct + * + * @param string $uid The username + * @param string $password The password + * @return string|false user id a string on success, false otherwise + * + * Check if the password is correct without logging in the user + * returns the user id or false + */ + public static function checkPassword($uid, $password) { + $manager = \OC::$server->getUserManager(); + $username = $manager->checkPassword($uid, $password); + if ($username !== false) { + return $username->getUID(); + } + return false; + } + + /** + * @param string $uid The username + * @return string + * + * returns the path to the users home directory + * @deprecated Use \OC::$server->getUserManager->getHome() + */ + public static function getHome($uid) { + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + return $user->getHome(); + } else { + return \OC::$server->getSystemConfig()->getValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $uid; + } + } + + /** + * Get a list of all users + * + * @return array an array of all uids + * + * Get a list of all users. + * @param string $search + * @param integer $limit + * @param integer $offset + */ + public static function getUsers($search = '', $limit = null, $offset = null) { + $users = \OC::$server->getUserManager()->search($search, $limit, $offset); + $uids = array(); + foreach ($users as $user) { + $uids[] = $user->getUID(); + } + return $uids; + } + + /** + * Get a list of all users display name + * + * @param string $search + * @param int $limit + * @param int $offset + * @return array associative array with all display names (value) and corresponding uids (key) + * + * Get a list of all display names and user ids. + * @deprecated Use \OC::$server->getUserManager->searchDisplayName($search, $limit, $offset) instead. + */ + public static function getDisplayNames($search = '', $limit = null, $offset = null) { + $displayNames = array(); + $users = \OC::$server->getUserManager()->searchDisplayName($search, $limit, $offset); + foreach ($users as $user) { + $displayNames[$user->getUID()] = $user->getDisplayName(); + } + return $displayNames; + } + + /** + * check if a user exists + * + * @param string $uid the username + * @return boolean + */ + public static function userExists($uid) { + return \OC::$server->getUserManager()->userExists($uid); + } + + /** + * disables a user + * + * @param string $uid the user to disable + */ + public static function disableUser($uid) { + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + $user->setEnabled(false); + } + } + + /** + * enable a user + * + * @param string $uid + */ + public static function enableUser($uid) { + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + $user->setEnabled(true); + } + } + + /** + * checks if a user is enabled + * + * @param string $uid + * @return bool + */ + public static function isEnabled($uid) { + $user = \OC::$server->getUserManager()->get($uid); + if ($user) { + return $user->isEnabled(); + } else { + return false; + } + } + + /** + * Set cookie value to use in next page load + * + * @param string $username username to be set + * @param string $token + */ + public static function setMagicInCookie($username, $token) { + self::getUserSession()->setMagicInCookie($username, $token); + } + + /** + * Remove cookie for "remember username" + */ + public static function unsetMagicInCookie() { + self::getUserSession()->unsetMagicInCookie(); + } + + /** + * Returns the first active backend from self::$_usedBackends. + * + * @return OCP\Authentication\IApacheBackend|null if no backend active, otherwise OCP\Authentication\IApacheBackend + */ + private static function findFirstActiveUsedBackend() { + foreach (self::$_usedBackends as $backend) { + if ($backend instanceof OCP\Authentication\IApacheBackend) { + if ($backend->isSessionActive()) { + return $backend; + } + } + } + + return null; + } +} diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php new file mode 100644 index 00000000000..b3432470f03 --- /dev/null +++ b/lib/private/legacy/util.php @@ -0,0 +1,1450 @@ + + * @author Andreas Böhler + * @author Andreas Fischer + * @author Arthur Schiwon + * @author Bart Visscher + * @author Bernhard Posselt + * @author Birk Borkason + * @author Björn Schießle + * @author Brice Maron + * @author Christopher Schäpers + * @author Clark Tomlinson + * @author cmeh + * @author Florin Peter + * @author Frank Karlitschek + * @author Georg Ehrke + * @author helix84 + * @author Individual IT Services + * @author Jakob Sack + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Markus Goetz + * @author Martin Mattel + * @author Marvin Thomas Rabe + * @author Michael Gapczynski + * @author Michael Göhler + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Stefan Rado + * @author Thomas Müller + * @author Thomas Schmidt + * @author Thomas Tanghus + * @author Victor Dubiniuk + * @author Vincent Chan + * @author Vincent Petry + * @author Volkan Gezer + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IUser; + +class OC_Util { + public static $scripts = array(); + public static $styles = array(); + public static $headers = array(); + private static $rootMounted = false; + private static $fsSetup = false; + + protected static function getAppManager() { + return \OC::$server->getAppManager(); + } + + private static function initLocalStorageRootFS() { + // mount local file backend as root + $configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data"); + //first set up the local "root" storage + \OC\Files\Filesystem::initMountManager(); + if (!self::$rootMounted) { + \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => $configDataDirectory), '/'); + self::$rootMounted = true; + } + } + + /** + * mounting an object storage as the root fs will in essence remove the + * necessity of a data folder being present. + * TODO make home storage aware of this and use the object storage instead of local disk access + * + * @param array $config containing 'class' and optional 'arguments' + */ + private static function initObjectStoreRootFS($config) { + // check misconfiguration + if (empty($config['class'])) { + \OCP\Util::writeLog('files', 'No class given for objectstore', \OCP\Util::ERROR); + } + if (!isset($config['arguments'])) { + $config['arguments'] = array(); + } + + // instantiate object store implementation + $config['arguments']['objectstore'] = new $config['class']($config['arguments']); + // mount with plain / root object store implementation + $config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage'; + + // mount object storage as root + \OC\Files\Filesystem::initMountManager(); + if (!self::$rootMounted) { + \OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/'); + self::$rootMounted = true; + } + } + + /** + * Can be set up + * + * @param string $user + * @return boolean + * @description configure the initial filesystem based on the configuration + */ + public static function setupFS($user = '') { + //setting up the filesystem twice can only lead to trouble + if (self::$fsSetup) { + return false; + } + + \OC::$server->getEventLogger()->start('setup_fs', 'Setup filesystem'); + + // If we are not forced to load a specific user we load the one that is logged in + if ($user === null) { + $user = ''; + } else if ($user == "" && OC_User::isLoggedIn()) { + $user = OC_User::getUser(); + } + + // load all filesystem apps before, so no setup-hook gets lost + OC_App::loadApps(array('filesystem')); + + // the filesystem will finish when $user is not empty, + // mark fs setup here to avoid doing the setup from loading + // OC_Filesystem + if ($user != '') { + self::$fsSetup = true; + } + + \OC\Files\Filesystem::initMountManager(); + + \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(false); + \OC\Files\Filesystem::addStorageWrapper('mount_options', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { + if ($storage->instanceOfStorage('\OC\Files\Storage\Common')) { + /** @var \OC\Files\Storage\Common $storage */ + $storage->setMountOptions($mount->getOptions()); + } + return $storage; + }); + + \OC\Files\Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { + if (!$mount->getOption('enable_sharing', true)) { + return new \OC\Files\Storage\Wrapper\PermissionsMask([ + 'storage' => $storage, + 'mask' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_SHARE + ]); + } + return $storage; + }); + + // install storage availability wrapper, before most other wrappers + \OC\Files\Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, $storage) { + if (!$storage->instanceOfStorage('\OC\Files\Storage\Shared') && !$storage->isLocal()) { + return new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]); + } + return $storage; + }); + + \OC\Files\Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) { + // set up quota for home storages, even for other users + // which can happen when using sharing + + /** + * @var \OC\Files\Storage\Storage $storage + */ + if ($storage->instanceOfStorage('\OC\Files\Storage\Home') + || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage') + ) { + /** @var \OC\Files\Storage\Home $storage */ + if (is_object($storage->getUser())) { + $user = $storage->getUser()->getUID(); + $quota = OC_Util::getUserQuota($user); + if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { + return new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => $quota, 'root' => 'files')); + } + } + } + + return $storage; + }); + + OC_Hook::emit('OC_Filesystem', 'preSetup', array('user' => $user)); + \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(true); + + //check if we are using an object storage + $objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null); + if (isset($objectStore)) { + self::initObjectStoreRootFS($objectStore); + } else { + self::initLocalStorageRootFS(); + } + + if ($user != '' && !OCP\User::userExists($user)) { + \OC::$server->getEventLogger()->end('setup_fs'); + return false; + } + + //if we aren't logged in, there is no use to set up the filesystem + if ($user != "") { + + $userDir = '/' . $user . '/files'; + + //jail the user into his "home" directory + \OC\Files\Filesystem::init($user, $userDir); + + OC_Hook::emit('OC_Filesystem', 'setup', array('user' => $user, 'user_dir' => $userDir)); + } + \OC::$server->getEventLogger()->end('setup_fs'); + return true; + } + + /** + * check if a password is required for each public link + * + * @return boolean + */ + public static function isPublicLinkPasswordRequired() { + $appConfig = \OC::$server->getAppConfig(); + $enforcePassword = $appConfig->getValue('core', 'shareapi_enforce_links_password', 'no'); + return ($enforcePassword === 'yes') ? true : false; + } + + /** + * check if sharing is disabled for the current user + * @param IConfig $config + * @param IGroupManager $groupManager + * @param IUser|null $user + * @return bool + */ + public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) { + if ($config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') { + $groupsList = $config->getAppValue('core', 'shareapi_exclude_groups_list', ''); + $excludedGroups = json_decode($groupsList); + if (is_null($excludedGroups)) { + $excludedGroups = explode(',', $groupsList); + $newValue = json_encode($excludedGroups); + $config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue); + } + $usersGroups = $groupManager->getUserGroupIds($user); + if (!empty($usersGroups)) { + $remainingGroups = array_diff($usersGroups, $excludedGroups); + // if the user is only in groups which are disabled for sharing then + // sharing is also disabled for the user + if (empty($remainingGroups)) { + return true; + } + } + } + return false; + } + + /** + * check if share API enforces a default expire date + * + * @return boolean + */ + public static function isDefaultExpireDateEnforced() { + $isDefaultExpireDateEnabled = \OCP\Config::getAppValue('core', 'shareapi_default_expire_date', 'no'); + $enforceDefaultExpireDate = false; + if ($isDefaultExpireDateEnabled === 'yes') { + $value = \OCP\Config::getAppValue('core', 'shareapi_enforce_expire_date', 'no'); + $enforceDefaultExpireDate = ($value === 'yes') ? true : false; + } + + return $enforceDefaultExpireDate; + } + + /** + * Get the quota of a user + * + * @param string $user + * @return int Quota bytes + */ + public static function getUserQuota($user) { + $userQuota = \OC::$server->getUserManager()->get($user)->getQuota(); + if($userQuota === 'none') { + return \OCP\Files\FileInfo::SPACE_UNLIMITED; + }else{ + return OC_Helper::computerFileSize($userQuota); + } + } + + /** + * copies the skeleton to the users /files + * + * @param String $userId + * @param \OCP\Files\Folder $userDirectory + */ + public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) { + + $skeletonDirectory = \OCP\Config::getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton'); + + if (!empty($skeletonDirectory)) { + \OCP\Util::writeLog( + 'files_skeleton', + 'copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'), + \OCP\Util::DEBUG + ); + self::copyr($skeletonDirectory, $userDirectory); + // update the file cache + $userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE); + } + } + + /** + * copies a directory recursively by using streams + * + * @param string $source + * @param \OCP\Files\Folder $target + * @return void + */ + public static function copyr($source, \OCP\Files\Folder $target) { + $dir = opendir($source); + while (false !== ($file = readdir($dir))) { + if (!\OC\Files\Filesystem::isIgnoredDir($file)) { + if (is_dir($source . '/' . $file)) { + $child = $target->newFolder($file); + self::copyr($source . '/' . $file, $child); + } else { + $child = $target->newFile($file); + stream_copy_to_stream(fopen($source . '/' . $file,'r'), $child->fopen('w')); + } + } + } + closedir($dir); + } + + /** + * @return void + */ + public static function tearDownFS() { + \OC\Files\Filesystem::tearDown(); + self::$fsSetup = false; + self::$rootMounted = false; + } + + /** + * get the current installed version of ownCloud + * + * @return array + */ + public static function getVersion() { + OC_Util::loadVersion(); + return \OC::$server->getSession()->get('OC_Version'); + } + + /** + * get the current installed version string of ownCloud + * + * @return string + */ + public static function getVersionString() { + OC_Util::loadVersion(); + return \OC::$server->getSession()->get('OC_VersionString'); + } + + /** + * @description get the current installed edition of ownCloud. There is the community + * edition that just returns an empty string and the enterprise edition + * that returns "Enterprise". + * @return string + */ + public static function getEditionString() { + if (OC_App::isEnabled('enterprise_key')) { + return "Enterprise"; + } else { + return ""; + } + + } + + /** + * @description get the update channel of the current installed of ownCloud. + * @return string + */ + public static function getChannel() { + OC_Util::loadVersion(); + return \OC::$server->getSession()->get('OC_Channel'); + } + + /** + * @description get the build number of the current installed of ownCloud. + * @return string + */ + public static function getBuild() { + OC_Util::loadVersion(); + return \OC::$server->getSession()->get('OC_Build'); + } + + /** + * @description load the version.php into the session as cache + */ + private static function loadVersion() { + $timestamp = filemtime(OC::$SERVERROOT . '/version.php'); + if (!\OC::$server->getSession()->exists('OC_Version') or OC::$server->getSession()->get('OC_Version_Timestamp') != $timestamp) { + require OC::$SERVERROOT . '/version.php'; + $session = \OC::$server->getSession(); + /** @var $timestamp int */ + $session->set('OC_Version_Timestamp', $timestamp); + /** @var $OC_Version string */ + $session->set('OC_Version', $OC_Version); + /** @var $OC_VersionString string */ + $session->set('OC_VersionString', $OC_VersionString); + /** @var $OC_Build string */ + $session->set('OC_Build', $OC_Build); + + // Allow overriding update channel + + if (\OC::$server->getSystemConfig()->getValue('installed', false)) { + $channel = \OC::$server->getAppConfig()->getValue('core', 'OC_Channel'); + } else { + /** @var $OC_Channel string */ + $channel = $OC_Channel; + } + + if (!is_null($channel)) { + $session->set('OC_Channel', $channel); + } else { + /** @var $OC_Channel string */ + $session->set('OC_Channel', $OC_Channel); + } + } + } + + /** + * generates a path for JS/CSS files. If no application is provided it will create the path for core. + * + * @param string $application application to get the files from + * @param string $directory directory within this application (css, js, vendor, etc) + * @param string $file the file inside of the above folder + * @return string the path + */ + private static function generatePath($application, $directory, $file) { + if (is_null($file)) { + $file = $application; + $application = ""; + } + if (!empty($application)) { + return "$application/$directory/$file"; + } else { + return "$directory/$file"; + } + } + + /** + * add a javascript file + * + * @param string $application application id + * @param string|null $file filename + * @param bool $prepend prepend the Script to the beginning of the list + * @return void + */ + public static function addScript($application, $file = null, $prepend = false) { + $path = OC_Util::generatePath($application, 'js', $file); + + // core js files need separate handling + if ($application !== 'core' && $file !== null) { + self::addTranslations ( $application ); + } + self::addExternalResource($application, $prepend, $path, "script"); + } + + /** + * add a javascript file from the vendor sub folder + * + * @param string $application application id + * @param string|null $file filename + * @param bool $prepend prepend the Script to the beginning of the list + * @return void + */ + public static function addVendorScript($application, $file = null, $prepend = false) { + $path = OC_Util::generatePath($application, 'vendor', $file); + self::addExternalResource($application, $prepend, $path, "script"); + } + + /** + * add a translation JS file + * + * @param string $application application id + * @param string $languageCode language code, defaults to the current language + * @param bool $prepend prepend the Script to the beginning of the list + */ + public static function addTranslations($application, $languageCode = null, $prepend = false) { + if (is_null($languageCode)) { + $languageCode = \OC_L10N::findLanguage($application); + } + if (!empty($application)) { + $path = "$application/l10n/$languageCode"; + } else { + $path = "l10n/$languageCode"; + } + self::addExternalResource($application, $prepend, $path, "script"); + } + + /** + * add a css file + * + * @param string $application application id + * @param string|null $file filename + * @param bool $prepend prepend the Style to the beginning of the list + * @return void + */ + public static function addStyle($application, $file = null, $prepend = false) { + $path = OC_Util::generatePath($application, 'css', $file); + self::addExternalResource($application, $prepend, $path, "style"); + } + + /** + * add a css file from the vendor sub folder + * + * @param string $application application id + * @param string|null $file filename + * @param bool $prepend prepend the Style to the beginning of the list + * @return void + */ + public static function addVendorStyle($application, $file = null, $prepend = false) { + $path = OC_Util::generatePath($application, 'vendor', $file); + self::addExternalResource($application, $prepend, $path, "style"); + } + + /** + * add an external resource css/js file + * + * @param string $application application id + * @param bool $prepend prepend the file to the beginning of the list + * @param string $path + * @param string $type (script or style) + * @return void + */ + private static function addExternalResource($application, $prepend, $path, $type = "script") { + + if ($type === "style") { + if (!in_array($path, self::$styles)) { + if ($prepend === true) { + array_unshift ( self::$styles, $path ); + } else { + self::$styles[] = $path; + } + } + } elseif ($type === "script") { + if (!in_array($path, self::$scripts)) { + if ($prepend === true) { + array_unshift ( self::$scripts, $path ); + } else { + self::$scripts [] = $path; + } + } + } + } + + /** + * Add a custom element to the header + * If $text is null then the element will be written as empty element. + * So use "" to get a closing tag. + * @param string $tag tag name of the element + * @param array $attributes array of attributes for the element + * @param string $text the text content for the element + */ + public static function addHeader($tag, $attributes, $text=null) { + self::$headers[] = array( + 'tag' => $tag, + 'attributes' => $attributes, + 'text' => $text + ); + } + + /** + * formats a timestamp in the "right" way + * + * @param int $timestamp + * @param bool $dateOnly option to omit time from the result + * @param DateTimeZone|string $timeZone where the given timestamp shall be converted to + * @return string timestamp + * + * @deprecated Use \OC::$server->query('DateTimeFormatter') instead + */ + public static function formatDate($timestamp, $dateOnly = false, $timeZone = null) { + if ($timeZone !== null && !$timeZone instanceof \DateTimeZone) { + $timeZone = new \DateTimeZone($timeZone); + } + + /** @var \OC\DateTimeFormatter $formatter */ + $formatter = \OC::$server->query('DateTimeFormatter'); + if ($dateOnly) { + return $formatter->formatDate($timestamp, 'long', $timeZone); + } + return $formatter->formatDateTime($timestamp, 'long', 'long', $timeZone); + } + + /** + * check if the current server configuration is suitable for ownCloud + * + * @param \OCP\IConfig $config + * @return array arrays with error messages and hints + */ + public static function checkServer(\OCP\IConfig $config) { + $l = \OC::$server->getL10N('lib'); + $errors = array(); + $CONFIG_DATADIRECTORY = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data'); + + if (!self::needUpgrade($config) && $config->getSystemValue('installed', false)) { + // this check needs to be done every time + $errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY); + } + + // Assume that if checkServer() succeeded before in this session, then all is fine. + if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) { + return $errors; + } + + $webServerRestart = false; + $setup = new \OC\Setup($config, \OC::$server->getIniWrapper(), \OC::$server->getL10N('lib'), + new \OC_Defaults(), \OC::$server->getLogger(), \OC::$server->getSecureRandom()); + + $urlGenerator = \OC::$server->getURLGenerator(); + + $availableDatabases = $setup->getSupportedDatabases(); + if (empty($availableDatabases)) { + $errors[] = array( + 'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'), + 'hint' => '' //TODO: sane hint + ); + $webServerRestart = true; + } + + // Check if server running on Windows platform + if(OC_Util::runningOnWindows()) { + $errors[] = [ + 'error' => $l->t('Microsoft Windows Platform is not supported'), + 'hint' => $l->t('Running ownCloud Server on the Microsoft Windows platform is not supported. We suggest you ' . + 'use a Linux server in a virtual machine if you have no option for migrating the server itself. ' . + 'Find Linux packages as well as easy to deploy virtual machine images on %s. ' . + 'For migrating existing installations to Linux you can find some tips and a migration script ' . + 'in our documentation.', + ['https://owncloud.org/install/', 'owncloud.org/install/', 'https://owncloud.org/?p=8045']) + ]; + } + + // Check if config folder is writable. + if(!OC_Helper::isReadOnlyConfigEnabled()) { + if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) { + $errors[] = array( + 'error' => $l->t('Cannot write into "config" directory'), + 'hint' => $l->t('This can usually be fixed by ' + . '%sgiving the webserver write access to the config directory%s.', + array('', '')) + ); + } + } + + // Check if there is a writable install folder. + if ($config->getSystemValue('appstoreenabled', true)) { + if (OC_App::getInstallPath() === null + || !is_writable(OC_App::getInstallPath()) + || !is_readable(OC_App::getInstallPath()) + ) { + $errors[] = array( + 'error' => $l->t('Cannot write into "apps" directory'), + 'hint' => $l->t('This can usually be fixed by ' + . '%sgiving the webserver write access to the apps directory%s' + . ' or disabling the appstore in the config file.', + array('', '')) + ); + } + } + // Create root dir. + if ($config->getSystemValue('installed', false)) { + if (!is_dir($CONFIG_DATADIRECTORY)) { + $success = @mkdir($CONFIG_DATADIRECTORY); + if ($success) { + $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); + } else { + $errors[] = array( + 'error' => $l->t('Cannot create "data" directory (%s)', array($CONFIG_DATADIRECTORY)), + 'hint' => $l->t('This can usually be fixed by ' + . 'giving the webserver write access to the root directory.', + array($urlGenerator->linkToDocs('admin-dir_permissions'))) + ); + } + } else if (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) { + //common hint for all file permissions error messages + $permissionsHint = $l->t('Permissions can usually be fixed by ' + . '%sgiving the webserver write access to the root directory%s.', + array('', '')); + $errors[] = array( + 'error' => 'Data directory (' . $CONFIG_DATADIRECTORY . ') not writable by ownCloud', + 'hint' => $permissionsHint + ); + } else { + $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); + } + } + + if (!OC_Util::isSetLocaleWorking()) { + $errors[] = array( + 'error' => $l->t('Setting locale to %s failed', + array('en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/' + . 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8')), + 'hint' => $l->t('Please install one of these locales on your system and restart your webserver.') + ); + } + + // Contains the dependencies that should be checked against + // classes = class_exists + // functions = function_exists + // defined = defined + // ini = ini_get + // If the dependency is not found the missing module name is shown to the EndUser + // When adding new checks always verify that they pass on Travis as well + // for ini settings, see https://github.com/owncloud/administration/blob/master/travis-ci/custom.ini + $dependencies = array( + 'classes' => array( + 'ZipArchive' => 'zip', + 'DOMDocument' => 'dom', + 'XMLWriter' => 'XMLWriter', + 'XMLReader' => 'XMLReader', + ), + 'functions' => [ + 'xml_parser_create' => 'libxml', + 'mb_detect_encoding' => 'mb multibyte', + 'ctype_digit' => 'ctype', + 'json_encode' => 'JSON', + 'gd_info' => 'GD', + 'gzencode' => 'zlib', + 'iconv' => 'iconv', + 'simplexml_load_string' => 'SimpleXML', + 'hash' => 'HASH Message Digest Framework', + 'curl_init' => 'cURL', + ], + 'defined' => array( + 'PDO::ATTR_DRIVER_NAME' => 'PDO' + ), + 'ini' => [ + 'default_charset' => 'UTF-8', + ], + ); + $missingDependencies = array(); + $invalidIniSettings = []; + $moduleHint = $l->t('Please ask your server administrator to install the module.'); + + /** + * FIXME: The dependency check does not work properly on HHVM on the moment + * and prevents installation. Once HHVM is more compatible with our + * approach to check for these values we should re-enable those + * checks. + */ + $iniWrapper = \OC::$server->getIniWrapper(); + if (!self::runningOnHhvm()) { + foreach ($dependencies['classes'] as $class => $module) { + if (!class_exists($class)) { + $missingDependencies[] = $module; + } + } + foreach ($dependencies['functions'] as $function => $module) { + if (!function_exists($function)) { + $missingDependencies[] = $module; + } + } + foreach ($dependencies['defined'] as $defined => $module) { + if (!defined($defined)) { + $missingDependencies[] = $module; + } + } + foreach ($dependencies['ini'] as $setting => $expected) { + if (is_bool($expected)) { + if ($iniWrapper->getBool($setting) !== $expected) { + $invalidIniSettings[] = [$setting, $expected]; + } + } + if (is_int($expected)) { + if ($iniWrapper->getNumeric($setting) !== $expected) { + $invalidIniSettings[] = [$setting, $expected]; + } + } + if (is_string($expected)) { + if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) { + $invalidIniSettings[] = [$setting, $expected]; + } + } + } + } + + foreach($missingDependencies as $missingDependency) { + $errors[] = array( + 'error' => $l->t('PHP module %s not installed.', array($missingDependency)), + 'hint' => $moduleHint + ); + $webServerRestart = true; + } + foreach($invalidIniSettings as $setting) { + if(is_bool($setting[1])) { + $setting[1] = ($setting[1]) ? 'on' : 'off'; + } + $errors[] = [ + 'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]), + 'hint' => $l->t('Adjusting this setting in php.ini will make ownCloud run again') + ]; + $webServerRestart = true; + } + + /** + * The mbstring.func_overload check can only be performed if the mbstring + * module is installed as it will return null if the checking setting is + * not available and thus a check on the boolean value fails. + * + * TODO: Should probably be implemented in the above generic dependency + * check somehow in the long-term. + */ + if($iniWrapper->getBool('mbstring.func_overload') !== null && + $iniWrapper->getBool('mbstring.func_overload') === true) { + $errors[] = array( + 'error' => $l->t('mbstring.func_overload is set to "%s" instead of the expected value "0"', [$iniWrapper->getString('mbstring.func_overload')]), + 'hint' => $l->t('To fix this issue set mbstring.func_overload to 0 in your php.ini') + ); + } + + if(function_exists('xml_parser_create') && + version_compare('2.7.0', LIBXML_DOTTED_VERSION) === 1) { + $errors[] = array( + 'error' => $l->t('libxml2 2.7.0 is at least required. Currently %s is installed.', [LIBXML_DOTTED_VERSION]), + 'hint' => $l->t('To fix this issue update your libxml2 version and restart your web server.') + ); + } + + if (!self::isAnnotationsWorking()) { + $errors[] = array( + 'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'), + 'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.') + ); + } + + if (!\OC::$CLI && $webServerRestart) { + $errors[] = array( + 'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'), + 'hint' => $l->t('Please ask your server administrator to restart the web server.') + ); + } + + $errors = array_merge($errors, self::checkDatabaseVersion()); + + // Cache the result of this function + \OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0); + + return $errors; + } + + /** + * Check the database version + * + * @return array errors array + */ + public static function checkDatabaseVersion() { + $l = \OC::$server->getL10N('lib'); + $errors = array(); + $dbType = \OC::$server->getSystemConfig()->getValue('dbtype', 'sqlite'); + if ($dbType === 'pgsql') { + // check PostgreSQL version + try { + $result = \OC_DB::executeAudited('SHOW SERVER_VERSION'); + $data = $result->fetchRow(); + if (isset($data['server_version'])) { + $version = $data['server_version']; + if (version_compare($version, '9.0.0', '<')) { + $errors[] = array( + 'error' => $l->t('PostgreSQL >= 9 required'), + 'hint' => $l->t('Please upgrade your database version') + ); + } + } + } catch (\Doctrine\DBAL\DBALException $e) { + $logger = \OC::$server->getLogger(); + $logger->warning('Error occurred while checking PostgreSQL version, assuming >= 9'); + $logger->logException($e); + } + } + return $errors; + } + + /** + * Check for correct file permissions of data directory + * + * @param string $dataDirectory + * @return array arrays with error messages and hints + */ + public static function checkDataDirectoryPermissions($dataDirectory) { + $l = \OC::$server->getL10N('lib'); + $errors = array(); + if (self::runningOnWindows()) { + //TODO: permissions checks for windows hosts + } else { + $permissionsModHint = $l->t('Please change the permissions to 0770 so that the directory' + . ' cannot be listed by other users.'); + $perms = substr(decoct(@fileperms($dataDirectory)), -3); + if (substr($perms, -1) != '0') { + chmod($dataDirectory, 0770); + clearstatcache(); + $perms = substr(decoct(@fileperms($dataDirectory)), -3); + if (substr($perms, 2, 1) != '0') { + $errors[] = array( + 'error' => $l->t('Data directory (%s) is readable by other users', array($dataDirectory)), + 'hint' => $permissionsModHint + ); + } + } + } + return $errors; + } + + /** + * Check that the data directory exists and is valid by + * checking the existence of the ".ocdata" file. + * + * @param string $dataDirectory data directory path + * @return array errors found + */ + public static function checkDataDirectoryValidity($dataDirectory) { + $l = \OC::$server->getL10N('lib'); + $errors = []; + if (!self::runningOnWindows() && $dataDirectory[0] !== '/') { + $errors[] = [ + 'error' => $l->t('Data directory (%s) must be an absolute path', [$dataDirectory]), + 'hint' => $l->t('Check the value of "datadirectory" in your configuration') + ]; + } + if (!file_exists($dataDirectory . '/.ocdata')) { + $errors[] = [ + 'error' => $l->t('Data directory (%s) is invalid', [$dataDirectory]), + 'hint' => $l->t('Please check that the data directory contains a file' . + ' ".ocdata" in its root.') + ]; + } + return $errors; + } + + /** + * Check if the user is logged in, redirects to home if not. With + * redirect URL parameter to the request URI. + * + * @return void + */ + public static function checkLoggedIn() { + // Check if we are a user + if (!OC_User::isLoggedIn()) { + header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute( + 'core.login.showLoginForm', + [ + 'redirect_url' => \OC::$server->getRequest()->getRequestUri() + ] + ) + ); + exit(); + } + } + + /** + * Check if the user is a admin, redirects to home if not + * + * @return void + */ + public static function checkAdminUser() { + OC_Util::checkLoggedIn(); + if (!OC_User::isAdminUser(OC_User::getUser())) { + header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); + exit(); + } + } + + /** + * Check if it is allowed to remember login. + * + * @note Every app can set 'rememberlogin' to 'false' to disable the remember login feature + * + * @return bool + */ + public static function rememberLoginAllowed() { + + $apps = OC_App::getEnabledApps(); + + foreach ($apps as $app) { + $appInfo = OC_App::getAppInfo($app); + if (isset($appInfo['rememberlogin']) && $appInfo['rememberlogin'] === 'false') { + return false; + } + + } + return true; + } + + /** + * Check if the user is a subadmin, redirects to home if not + * + * @return null|boolean $groups where the current user is subadmin + */ + public static function checkSubAdminUser() { + OC_Util::checkLoggedIn(); + $userObject = \OC::$server->getUserSession()->getUser(); + $isSubAdmin = false; + if($userObject !== null) { + $isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject); + } + + if (!$isSubAdmin) { + header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); + exit(); + } + return true; + } + + /** + * Returns the URL of the default page + * based on the system configuration and + * the apps visible for the current user + * + * @return string URL + */ + public static function getDefaultPageUrl() { + $urlGenerator = \OC::$server->getURLGenerator(); + // Deny the redirect if the URL contains a @ + // This prevents unvalidated redirects like ?redirect_url=:user@domain.com + if (isset($_REQUEST['redirect_url']) && strpos($_REQUEST['redirect_url'], '@') === false) { + $location = $urlGenerator->getAbsoluteURL(urldecode($_REQUEST['redirect_url'])); + } else { + $defaultPage = \OC::$server->getAppConfig()->getValue('core', 'defaultpage'); + if ($defaultPage) { + $location = $urlGenerator->getAbsoluteURL($defaultPage); + } else { + $appId = 'files'; + $defaultApps = explode(',', \OCP\Config::getSystemValue('defaultapp', 'files')); + // find the first app that is enabled for the current user + foreach ($defaultApps as $defaultApp) { + $defaultApp = OC_App::cleanAppId(strip_tags($defaultApp)); + if (static::getAppManager()->isEnabledForUser($defaultApp)) { + $appId = $defaultApp; + break; + } + } + + if(getenv('front_controller_active') === 'true') { + $location = $urlGenerator->getAbsoluteURL('/apps/' . $appId . '/'); + } else { + $location = $urlGenerator->getAbsoluteURL('/index.php/apps/' . $appId . '/'); + } + } + } + return $location; + } + + /** + * Redirect to the user default page + * + * @return void + */ + public static function redirectToDefaultPage() { + $location = self::getDefaultPageUrl(); + header('Location: ' . $location); + exit(); + } + + /** + * get an id unique for this instance + * + * @return string + */ + public static function getInstanceId() { + $id = \OC::$server->getSystemConfig()->getValue('instanceid', null); + if (is_null($id)) { + // We need to guarantee at least one letter in instanceid so it can be used as the session_name + $id = 'oc' . \OC::$server->getSecureRandom()->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_DIGITS); + \OC::$server->getSystemConfig()->setValue('instanceid', $id); + } + return $id; + } + + /** + * Public function to sanitize HTML + * + * This function is used to sanitize HTML and should be applied on any + * string or array of strings before displaying it on a web page. + * + * @param string|array $value + * @return string|array an array of sanitized strings or a single sanitized string, depends on the input parameter. + */ + public static function sanitizeHTML($value) { + if (is_array($value)) { + $value = array_map(function($value) { + return self::sanitizeHTML($value); + }, $value); + } else { + // Specify encoding for PHP<5.4 + $value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + } + return $value; + } + + /** + * Public function to encode url parameters + * + * This function is used to encode path to file before output. + * Encoding is done according to RFC 3986 with one exception: + * Character '/' is preserved as is. + * + * @param string $component part of URI to encode + * @return string + */ + public static function encodePath($component) { + $encoded = rawurlencode($component); + $encoded = str_replace('%2F', '/', $encoded); + return $encoded; + } + + /** + * Check if the .htaccess file is working + * @param \OCP\IConfig $config + * @return bool + * @throws Exception + * @throws \OC\HintException If the test file can't get written. + */ + public function isHtaccessWorking(\OCP\IConfig $config) { + + if (\OC::$CLI || !$config->getSystemValue('check_for_working_htaccess', true)) { + return true; + } + + // php dev server does not support htaccess + if (php_sapi_name() === 'cli-server') { + return false; + } + + // testdata + $fileName = '/htaccesstest.txt'; + $testContent = 'testcontent'; + + // creating a test file + $testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName; + + if (file_exists($testFile)) {// already running this test, possible recursive call + return false; + } + + $fp = @fopen($testFile, 'w'); + if (!$fp) { + throw new OC\HintException('Can\'t create test file to check for working .htaccess file.', + 'Make sure it is possible for the webserver to write to ' . $testFile); + } + fwrite($fp, $testContent); + fclose($fp); + + // accessing the file via http + $url = \OC::$server->getURLGenerator()->getAbsoluteURL(OC::$WEBROOT . '/data' . $fileName); + try { + $content = \OC::$server->getHTTPClientService()->newClient()->get($url)->getBody(); + } catch (\Exception $e) { + $content = false; + } + + // cleanup + @unlink($testFile); + + /* + * If the content is not equal to test content our .htaccess + * is working as required + */ + return $content !== $testContent; + } + + /** + * Check if the setlocal call does not work. This can happen if the right + * local packages are not available on the server. + * + * @return bool + */ + public static function isSetLocaleWorking() { + // setlocale test is pointless on Windows + if (OC_Util::runningOnWindows()) { + return true; + } + + \Patchwork\Utf8\Bootup::initLocale(); + if ('' === basename('§')) { + return false; + } + return true; + } + + /** + * Check if it's possible to get the inline annotations + * + * @return bool + */ + public static function isAnnotationsWorking() { + $reflection = new \ReflectionMethod(__METHOD__); + $docs = $reflection->getDocComment(); + + return (is_string($docs) && strlen($docs) > 50); + } + + /** + * Check if the PHP module fileinfo is loaded. + * + * @return bool + */ + public static function fileInfoLoaded() { + return function_exists('finfo_open'); + } + + /** + * clear all levels of output buffering + * + * @return void + */ + public static function obEnd() { + while (ob_get_level()) { + ob_end_clean(); + } + } + + /** + * Checks whether the server is running on Windows + * + * @return bool true if running on Windows, false otherwise + */ + public static function runningOnWindows() { + return (substr(PHP_OS, 0, 3) === "WIN"); + } + + /** + * Checks whether the server is running on Mac OS X + * + * @return bool true if running on Mac OS X, false otherwise + */ + public static function runningOnMac() { + return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN'); + } + + /** + * Checks whether server is running on HHVM + * + * @return bool True if running on HHVM, false otherwise + */ + public static function runningOnHhvm() { + return defined('HHVM_VERSION'); + } + + /** + * Handles the case that there may not be a theme, then check if a "default" + * theme exists and take that one + * + * @return string the theme + */ + public static function getTheme() { + $theme = \OC::$server->getSystemConfig()->getValue("theme", ''); + + if ($theme === '') { + if (is_dir(OC::$SERVERROOT . '/themes/default')) { + $theme = 'default'; + } + } + + return $theme; + } + + /** + * Clear a single file from the opcode cache + * This is useful for writing to the config file + * in case the opcode cache does not re-validate files + * Returns true if successful, false if unsuccessful: + * caller should fall back on clearing the entire cache + * with clearOpcodeCache() if unsuccessful + * + * @param string $path the path of the file to clear from the cache + * @return bool true if underlying function returns true, otherwise false + */ + public static function deleteFromOpcodeCache($path) { + $ret = false; + if ($path) { + // APC >= 3.1.1 + if (function_exists('apc_delete_file')) { + $ret = @apc_delete_file($path); + } + // Zend OpCache >= 7.0.0, PHP >= 5.5.0 + if (function_exists('opcache_invalidate')) { + $ret = opcache_invalidate($path); + } + } + return $ret; + } + + /** + * Clear the opcode cache if one exists + * This is necessary for writing to the config file + * in case the opcode cache does not re-validate files + * + * @return void + */ + public static function clearOpcodeCache() { + // APC + if (function_exists('apc_clear_cache')) { + apc_clear_cache(); + } + // Zend Opcache + if (function_exists('accelerator_reset')) { + accelerator_reset(); + } + // XCache + if (function_exists('xcache_clear_cache')) { + if (\OC::$server->getIniWrapper()->getBool('xcache.admin.enable_auth')) { + \OCP\Util::writeLog('core', 'XCache opcode cache will not be cleared because "xcache.admin.enable_auth" is enabled.', \OCP\Util::WARN); + } else { + @xcache_clear_cache(XC_TYPE_PHP, 0); + } + } + // Opcache (PHP >= 5.5) + if (function_exists('opcache_reset')) { + opcache_reset(); + } + } + + /** + * Normalize a unicode string + * + * @param string $value a not normalized string + * @return bool|string + */ + public static function normalizeUnicode($value) { + if(Normalizer::isNormalized($value)) { + return $value; + } + + $normalizedValue = Normalizer::normalize($value); + if ($normalizedValue === null || $normalizedValue === false) { + \OC::$server->getLogger()->warning('normalizing failed for "' . $value . '"', ['app' => 'core']); + return $value; + } + + return $normalizedValue; + } + + /** + * @param boolean|string $file + * @return string + */ + public static function basename($file) { + $file = rtrim($file, '/'); + $t = explode('/', $file); + return array_pop($t); + } + + /** + * A human readable string is generated based on version, channel and build number + * + * @return string + */ + public static function getHumanVersion() { + $version = OC_Util::getVersionString() . ' (' . OC_Util::getChannel() . ')'; + $build = OC_Util::getBuild(); + if (!empty($build) and OC_Util::getChannel() === 'daily') { + $version .= ' Build:' . $build; + } + return $version; + } + + /** + * Returns whether the given file name is valid + * + * @param string $file file name to check + * @return bool true if the file name is valid, false otherwise + * @deprecated use \OC\Files\View::verifyPath() + */ + public static function isValidFileName($file) { + $trimmed = trim($file); + if ($trimmed === '') { + return false; + } + if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) { + return false; + } + foreach (str_split($trimmed) as $char) { + if (strpos(\OCP\Constants::FILENAME_INVALID_CHARS, $char) !== false) { + return false; + } + } + return true; + } + + /** + * Check whether the instance needs to perform an upgrade, + * either when the core version is higher or any app requires + * an upgrade. + * + * @param \OCP\IConfig $config + * @return bool whether the core or any app needs an upgrade + * @throws \OC\HintException When the upgrade from the given version is not allowed + */ + public static function needUpgrade(\OCP\IConfig $config) { + if ($config->getSystemValue('installed', false)) { + $installedVersion = $config->getSystemValue('version', '0.0.0'); + $currentVersion = implode('.', \OCP\Util::getVersion()); + $versionDiff = version_compare($currentVersion, $installedVersion); + if ($versionDiff > 0) { + return true; + } else if ($config->getSystemValue('debug', false) && $versionDiff < 0) { + // downgrade with debug + $installedMajor = explode('.', $installedVersion); + $installedMajor = $installedMajor[0] . '.' . $installedMajor[1]; + $currentMajor = explode('.', $currentVersion); + $currentMajor = $currentMajor[0] . '.' . $currentMajor[1]; + if ($installedMajor === $currentMajor) { + // Same major, allow downgrade for developers + return true; + } else { + // downgrade attempt, throw exception + throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')'); + } + } else if ($versionDiff < 0) { + // downgrade attempt, throw exception + throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')'); + } + + // also check for upgrades for apps (independently from the user) + $apps = \OC_App::getEnabledApps(false, true); + $shouldUpgrade = false; + foreach ($apps as $app) { + if (\OC_App::shouldUpgrade($app)) { + $shouldUpgrade = true; + break; + } + } + return $shouldUpgrade; + } else { + return false; + } + } + +} diff --git a/lib/private/naturalsort_defaultcollator.php b/lib/private/naturalsort_defaultcollator.php deleted file mode 100644 index 7b8400fa8e1..00000000000 --- a/lib/private/naturalsort_defaultcollator.php +++ /dev/null @@ -1,37 +0,0 @@ - - * @author Joas Schilling - * @author Morris Jobke - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC; - -class NaturalSort_DefaultCollator { - public function compare($a, $b) { - $result = strcasecmp($a, $b); - if ($result === 0) { - if ($a === $b) { - return 0; - } - return ($a > $b) ? -1 : 1; - } - return ($result < 0) ? -1 : 1; - } -} diff --git a/lib/private/ocs.php b/lib/private/ocs.php deleted file mode 100644 index f48879a98b1..00000000000 --- a/lib/private/ocs.php +++ /dev/null @@ -1,42 +0,0 @@ - - * @author Frank Karlitschek - * @author Lukas Reschke - * @author Morris Jobke - * @author Robin Appelman - * @author Thomas Müller - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -use OCP\API; - -/** - * Class to handle open collaboration services API requests - */ -class OC_OCS { - /** - * Called when a not existing OCS endpoint has been called - */ - public static function notFound() { - $format = \OC::$server->getRequest()->getParam('format', 'xml'); - $txt='Invalid query, please check the syntax. API specifications are here:' - .' http://www.freedesktop.org/wiki/Specifications/open-collaboration-services. DEBUG OUTPUT:'."\n"; - OC_API::respond(new OC_OCS_Result(null, API::RESPOND_UNKNOWN_ERROR, $txt), $format); - } - -} diff --git a/lib/private/response.php b/lib/private/response.php deleted file mode 100644 index 51e0ff75e6a..00000000000 --- a/lib/private/response.php +++ /dev/null @@ -1,268 +0,0 @@ - - * @author Bart Visscher - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Morris Jobke - * @author Robin McCorkell - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -class OC_Response { - const STATUS_FOUND = 304; - const STATUS_NOT_MODIFIED = 304; - const STATUS_TEMPORARY_REDIRECT = 307; - const STATUS_BAD_REQUEST = 400; - const STATUS_NOT_FOUND = 404; - const STATUS_INTERNAL_SERVER_ERROR = 500; - const STATUS_SERVICE_UNAVAILABLE = 503; - - /** - * Enable response caching by sending correct HTTP headers - * @param integer $cache_time time to cache the response - * >0 cache time in seconds - * 0 and <0 enable default browser caching - * null cache indefinitely - */ - static public function enableCaching($cache_time = null) { - if (is_numeric($cache_time)) { - header('Pragma: public');// enable caching in IE - if ($cache_time > 0) { - self::setExpiresHeader('PT'.$cache_time.'S'); - header('Cache-Control: max-age='.$cache_time.', must-revalidate'); - } - else { - self::setExpiresHeader(0); - header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); - } - } - else { - header('Cache-Control: cache'); - header('Pragma: cache'); - } - - } - - /** - * disable browser caching - * @see enableCaching with cache_time = 0 - */ - static public function disableCaching() { - self::enableCaching(0); - } - - /** - * Set response status - * @param int $status a HTTP status code, see also the STATUS constants - */ - static public function setStatus($status) { - $protocol = \OC::$server->getRequest()->getHttpProtocol(); - switch($status) { - case self::STATUS_NOT_MODIFIED: - $status = $status . ' Not Modified'; - break; - case self::STATUS_TEMPORARY_REDIRECT: - if ($protocol == 'HTTP/1.1') { - $status = $status . ' Temporary Redirect'; - break; - } else { - $status = self::STATUS_FOUND; - // fallthrough - } - case self::STATUS_FOUND; - $status = $status . ' Found'; - break; - case self::STATUS_NOT_FOUND; - $status = $status . ' Not Found'; - break; - case self::STATUS_INTERNAL_SERVER_ERROR; - $status = $status . ' Internal Server Error'; - break; - case self::STATUS_SERVICE_UNAVAILABLE; - $status = $status . ' Service Unavailable'; - break; - } - header($protocol.' '.$status); - } - - /** - * Send redirect response - * @param string $location to redirect to - */ - static public function redirect($location) { - self::setStatus(self::STATUS_TEMPORARY_REDIRECT); - header('Location: '.$location); - } - - /** - * Set response expire time - * @param string|DateTime $expires date-time when the response expires - * string for DateInterval from now - * DateTime object when to expire response - */ - static public function setExpiresHeader($expires) { - if (is_string($expires) && $expires[0] == 'P') { - $interval = $expires; - $expires = new DateTime('now'); - $expires->add(new DateInterval($interval)); - } - if ($expires instanceof DateTime) { - $expires->setTimezone(new DateTimeZone('GMT')); - $expires = $expires->format(DateTime::RFC2822); - } - header('Expires: '.$expires); - } - - /** - * Checks and set ETag header, when the request matches sends a - * 'not modified' response - * @param string $etag token to use for modification check - */ - static public function setETagHeader($etag) { - if (empty($etag)) { - return; - } - $etag = '"'.$etag.'"'; - if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && - trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) { - self::setStatus(self::STATUS_NOT_MODIFIED); - exit; - } - header('ETag: '.$etag); - } - - /** - * Checks and set Last-Modified header, when the request matches sends a - * 'not modified' response - * @param int|DateTime|string $lastModified time when the response was last modified - */ - static public function setLastModifiedHeader($lastModified) { - if (empty($lastModified)) { - return; - } - if (is_int($lastModified)) { - $lastModified = gmdate(DateTime::RFC2822, $lastModified); - } - if ($lastModified instanceof DateTime) { - $lastModified = $lastModified->format(DateTime::RFC2822); - } - if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && - trim($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModified) { - self::setStatus(self::STATUS_NOT_MODIFIED); - exit; - } - header('Last-Modified: '.$lastModified); - } - - /** - * Sets the content disposition header (with possible workarounds) - * @param string $filename file name - * @param string $type disposition type, either 'attachment' or 'inline' - */ - static public function setContentDispositionHeader( $filename, $type = 'attachment' ) { - if (\OC::$server->getRequest()->isUserAgent( - [ - \OC\AppFramework\Http\Request::USER_AGENT_IE, - \OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME, - \OC\AppFramework\Http\Request::USER_AGENT_FREEBOX, - ])) { - header( 'Content-Disposition: ' . rawurlencode($type) . '; filename="' . rawurlencode( $filename ) . '"' ); - } else { - header( 'Content-Disposition: ' . rawurlencode($type) . '; filename*=UTF-8\'\'' . rawurlencode( $filename ) - . '; filename="' . rawurlencode( $filename ) . '"' ); - } - } - - /** - * Sets the content length header (with possible workarounds) - * @param string|int|float $length Length to be sent - */ - static public function setContentLengthHeader($length) { - if (PHP_INT_SIZE === 4) { - if ($length > PHP_INT_MAX && stripos(PHP_SAPI, 'apache') === 0) { - // Apache PHP SAPI casts Content-Length headers to PHP integers. - // This enforces a limit of PHP_INT_MAX (2147483647 on 32-bit - // platforms). So, if the length is greater than PHP_INT_MAX, - // we just do not send a Content-Length header to prevent - // bodies from being received incompletely. - return; - } - // Convert signed integer or float to unsigned base-10 string. - $lfh = new \OC\LargeFileHelper; - $length = $lfh->formatUnsignedInteger($length); - } - header('Content-Length: '.$length); - } - - /** - * Send file as response, checking and setting caching headers - * @param string $filepath of file to send - * @deprecated 8.1.0 - Use \OCP\AppFramework\Http\StreamResponse or another AppFramework controller instead - */ - static public function sendFile($filepath) { - $fp = fopen($filepath, 'rb'); - if ($fp) { - self::setLastModifiedHeader(filemtime($filepath)); - self::setETagHeader(md5_file($filepath)); - - self::setContentLengthHeader(filesize($filepath)); - fpassthru($fp); - } - else { - self::setStatus(self::STATUS_NOT_FOUND); - } - } - - /** - * This function adds some security related headers to all requests served via base.php - * The implementation of this function has to happen here to ensure that all third-party - * components (e.g. SabreDAV) also benefit from this headers. - */ - public static function addSecurityHeaders() { - /** - * FIXME: Content Security Policy for legacy ownCloud components. This - * can be removed once \OCP\AppFramework\Http\Response from the AppFramework - * is used everywhere. - * @see \OCP\AppFramework\Http\Response::getHeaders - */ - $policy = 'default-src \'self\'; ' - . 'script-src \'self\' \'unsafe-eval\'; ' - . 'style-src \'self\' \'unsafe-inline\'; ' - . 'frame-src *; ' - . 'img-src * data: blob:; ' - . 'font-src \'self\' data:; ' - . 'media-src *; ' - . 'connect-src *'; - header('Content-Security-Policy:' . $policy); - - // Send fallback headers for installations that don't have the possibility to send - // custom headers on the webserver side - if(getenv('modHeadersAvailable') !== 'true') { - header('X-XSS-Protection: 1; mode=block'); // Enforce browser based XSS filters - header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE - header('X-Frame-Options: Sameorigin'); // Disallow iFraming from other domains - header('X-Robots-Tag: none'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag - header('X-Download-Options: noopen'); // https://msdn.microsoft.com/en-us/library/jj542450(v=vs.85).aspx - header('X-Permitted-Cross-Domain-Policies: none'); // https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html - } - } - -} diff --git a/lib/private/template.php b/lib/private/template.php deleted file mode 100644 index 73725529702..00000000000 --- a/lib/private/template.php +++ /dev/null @@ -1,433 +0,0 @@ - - * @author Bart Visscher - * @author Björn Schießle - * @author Brice Maron - * @author Frank Karlitschek - * @author Hendrik Leppelsack - * @author Individual IT Services - * @author Jakob Sack - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Morris Jobke - * @author Raghu Nayyar - * @author Robin Appelman - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -use OC\TemplateLayout; - -require_once __DIR__.'/template/functions.php'; - -/** - * This class provides the templates for ownCloud. - */ -class OC_Template extends \OC\Template\Base { - - /** @var string */ - private $renderAs; // Create a full page? - - /** @var string */ - private $path; // The path to the template - - /** @var array */ - private $headers = array(); //custom headers - - /** @var string */ - protected $app; // app id - - protected static $initTemplateEngineFirstRun = true; - - /** - * Constructor - * - * @param string $app app providing the template - * @param string $name of the template file (without suffix) - * @param string $renderAs If $renderAs is set, OC_Template will try to - * produce a full page in the according layout. For - * now, $renderAs can be set to "guest", "user" or - * "admin". - * @param bool $registerCall = true - */ - public function __construct( $app, $name, $renderAs = "", $registerCall = true ) { - // Read the selected theme from the config file - self::initTemplateEngine($renderAs); - - $theme = OC_Util::getTheme(); - - $requestToken = (OC::$server->getSession() && $registerCall) ? \OCP\Util::callRegister() : ''; - - $parts = explode('/', $app); // fix translation when app is something like core/lostpassword - $l10n = \OC::$server->getL10N($parts[0]); - $themeDefaults = new OC_Defaults(); - - list($path, $template) = $this->findTemplate($theme, $app, $name); - - // Set the private data - $this->renderAs = $renderAs; - $this->path = $path; - $this->app = $app; - - parent::__construct($template, $requestToken, $l10n, $themeDefaults); - } - - /** - * @param string $renderAs - */ - public static function initTemplateEngine($renderAs) { - if (self::$initTemplateEngineFirstRun){ - - //apps that started before the template initialization can load their own scripts/styles - //so to make sure this scripts/styles here are loaded first we use OC_Util::addScript() with $prepend=true - //meaning the last script/style in this list will be loaded first - if (\OC::$server->getSystemConfig()->getValue ('installed', false) && $renderAs !== 'error' && !\OCP\Util::needUpgrade()) { - if (\OC::$server->getConfig ()->getAppValue ( 'core', 'backgroundjobs_mode', 'ajax' ) == 'ajax') { - OC_Util::addScript ( 'backgroundjobs', null, true ); - } - } - - OC_Util::addStyle("tooltip",null,true); - OC_Util::addStyle('jquery-ui-fixes',null,true); - OC_Util::addVendorStyle('jquery-ui/themes/base/jquery-ui',null,true); - OC_Util::addStyle("mobile",null,true); - OC_Util::addStyle("multiselect",null,true); - OC_Util::addStyle("fixes",null,true); - OC_Util::addStyle("global",null,true); - OC_Util::addStyle("apps",null,true); - OC_Util::addStyle("fonts",null,true); - OC_Util::addStyle("icons",null,true); - OC_Util::addStyle("header",null,true); - OC_Util::addStyle("inputs",null,true); - OC_Util::addStyle("styles",null,true); - - // avatars - if (\OC::$server->getSystemConfig()->getValue('enable_avatars', true) === true) { - \OC_Util::addScript('jquery.avatar', null, true); - \OC_Util::addScript('placeholder', null, true); - } - - OC_Util::addScript('oc-backbone', null, true); - OC_Util::addVendorScript('core', 'backbone/backbone', true); - OC_Util::addVendorScript('snapjs/dist/latest/snap', null, true); - OC_Util::addScript('mimetypelist', null, true); - OC_Util::addScript('mimetype', null, true); - OC_Util::addScript("apps", null, true); - OC_Util::addScript("oc-requesttoken", null, true); - OC_Util::addScript('search', 'search', true); - OC_Util::addScript("config", null, true); - OC_Util::addScript("eventsource", null, true); - OC_Util::addScript("octemplate", null, true); - OC_Util::addTranslations("core", null, true); - OC_Util::addScript("l10n", null, true); - OC_Util::addScript("js", null, true); - OC_Util::addScript("oc-dialogs", null, true); - OC_Util::addScript("jquery.ocdialog", null, true); - OC_Util::addStyle("jquery.ocdialog"); - OC_Util::addScript("compatibility", null, true); - OC_Util::addScript("placeholders", null, true); - OC_Util::addScript('files/fileinfo'); - OC_Util::addScript('files/client'); - - // Add the stuff we need always - // following logic will import all vendor libraries that are - // specified in core/js/core.json - $fileContent = file_get_contents(OC::$SERVERROOT . '/core/js/core.json'); - if($fileContent !== false) { - $coreDependencies = json_decode($fileContent, true); - foreach(array_reverse($coreDependencies['vendor']) as $vendorLibrary) { - // remove trailing ".js" as addVendorScript will append it - OC_Util::addVendorScript( - substr($vendorLibrary, 0, strlen($vendorLibrary) - 3),null,true); - } - } else { - throw new \Exception('Cannot read core/js/core.json'); - } - - if (\OC::$server->getRequest()->isUserAgent([\OC\AppFramework\Http\Request::USER_AGENT_IE])) { - // polyfill for btoa/atob for IE friends - OC_Util::addVendorScript('base64/base64'); - // shim for the davclient.js library - \OCP\Util::addScript('files/iedavclient'); - } - - self::$initTemplateEngineFirstRun = false; - } - - } - - - /** - * find the template with the given name - * @param string $name of the template file (without suffix) - * - * Will select the template file for the selected theme. - * Checking all the possible locations. - * @param string $theme - * @param string $app - * @return string[] - */ - protected function findTemplate($theme, $app, $name) { - // Check if it is a app template or not. - if( $app !== '' ) { - $dirs = $this->getAppTemplateDirs($theme, $app, OC::$SERVERROOT, OC_App::getAppPath($app)); - } else { - $dirs = $this->getCoreTemplateDirs($theme, OC::$SERVERROOT); - } - $locator = new \OC\Template\TemplateFileLocator( $dirs ); - $template = $locator->find($name); - $path = $locator->getPath(); - return array($path, $template); - } - - /** - * Add a custom element to the header - * @param string $tag tag name of the element - * @param array $attributes array of attributes for the element - * @param string $text the text content for the element. If $text is null then the - * element will be written as empty element. So use "" to get a closing tag. - */ - public function addHeader($tag, $attributes, $text=null) { - $this->headers[]= array( - 'tag' => $tag, - 'attributes' => $attributes, - 'text' => $text - ); - } - - /** - * Process the template - * @return boolean|string - * - * This function process the template. If $this->renderAs is set, it - * will produce a full page. - */ - public function fetchPage($additionalParams = null) { - $data = parent::fetchPage($additionalParams); - - if( $this->renderAs ) { - $page = new TemplateLayout($this->renderAs, $this->app); - - // Add custom headers - $headers = ''; - foreach(OC_Util::$headers as $header) { - $headers .= '<'.\OCP\Util::sanitizeHTML($header['tag']); - foreach($header['attributes'] as $name=>$value) { - $headers .= ' '.\OCP\Util::sanitizeHTML($name).'="'.\OCP\Util::sanitizeHTML($value).'"'; - } - if ($header['text'] !== null) { - $headers .= '>'.\OCP\Util::sanitizeHTML($header['text']).''; - } else { - $headers .= '/>'; - } - } - - $page->assign('headers', $headers); - - $page->assign('content', $data); - return $page->fetchPage(); - } - - return $data; - } - - /** - * Include template - * - * @param string $file - * @param array|null $additionalParams - * @return string returns content of included template - * - * Includes another template. use inc('template'); ?> to - * do this. - */ - public function inc( $file, $additionalParams = null ) { - return $this->load($this->path.$file.'.php', $additionalParams); - } - - /** - * Shortcut to print a simple page for users - * @param string $application The application we render the template for - * @param string $name Name of the template - * @param array $parameters Parameters for the template - * @return boolean|null - */ - public static function printUserPage( $application, $name, $parameters = array() ) { - $content = new OC_Template( $application, $name, "user" ); - foreach( $parameters as $key => $value ) { - $content->assign( $key, $value ); - } - print $content->printPage(); - } - - /** - * Shortcut to print a simple page for admins - * @param string $application The application we render the template for - * @param string $name Name of the template - * @param array $parameters Parameters for the template - * @return bool - */ - public static function printAdminPage( $application, $name, $parameters = array() ) { - $content = new OC_Template( $application, $name, "admin" ); - foreach( $parameters as $key => $value ) { - $content->assign( $key, $value ); - } - return $content->printPage(); - } - - /** - * Shortcut to print a simple page for guests - * @param string $application The application we render the template for - * @param string $name Name of the template - * @param array|string $parameters Parameters for the template - * @return bool - */ - public static function printGuestPage( $application, $name, $parameters = array() ) { - $content = new OC_Template( $application, $name, "guest" ); - foreach( $parameters as $key => $value ) { - $content->assign( $key, $value ); - } - return $content->printPage(); - } - - /** - * Print a fatal error page and terminates the script - * @param string $error_msg The error message to show - * @param string $hint An optional hint message - needs to be properly escaped - */ - public static function printErrorPage( $error_msg, $hint = '' ) { - try { - $content = new \OC_Template( '', 'error', 'error', false ); - $errors = array(array('error' => $error_msg, 'hint' => $hint)); - $content->assign( 'errors', $errors ); - $content->printPage(); - } catch (\Exception $e) { - $logger = \OC::$server->getLogger(); - $logger->error("$error_msg $hint", ['app' => 'core']); - $logger->logException($e, ['app' => 'core']); - - header(self::getHttpProtocol() . ' 500 Internal Server Error'); - header('Content-Type: text/plain; charset=utf-8'); - print("$error_msg $hint"); - } - die(); - } - - /** - * print error page using Exception details - * @param Exception | Throwable $exception - */ - public static function printExceptionErrorPage($exception, $fetchPage = false) { - try { - $request = \OC::$server->getRequest(); - $content = new \OC_Template('', 'exception', 'error', false); - $content->assign('errorClass', get_class($exception)); - $content->assign('errorMsg', $exception->getMessage()); - $content->assign('errorCode', $exception->getCode()); - $content->assign('file', $exception->getFile()); - $content->assign('line', $exception->getLine()); - $content->assign('trace', $exception->getTraceAsString()); - $content->assign('debugMode', \OC::$server->getSystemConfig()->getValue('debug', false)); - $content->assign('remoteAddr', $request->getRemoteAddress()); - $content->assign('requestID', $request->getId()); - if ($fetchPage) { - return $content->fetchPage(); - } - $content->printPage(); - } catch (\Exception $e) { - $logger = \OC::$server->getLogger(); - $logger->logException($exception, ['app' => 'core']); - $logger->logException($e, ['app' => 'core']); - - header(self::getHttpProtocol() . ' 500 Internal Server Error'); - header('Content-Type: text/plain; charset=utf-8'); - print("Internal Server Error\n\n"); - print("The server encountered an internal error and was unable to complete your request.\n"); - print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n"); - print("More details can be found in the server log.\n"); - } - die(); - } - - /** - * This is only here to reduce the dependencies in case of an exception to - * still be able to print a plain error message. - * - * Returns the used HTTP protocol. - * - * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0. - * @internal Don't use this - use AppFramework\Http\Request->getHttpProtocol instead - */ - protected static function getHttpProtocol() { - $claimedProtocol = strtoupper($_SERVER['SERVER_PROTOCOL']); - $validProtocols = [ - 'HTTP/1.0', - 'HTTP/1.1', - 'HTTP/2', - ]; - if(in_array($claimedProtocol, $validProtocols, true)) { - return $claimedProtocol; - } - return 'HTTP/1.1'; - } - - /** - * @return bool - */ - public static function isAssetPipelineEnabled() { - try { - if (\OCP\Util::needUpgrade()) { - // Don't use the compiled asset when we need to do an update - return false; - } - } catch (\Exception $e) { - // Catch any exception, because this code is also called when displaying - // an exception error page. - return false; - } - - // asset management enabled? - $config = \OC::$server->getConfig(); - $useAssetPipeline = $config->getSystemValue('asset-pipeline.enabled', false); - if (!$useAssetPipeline) { - return false; - } - - // assets folder exists? - $assetDir = $config->getSystemValue('assetdirectory', \OC::$SERVERROOT) . '/assets'; - if (!is_dir($assetDir)) { - if (!mkdir($assetDir)) { - \OCP\Util::writeLog('assets', - "Folder <$assetDir> does not exist and/or could not be generated.", \OCP\Util::ERROR); - return false; - } - } - - // assets folder can be accessed? - if (!touch($assetDir."/.oc")) { - \OCP\Util::writeLog('assets', - "Folder <$assetDir> could not be accessed.", \OCP\Util::ERROR); - return false; - } - return $useAssetPipeline; - } - -} diff --git a/lib/private/user.php b/lib/private/user.php deleted file mode 100644 index 11c35daa0de..00000000000 --- a/lib/private/user.php +++ /dev/null @@ -1,639 +0,0 @@ - - * @author Andreas Fischer - * @author Arthur Schiwon - * @author Bart Visscher - * @author Bartek Przybylski - * @author Björn Schießle - * @author Florian Preinstorfer - * @author Georg Ehrke - * @author Jakob Sack - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Roeland Jago Douma - * @author shkdee - * @author Thomas Müller - * @author Tom Needham - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -/** - * This class provides wrapper methods for user management. Multiple backends are - * supported. User management operations are delegated to the configured backend for - * execution. - * - * Note that &run is deprecated and won't work anymore. - * - * Hooks provided: - * pre_createUser(&run, uid, password) - * post_createUser(uid, password) - * pre_deleteUser(&run, uid) - * post_deleteUser(uid) - * pre_setPassword(&run, uid, password, recoveryPassword) - * post_setPassword(uid, password, recoveryPassword) - * pre_login(&run, uid, password) - * post_login(uid) - * logout() - */ -class OC_User { - - /** - * @return \OC\User\Session - */ - public static function getUserSession() { - return OC::$server->getUserSession(); - } - - private static $_backends = array(); - - private static $_usedBackends = array(); - - private static $_setupedBackends = array(); - - // bool, stores if a user want to access a resource anonymously, e.g if he opens a public link - private static $incognitoMode = false; - - /** - * Adds the backend to the list of used backends - * - * @param string|\OCP\UserInterface $backend default: database The backend to use for user management - * @return bool - * - * Set the User Authentication Module - */ - public static function useBackend($backend = 'database') { - if ($backend instanceof \OCP\UserInterface) { - self::$_usedBackends[get_class($backend)] = $backend; - \OC::$server->getUserManager()->registerBackend($backend); - } else { - // You'll never know what happens - if (null === $backend OR !is_string($backend)) { - $backend = 'database'; - } - - // Load backend - switch ($backend) { - case 'database': - case 'mysql': - case 'sqlite': - \OCP\Util::writeLog('core', 'Adding user backend ' . $backend . '.', \OCP\Util::DEBUG); - self::$_usedBackends[$backend] = new OC_User_Database(); - \OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]); - break; - case 'dummy': - self::$_usedBackends[$backend] = new \Test\Util\User\Dummy(); - \OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]); - break; - default: - \OCP\Util::writeLog('core', 'Adding default user backend ' . $backend . '.', \OCP\Util::DEBUG); - $className = 'OC_USER_' . strToUpper($backend); - self::$_usedBackends[$backend] = new $className(); - \OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]); - break; - } - } - return true; - } - - /** - * remove all used backends - */ - public static function clearBackends() { - self::$_usedBackends = array(); - \OC::$server->getUserManager()->clearBackends(); - } - - /** - * setup the configured backends in config.php - */ - public static function setupBackends() { - OC_App::loadApps(array('prelogin')); - $backends = \OC::$server->getSystemConfig()->getValue('user_backends', array()); - foreach ($backends as $i => $config) { - $class = $config['class']; - $arguments = $config['arguments']; - if (class_exists($class)) { - if (array_search($i, self::$_setupedBackends) === false) { - // make a reflection object - $reflectionObj = new ReflectionClass($class); - - // use Reflection to create a new instance, using the $args - $backend = $reflectionObj->newInstanceArgs($arguments); - self::useBackend($backend); - self::$_setupedBackends[] = $i; - } else { - \OCP\Util::writeLog('core', 'User backend ' . $class . ' already initialized.', \OCP\Util::DEBUG); - } - } else { - \OCP\Util::writeLog('core', 'User backend ' . $class . ' not found.', \OCP\Util::ERROR); - } - } - } - - /** - * Try to login a user - * - * @param string $loginname The login name of the user to log in - * @param string $password The password of the user - * @return boolean|null - * - * Log in a user and regenerate a new session - if the password is ok - */ - public static function login($loginname, $password) { - $result = self::getUserSession()->login($loginname, $password); - if ($result) { - // Refresh the token - \OC::$server->getCsrfTokenManager()->refreshToken(); - //we need to pass the user name, which may differ from login name - $user = self::getUserSession()->getUser()->getUID(); - OC_Util::setupFS($user); - //trigger creation of user home and /files folder - \OC::$server->getUserFolder($user); - } - return $result; - } - - /** - * Try to login a user using the magic cookie (remember login) - * - * @param string $uid The username of the user to log in - * @param string $token - * @return bool - */ - public static function loginWithCookie($uid, $token) { - return self::getUserSession()->loginWithCookie($uid, $token); - } - - /** - * Try to login a user, assuming authentication - * has already happened (e.g. via Single Sign On). - * - * Log in a user and regenerate a new session. - * - * @param \OCP\Authentication\IApacheBackend $backend - * @return bool - */ - public static function loginWithApache(\OCP\Authentication\IApacheBackend $backend) { - - $uid = $backend->getCurrentUserId(); - $run = true; - OC_Hook::emit("OC_User", "pre_login", array("run" => &$run, "uid" => $uid)); - - if ($uid) { - if (self::getUser() !== $uid) { - self::setUserId($uid); - self::setDisplayName($uid); - self::getUserSession()->setLoginName($uid); - // setup the filesystem - OC_Util::setupFS($uid); - //trigger creation of user home and /files folder - \OC::$server->getUserFolder($uid); - - OC_Hook::emit("OC_User", "post_login", array("uid" => $uid, 'password' => '')); - } - return true; - } - return false; - } - - /** - * Verify with Apache whether user is authenticated. - * - * @return boolean|null - * true: authenticated - * false: not authenticated - * null: not handled / no backend available - */ - public static function handleApacheAuth() { - $backend = self::findFirstActiveUsedBackend(); - if ($backend) { - OC_App::loadApps(); - - //setup extra user backends - self::setupBackends(); - self::unsetMagicInCookie(); - - return self::loginWithApache($backend); - } - - return null; - } - - - /** - * Sets user id for session and triggers emit - */ - public static function setUserId($uid) { - $userSession = \OC::$server->getUserSession(); - $userManager = \OC::$server->getUserManager(); - if ($user = $userManager->get($uid)) { - $userSession->setUser($user); - } else { - \OC::$server->getSession()->set('user_id', $uid); - } - } - - /** - * Sets user display name for session - * - * @param string $uid - * @param string $displayName - * @return bool Whether the display name could get set - */ - public static function setDisplayName($uid, $displayName = null) { - if (is_null($displayName)) { - $displayName = $uid; - } - $user = \OC::$server->getUserManager()->get($uid); - if ($user) { - return $user->setDisplayName($displayName); - } else { - return false; - } - } - - /** - * Tries to login the user with HTTP Basic Authentication - */ - public static function tryBasicAuthLogin() { - if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) { - $result = \OC_User::login($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); - if($result === true) { - /** - * Add DAV authenticated. This should in an ideal world not be - * necessary but the iOS App reads cookies from anywhere instead - * only the DAV endpoint. - * This makes sure that the cookies will be valid for the whole scope - * @see https://github.com/owncloud/core/issues/22893 - */ - \OC::$server->getSession()->set( - \OCA\DAV\Connector\Sabre\Auth::DAV_AUTHENTICATED, - \OC::$server->getUserSession()->getUser()->getUID() - ); - } - } - } - - /** - * Check if the user is logged in, considers also the HTTP basic credentials - * - * @return bool - */ - public static function isLoggedIn() { - if (\OC::$server->getSession()->get('user_id') !== null && self::$incognitoMode === false) { - return self::userExists(\OC::$server->getSession()->get('user_id')); - } - - return false; - } - - /** - * set incognito mode, e.g. if a user wants to open a public link - * - * @param bool $status - */ - public static function setIncognitoMode($status) { - self::$incognitoMode = $status; - } - - /** - * get incognito mode status - * - * @return bool - */ - public static function isIncognitoMode() { - return self::$incognitoMode; - } - - /** - * Supplies an attribute to the logout hyperlink. The default behaviour - * is to return an href with '?logout=true' appended. However, it can - * supply any attribute(s) which are valid for . - * - * @return string with one or more HTML attributes. - */ - public static function getLogoutAttribute() { - $backend = self::findFirstActiveUsedBackend(); - if ($backend) { - return $backend->getLogoutAttribute(); - } - - $logoutUrl = \OC::$server->getURLGenerator()->linkToRouteAbsolute( - 'core.login.logout', - [ - 'requesttoken' => \OCP\Util::callRegister(), - ] - ); - - return 'href="'.$logoutUrl.'"'; - } - - /** - * Check if the user is an admin user - * - * @param string $uid uid of the admin - * @return bool - */ - public static function isAdminUser($uid) { - if (OC_Group::inGroup($uid, 'admin') && self::$incognitoMode === false) { - return true; - } - return false; - } - - - /** - * get the user id of the user currently logged in. - * - * @return string|bool uid or false - */ - public static function getUser() { - $uid = \OC::$server->getSession() ? \OC::$server->getSession()->get('user_id') : null; - if (!is_null($uid) && self::$incognitoMode === false) { - return $uid; - } else { - return false; - } - } - - /** - * get the display name of the user currently logged in. - * - * @param string $uid - * @return string uid or false - */ - public static function getDisplayName($uid = null) { - if ($uid) { - $user = \OC::$server->getUserManager()->get($uid); - if ($user) { - return $user->getDisplayName(); - } else { - return $uid; - } - } else { - $user = self::getUserSession()->getUser(); - if ($user) { - return $user->getDisplayName(); - } else { - return false; - } - } - } - - /** - * Autogenerate a password - * - * @return string - * - * generates a password - */ - public static function generatePassword() { - return \OC::$server->getSecureRandom()->generate(30); - } - - /** - * Set password - * - * @param string $uid The username - * @param string $password The new password - * @param string $recoveryPassword for the encryption app to reset encryption keys - * @return bool - * - * Change the password of a user - */ - public static function setPassword($uid, $password, $recoveryPassword = null) { - $user = \OC::$server->getUserManager()->get($uid); - if ($user) { - return $user->setPassword($password, $recoveryPassword); - } else { - return false; - } - } - - /** - * Check whether user can change his avatar - * - * @param string $uid The username - * @return bool - * - * Check whether a specified user can change his avatar - */ - public static function canUserChangeAvatar($uid) { - $user = \OC::$server->getUserManager()->get($uid); - if ($user) { - return $user->canChangeAvatar(); - } else { - return false; - } - } - - /** - * Check whether user can change his password - * - * @param string $uid The username - * @return bool - * - * Check whether a specified user can change his password - */ - public static function canUserChangePassword($uid) { - $user = \OC::$server->getUserManager()->get($uid); - if ($user) { - return $user->canChangePassword(); - } else { - return false; - } - } - - /** - * Check whether user can change his display name - * - * @param string $uid The username - * @return bool - * - * Check whether a specified user can change his display name - */ - public static function canUserChangeDisplayName($uid) { - $user = \OC::$server->getUserManager()->get($uid); - if ($user) { - return $user->canChangeDisplayName(); - } else { - return false; - } - } - - /** - * Check if the password is correct - * - * @param string $uid The username - * @param string $password The password - * @return string|false user id a string on success, false otherwise - * - * Check if the password is correct without logging in the user - * returns the user id or false - */ - public static function checkPassword($uid, $password) { - $manager = \OC::$server->getUserManager(); - $username = $manager->checkPassword($uid, $password); - if ($username !== false) { - return $username->getUID(); - } - return false; - } - - /** - * @param string $uid The username - * @return string - * - * returns the path to the users home directory - * @deprecated Use \OC::$server->getUserManager->getHome() - */ - public static function getHome($uid) { - $user = \OC::$server->getUserManager()->get($uid); - if ($user) { - return $user->getHome(); - } else { - return \OC::$server->getSystemConfig()->getValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $uid; - } - } - - /** - * Get a list of all users - * - * @return array an array of all uids - * - * Get a list of all users. - * @param string $search - * @param integer $limit - * @param integer $offset - */ - public static function getUsers($search = '', $limit = null, $offset = null) { - $users = \OC::$server->getUserManager()->search($search, $limit, $offset); - $uids = array(); - foreach ($users as $user) { - $uids[] = $user->getUID(); - } - return $uids; - } - - /** - * Get a list of all users display name - * - * @param string $search - * @param int $limit - * @param int $offset - * @return array associative array with all display names (value) and corresponding uids (key) - * - * Get a list of all display names and user ids. - * @deprecated Use \OC::$server->getUserManager->searchDisplayName($search, $limit, $offset) instead. - */ - public static function getDisplayNames($search = '', $limit = null, $offset = null) { - $displayNames = array(); - $users = \OC::$server->getUserManager()->searchDisplayName($search, $limit, $offset); - foreach ($users as $user) { - $displayNames[$user->getUID()] = $user->getDisplayName(); - } - return $displayNames; - } - - /** - * check if a user exists - * - * @param string $uid the username - * @return boolean - */ - public static function userExists($uid) { - return \OC::$server->getUserManager()->userExists($uid); - } - - /** - * disables a user - * - * @param string $uid the user to disable - */ - public static function disableUser($uid) { - $user = \OC::$server->getUserManager()->get($uid); - if ($user) { - $user->setEnabled(false); - } - } - - /** - * enable a user - * - * @param string $uid - */ - public static function enableUser($uid) { - $user = \OC::$server->getUserManager()->get($uid); - if ($user) { - $user->setEnabled(true); - } - } - - /** - * checks if a user is enabled - * - * @param string $uid - * @return bool - */ - public static function isEnabled($uid) { - $user = \OC::$server->getUserManager()->get($uid); - if ($user) { - return $user->isEnabled(); - } else { - return false; - } - } - - /** - * Set cookie value to use in next page load - * - * @param string $username username to be set - * @param string $token - */ - public static function setMagicInCookie($username, $token) { - self::getUserSession()->setMagicInCookie($username, $token); - } - - /** - * Remove cookie for "remember username" - */ - public static function unsetMagicInCookie() { - self::getUserSession()->unsetMagicInCookie(); - } - - /** - * Returns the first active backend from self::$_usedBackends. - * - * @return OCP\Authentication\IApacheBackend|null if no backend active, otherwise OCP\Authentication\IApacheBackend - */ - private static function findFirstActiveUsedBackend() { - foreach (self::$_usedBackends as $backend) { - if ($backend instanceof OCP\Authentication\IApacheBackend) { - if ($backend->isSessionActive()) { - return $backend; - } - } - } - - return null; - } -} diff --git a/lib/private/util.php b/lib/private/util.php deleted file mode 100644 index b3432470f03..00000000000 --- a/lib/private/util.php +++ /dev/null @@ -1,1450 +0,0 @@ - - * @author Andreas Böhler - * @author Andreas Fischer - * @author Arthur Schiwon - * @author Bart Visscher - * @author Bernhard Posselt - * @author Birk Borkason - * @author Björn Schießle - * @author Brice Maron - * @author Christopher Schäpers - * @author Clark Tomlinson - * @author cmeh - * @author Florin Peter - * @author Frank Karlitschek - * @author Georg Ehrke - * @author helix84 - * @author Individual IT Services - * @author Jakob Sack - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Markus Goetz - * @author Martin Mattel - * @author Marvin Thomas Rabe - * @author Michael Gapczynski - * @author Michael Göhler - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Roeland Jago Douma - * @author Stefan Rado - * @author Thomas Müller - * @author Thomas Schmidt - * @author Thomas Tanghus - * @author Victor Dubiniuk - * @author Vincent Chan - * @author Vincent Petry - * @author Volkan Gezer - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -use OCP\IConfig; -use OCP\IGroupManager; -use OCP\IUser; - -class OC_Util { - public static $scripts = array(); - public static $styles = array(); - public static $headers = array(); - private static $rootMounted = false; - private static $fsSetup = false; - - protected static function getAppManager() { - return \OC::$server->getAppManager(); - } - - private static function initLocalStorageRootFS() { - // mount local file backend as root - $configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data"); - //first set up the local "root" storage - \OC\Files\Filesystem::initMountManager(); - if (!self::$rootMounted) { - \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => $configDataDirectory), '/'); - self::$rootMounted = true; - } - } - - /** - * mounting an object storage as the root fs will in essence remove the - * necessity of a data folder being present. - * TODO make home storage aware of this and use the object storage instead of local disk access - * - * @param array $config containing 'class' and optional 'arguments' - */ - private static function initObjectStoreRootFS($config) { - // check misconfiguration - if (empty($config['class'])) { - \OCP\Util::writeLog('files', 'No class given for objectstore', \OCP\Util::ERROR); - } - if (!isset($config['arguments'])) { - $config['arguments'] = array(); - } - - // instantiate object store implementation - $config['arguments']['objectstore'] = new $config['class']($config['arguments']); - // mount with plain / root object store implementation - $config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage'; - - // mount object storage as root - \OC\Files\Filesystem::initMountManager(); - if (!self::$rootMounted) { - \OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/'); - self::$rootMounted = true; - } - } - - /** - * Can be set up - * - * @param string $user - * @return boolean - * @description configure the initial filesystem based on the configuration - */ - public static function setupFS($user = '') { - //setting up the filesystem twice can only lead to trouble - if (self::$fsSetup) { - return false; - } - - \OC::$server->getEventLogger()->start('setup_fs', 'Setup filesystem'); - - // If we are not forced to load a specific user we load the one that is logged in - if ($user === null) { - $user = ''; - } else if ($user == "" && OC_User::isLoggedIn()) { - $user = OC_User::getUser(); - } - - // load all filesystem apps before, so no setup-hook gets lost - OC_App::loadApps(array('filesystem')); - - // the filesystem will finish when $user is not empty, - // mark fs setup here to avoid doing the setup from loading - // OC_Filesystem - if ($user != '') { - self::$fsSetup = true; - } - - \OC\Files\Filesystem::initMountManager(); - - \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(false); - \OC\Files\Filesystem::addStorageWrapper('mount_options', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { - if ($storage->instanceOfStorage('\OC\Files\Storage\Common')) { - /** @var \OC\Files\Storage\Common $storage */ - $storage->setMountOptions($mount->getOptions()); - } - return $storage; - }); - - \OC\Files\Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { - if (!$mount->getOption('enable_sharing', true)) { - return new \OC\Files\Storage\Wrapper\PermissionsMask([ - 'storage' => $storage, - 'mask' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_SHARE - ]); - } - return $storage; - }); - - // install storage availability wrapper, before most other wrappers - \OC\Files\Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, $storage) { - if (!$storage->instanceOfStorage('\OC\Files\Storage\Shared') && !$storage->isLocal()) { - return new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]); - } - return $storage; - }); - - \OC\Files\Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) { - // set up quota for home storages, even for other users - // which can happen when using sharing - - /** - * @var \OC\Files\Storage\Storage $storage - */ - if ($storage->instanceOfStorage('\OC\Files\Storage\Home') - || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage') - ) { - /** @var \OC\Files\Storage\Home $storage */ - if (is_object($storage->getUser())) { - $user = $storage->getUser()->getUID(); - $quota = OC_Util::getUserQuota($user); - if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { - return new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => $quota, 'root' => 'files')); - } - } - } - - return $storage; - }); - - OC_Hook::emit('OC_Filesystem', 'preSetup', array('user' => $user)); - \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(true); - - //check if we are using an object storage - $objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null); - if (isset($objectStore)) { - self::initObjectStoreRootFS($objectStore); - } else { - self::initLocalStorageRootFS(); - } - - if ($user != '' && !OCP\User::userExists($user)) { - \OC::$server->getEventLogger()->end('setup_fs'); - return false; - } - - //if we aren't logged in, there is no use to set up the filesystem - if ($user != "") { - - $userDir = '/' . $user . '/files'; - - //jail the user into his "home" directory - \OC\Files\Filesystem::init($user, $userDir); - - OC_Hook::emit('OC_Filesystem', 'setup', array('user' => $user, 'user_dir' => $userDir)); - } - \OC::$server->getEventLogger()->end('setup_fs'); - return true; - } - - /** - * check if a password is required for each public link - * - * @return boolean - */ - public static function isPublicLinkPasswordRequired() { - $appConfig = \OC::$server->getAppConfig(); - $enforcePassword = $appConfig->getValue('core', 'shareapi_enforce_links_password', 'no'); - return ($enforcePassword === 'yes') ? true : false; - } - - /** - * check if sharing is disabled for the current user - * @param IConfig $config - * @param IGroupManager $groupManager - * @param IUser|null $user - * @return bool - */ - public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) { - if ($config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') { - $groupsList = $config->getAppValue('core', 'shareapi_exclude_groups_list', ''); - $excludedGroups = json_decode($groupsList); - if (is_null($excludedGroups)) { - $excludedGroups = explode(',', $groupsList); - $newValue = json_encode($excludedGroups); - $config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue); - } - $usersGroups = $groupManager->getUserGroupIds($user); - if (!empty($usersGroups)) { - $remainingGroups = array_diff($usersGroups, $excludedGroups); - // if the user is only in groups which are disabled for sharing then - // sharing is also disabled for the user - if (empty($remainingGroups)) { - return true; - } - } - } - return false; - } - - /** - * check if share API enforces a default expire date - * - * @return boolean - */ - public static function isDefaultExpireDateEnforced() { - $isDefaultExpireDateEnabled = \OCP\Config::getAppValue('core', 'shareapi_default_expire_date', 'no'); - $enforceDefaultExpireDate = false; - if ($isDefaultExpireDateEnabled === 'yes') { - $value = \OCP\Config::getAppValue('core', 'shareapi_enforce_expire_date', 'no'); - $enforceDefaultExpireDate = ($value === 'yes') ? true : false; - } - - return $enforceDefaultExpireDate; - } - - /** - * Get the quota of a user - * - * @param string $user - * @return int Quota bytes - */ - public static function getUserQuota($user) { - $userQuota = \OC::$server->getUserManager()->get($user)->getQuota(); - if($userQuota === 'none') { - return \OCP\Files\FileInfo::SPACE_UNLIMITED; - }else{ - return OC_Helper::computerFileSize($userQuota); - } - } - - /** - * copies the skeleton to the users /files - * - * @param String $userId - * @param \OCP\Files\Folder $userDirectory - */ - public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) { - - $skeletonDirectory = \OCP\Config::getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton'); - - if (!empty($skeletonDirectory)) { - \OCP\Util::writeLog( - 'files_skeleton', - 'copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'), - \OCP\Util::DEBUG - ); - self::copyr($skeletonDirectory, $userDirectory); - // update the file cache - $userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE); - } - } - - /** - * copies a directory recursively by using streams - * - * @param string $source - * @param \OCP\Files\Folder $target - * @return void - */ - public static function copyr($source, \OCP\Files\Folder $target) { - $dir = opendir($source); - while (false !== ($file = readdir($dir))) { - if (!\OC\Files\Filesystem::isIgnoredDir($file)) { - if (is_dir($source . '/' . $file)) { - $child = $target->newFolder($file); - self::copyr($source . '/' . $file, $child); - } else { - $child = $target->newFile($file); - stream_copy_to_stream(fopen($source . '/' . $file,'r'), $child->fopen('w')); - } - } - } - closedir($dir); - } - - /** - * @return void - */ - public static function tearDownFS() { - \OC\Files\Filesystem::tearDown(); - self::$fsSetup = false; - self::$rootMounted = false; - } - - /** - * get the current installed version of ownCloud - * - * @return array - */ - public static function getVersion() { - OC_Util::loadVersion(); - return \OC::$server->getSession()->get('OC_Version'); - } - - /** - * get the current installed version string of ownCloud - * - * @return string - */ - public static function getVersionString() { - OC_Util::loadVersion(); - return \OC::$server->getSession()->get('OC_VersionString'); - } - - /** - * @description get the current installed edition of ownCloud. There is the community - * edition that just returns an empty string and the enterprise edition - * that returns "Enterprise". - * @return string - */ - public static function getEditionString() { - if (OC_App::isEnabled('enterprise_key')) { - return "Enterprise"; - } else { - return ""; - } - - } - - /** - * @description get the update channel of the current installed of ownCloud. - * @return string - */ - public static function getChannel() { - OC_Util::loadVersion(); - return \OC::$server->getSession()->get('OC_Channel'); - } - - /** - * @description get the build number of the current installed of ownCloud. - * @return string - */ - public static function getBuild() { - OC_Util::loadVersion(); - return \OC::$server->getSession()->get('OC_Build'); - } - - /** - * @description load the version.php into the session as cache - */ - private static function loadVersion() { - $timestamp = filemtime(OC::$SERVERROOT . '/version.php'); - if (!\OC::$server->getSession()->exists('OC_Version') or OC::$server->getSession()->get('OC_Version_Timestamp') != $timestamp) { - require OC::$SERVERROOT . '/version.php'; - $session = \OC::$server->getSession(); - /** @var $timestamp int */ - $session->set('OC_Version_Timestamp', $timestamp); - /** @var $OC_Version string */ - $session->set('OC_Version', $OC_Version); - /** @var $OC_VersionString string */ - $session->set('OC_VersionString', $OC_VersionString); - /** @var $OC_Build string */ - $session->set('OC_Build', $OC_Build); - - // Allow overriding update channel - - if (\OC::$server->getSystemConfig()->getValue('installed', false)) { - $channel = \OC::$server->getAppConfig()->getValue('core', 'OC_Channel'); - } else { - /** @var $OC_Channel string */ - $channel = $OC_Channel; - } - - if (!is_null($channel)) { - $session->set('OC_Channel', $channel); - } else { - /** @var $OC_Channel string */ - $session->set('OC_Channel', $OC_Channel); - } - } - } - - /** - * generates a path for JS/CSS files. If no application is provided it will create the path for core. - * - * @param string $application application to get the files from - * @param string $directory directory within this application (css, js, vendor, etc) - * @param string $file the file inside of the above folder - * @return string the path - */ - private static function generatePath($application, $directory, $file) { - if (is_null($file)) { - $file = $application; - $application = ""; - } - if (!empty($application)) { - return "$application/$directory/$file"; - } else { - return "$directory/$file"; - } - } - - /** - * add a javascript file - * - * @param string $application application id - * @param string|null $file filename - * @param bool $prepend prepend the Script to the beginning of the list - * @return void - */ - public static function addScript($application, $file = null, $prepend = false) { - $path = OC_Util::generatePath($application, 'js', $file); - - // core js files need separate handling - if ($application !== 'core' && $file !== null) { - self::addTranslations ( $application ); - } - self::addExternalResource($application, $prepend, $path, "script"); - } - - /** - * add a javascript file from the vendor sub folder - * - * @param string $application application id - * @param string|null $file filename - * @param bool $prepend prepend the Script to the beginning of the list - * @return void - */ - public static function addVendorScript($application, $file = null, $prepend = false) { - $path = OC_Util::generatePath($application, 'vendor', $file); - self::addExternalResource($application, $prepend, $path, "script"); - } - - /** - * add a translation JS file - * - * @param string $application application id - * @param string $languageCode language code, defaults to the current language - * @param bool $prepend prepend the Script to the beginning of the list - */ - public static function addTranslations($application, $languageCode = null, $prepend = false) { - if (is_null($languageCode)) { - $languageCode = \OC_L10N::findLanguage($application); - } - if (!empty($application)) { - $path = "$application/l10n/$languageCode"; - } else { - $path = "l10n/$languageCode"; - } - self::addExternalResource($application, $prepend, $path, "script"); - } - - /** - * add a css file - * - * @param string $application application id - * @param string|null $file filename - * @param bool $prepend prepend the Style to the beginning of the list - * @return void - */ - public static function addStyle($application, $file = null, $prepend = false) { - $path = OC_Util::generatePath($application, 'css', $file); - self::addExternalResource($application, $prepend, $path, "style"); - } - - /** - * add a css file from the vendor sub folder - * - * @param string $application application id - * @param string|null $file filename - * @param bool $prepend prepend the Style to the beginning of the list - * @return void - */ - public static function addVendorStyle($application, $file = null, $prepend = false) { - $path = OC_Util::generatePath($application, 'vendor', $file); - self::addExternalResource($application, $prepend, $path, "style"); - } - - /** - * add an external resource css/js file - * - * @param string $application application id - * @param bool $prepend prepend the file to the beginning of the list - * @param string $path - * @param string $type (script or style) - * @return void - */ - private static function addExternalResource($application, $prepend, $path, $type = "script") { - - if ($type === "style") { - if (!in_array($path, self::$styles)) { - if ($prepend === true) { - array_unshift ( self::$styles, $path ); - } else { - self::$styles[] = $path; - } - } - } elseif ($type === "script") { - if (!in_array($path, self::$scripts)) { - if ($prepend === true) { - array_unshift ( self::$scripts, $path ); - } else { - self::$scripts [] = $path; - } - } - } - } - - /** - * Add a custom element to the header - * If $text is null then the element will be written as empty element. - * So use "" to get a closing tag. - * @param string $tag tag name of the element - * @param array $attributes array of attributes for the element - * @param string $text the text content for the element - */ - public static function addHeader($tag, $attributes, $text=null) { - self::$headers[] = array( - 'tag' => $tag, - 'attributes' => $attributes, - 'text' => $text - ); - } - - /** - * formats a timestamp in the "right" way - * - * @param int $timestamp - * @param bool $dateOnly option to omit time from the result - * @param DateTimeZone|string $timeZone where the given timestamp shall be converted to - * @return string timestamp - * - * @deprecated Use \OC::$server->query('DateTimeFormatter') instead - */ - public static function formatDate($timestamp, $dateOnly = false, $timeZone = null) { - if ($timeZone !== null && !$timeZone instanceof \DateTimeZone) { - $timeZone = new \DateTimeZone($timeZone); - } - - /** @var \OC\DateTimeFormatter $formatter */ - $formatter = \OC::$server->query('DateTimeFormatter'); - if ($dateOnly) { - return $formatter->formatDate($timestamp, 'long', $timeZone); - } - return $formatter->formatDateTime($timestamp, 'long', 'long', $timeZone); - } - - /** - * check if the current server configuration is suitable for ownCloud - * - * @param \OCP\IConfig $config - * @return array arrays with error messages and hints - */ - public static function checkServer(\OCP\IConfig $config) { - $l = \OC::$server->getL10N('lib'); - $errors = array(); - $CONFIG_DATADIRECTORY = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data'); - - if (!self::needUpgrade($config) && $config->getSystemValue('installed', false)) { - // this check needs to be done every time - $errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY); - } - - // Assume that if checkServer() succeeded before in this session, then all is fine. - if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) { - return $errors; - } - - $webServerRestart = false; - $setup = new \OC\Setup($config, \OC::$server->getIniWrapper(), \OC::$server->getL10N('lib'), - new \OC_Defaults(), \OC::$server->getLogger(), \OC::$server->getSecureRandom()); - - $urlGenerator = \OC::$server->getURLGenerator(); - - $availableDatabases = $setup->getSupportedDatabases(); - if (empty($availableDatabases)) { - $errors[] = array( - 'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'), - 'hint' => '' //TODO: sane hint - ); - $webServerRestart = true; - } - - // Check if server running on Windows platform - if(OC_Util::runningOnWindows()) { - $errors[] = [ - 'error' => $l->t('Microsoft Windows Platform is not supported'), - 'hint' => $l->t('Running ownCloud Server on the Microsoft Windows platform is not supported. We suggest you ' . - 'use a Linux server in a virtual machine if you have no option for migrating the server itself. ' . - 'Find Linux packages as well as easy to deploy virtual machine images on %s. ' . - 'For migrating existing installations to Linux you can find some tips and a migration script ' . - 'in our documentation.', - ['https://owncloud.org/install/', 'owncloud.org/install/', 'https://owncloud.org/?p=8045']) - ]; - } - - // Check if config folder is writable. - if(!OC_Helper::isReadOnlyConfigEnabled()) { - if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) { - $errors[] = array( - 'error' => $l->t('Cannot write into "config" directory'), - 'hint' => $l->t('This can usually be fixed by ' - . '%sgiving the webserver write access to the config directory%s.', - array('', '')) - ); - } - } - - // Check if there is a writable install folder. - if ($config->getSystemValue('appstoreenabled', true)) { - if (OC_App::getInstallPath() === null - || !is_writable(OC_App::getInstallPath()) - || !is_readable(OC_App::getInstallPath()) - ) { - $errors[] = array( - 'error' => $l->t('Cannot write into "apps" directory'), - 'hint' => $l->t('This can usually be fixed by ' - . '%sgiving the webserver write access to the apps directory%s' - . ' or disabling the appstore in the config file.', - array('', '')) - ); - } - } - // Create root dir. - if ($config->getSystemValue('installed', false)) { - if (!is_dir($CONFIG_DATADIRECTORY)) { - $success = @mkdir($CONFIG_DATADIRECTORY); - if ($success) { - $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); - } else { - $errors[] = array( - 'error' => $l->t('Cannot create "data" directory (%s)', array($CONFIG_DATADIRECTORY)), - 'hint' => $l->t('This can usually be fixed by ' - . 'giving the webserver write access to the root directory.', - array($urlGenerator->linkToDocs('admin-dir_permissions'))) - ); - } - } else if (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) { - //common hint for all file permissions error messages - $permissionsHint = $l->t('Permissions can usually be fixed by ' - . '%sgiving the webserver write access to the root directory%s.', - array('', '')); - $errors[] = array( - 'error' => 'Data directory (' . $CONFIG_DATADIRECTORY . ') not writable by ownCloud', - 'hint' => $permissionsHint - ); - } else { - $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); - } - } - - if (!OC_Util::isSetLocaleWorking()) { - $errors[] = array( - 'error' => $l->t('Setting locale to %s failed', - array('en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/' - . 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8')), - 'hint' => $l->t('Please install one of these locales on your system and restart your webserver.') - ); - } - - // Contains the dependencies that should be checked against - // classes = class_exists - // functions = function_exists - // defined = defined - // ini = ini_get - // If the dependency is not found the missing module name is shown to the EndUser - // When adding new checks always verify that they pass on Travis as well - // for ini settings, see https://github.com/owncloud/administration/blob/master/travis-ci/custom.ini - $dependencies = array( - 'classes' => array( - 'ZipArchive' => 'zip', - 'DOMDocument' => 'dom', - 'XMLWriter' => 'XMLWriter', - 'XMLReader' => 'XMLReader', - ), - 'functions' => [ - 'xml_parser_create' => 'libxml', - 'mb_detect_encoding' => 'mb multibyte', - 'ctype_digit' => 'ctype', - 'json_encode' => 'JSON', - 'gd_info' => 'GD', - 'gzencode' => 'zlib', - 'iconv' => 'iconv', - 'simplexml_load_string' => 'SimpleXML', - 'hash' => 'HASH Message Digest Framework', - 'curl_init' => 'cURL', - ], - 'defined' => array( - 'PDO::ATTR_DRIVER_NAME' => 'PDO' - ), - 'ini' => [ - 'default_charset' => 'UTF-8', - ], - ); - $missingDependencies = array(); - $invalidIniSettings = []; - $moduleHint = $l->t('Please ask your server administrator to install the module.'); - - /** - * FIXME: The dependency check does not work properly on HHVM on the moment - * and prevents installation. Once HHVM is more compatible with our - * approach to check for these values we should re-enable those - * checks. - */ - $iniWrapper = \OC::$server->getIniWrapper(); - if (!self::runningOnHhvm()) { - foreach ($dependencies['classes'] as $class => $module) { - if (!class_exists($class)) { - $missingDependencies[] = $module; - } - } - foreach ($dependencies['functions'] as $function => $module) { - if (!function_exists($function)) { - $missingDependencies[] = $module; - } - } - foreach ($dependencies['defined'] as $defined => $module) { - if (!defined($defined)) { - $missingDependencies[] = $module; - } - } - foreach ($dependencies['ini'] as $setting => $expected) { - if (is_bool($expected)) { - if ($iniWrapper->getBool($setting) !== $expected) { - $invalidIniSettings[] = [$setting, $expected]; - } - } - if (is_int($expected)) { - if ($iniWrapper->getNumeric($setting) !== $expected) { - $invalidIniSettings[] = [$setting, $expected]; - } - } - if (is_string($expected)) { - if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) { - $invalidIniSettings[] = [$setting, $expected]; - } - } - } - } - - foreach($missingDependencies as $missingDependency) { - $errors[] = array( - 'error' => $l->t('PHP module %s not installed.', array($missingDependency)), - 'hint' => $moduleHint - ); - $webServerRestart = true; - } - foreach($invalidIniSettings as $setting) { - if(is_bool($setting[1])) { - $setting[1] = ($setting[1]) ? 'on' : 'off'; - } - $errors[] = [ - 'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]), - 'hint' => $l->t('Adjusting this setting in php.ini will make ownCloud run again') - ]; - $webServerRestart = true; - } - - /** - * The mbstring.func_overload check can only be performed if the mbstring - * module is installed as it will return null if the checking setting is - * not available and thus a check on the boolean value fails. - * - * TODO: Should probably be implemented in the above generic dependency - * check somehow in the long-term. - */ - if($iniWrapper->getBool('mbstring.func_overload') !== null && - $iniWrapper->getBool('mbstring.func_overload') === true) { - $errors[] = array( - 'error' => $l->t('mbstring.func_overload is set to "%s" instead of the expected value "0"', [$iniWrapper->getString('mbstring.func_overload')]), - 'hint' => $l->t('To fix this issue set mbstring.func_overload to 0 in your php.ini') - ); - } - - if(function_exists('xml_parser_create') && - version_compare('2.7.0', LIBXML_DOTTED_VERSION) === 1) { - $errors[] = array( - 'error' => $l->t('libxml2 2.7.0 is at least required. Currently %s is installed.', [LIBXML_DOTTED_VERSION]), - 'hint' => $l->t('To fix this issue update your libxml2 version and restart your web server.') - ); - } - - if (!self::isAnnotationsWorking()) { - $errors[] = array( - 'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'), - 'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.') - ); - } - - if (!\OC::$CLI && $webServerRestart) { - $errors[] = array( - 'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'), - 'hint' => $l->t('Please ask your server administrator to restart the web server.') - ); - } - - $errors = array_merge($errors, self::checkDatabaseVersion()); - - // Cache the result of this function - \OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0); - - return $errors; - } - - /** - * Check the database version - * - * @return array errors array - */ - public static function checkDatabaseVersion() { - $l = \OC::$server->getL10N('lib'); - $errors = array(); - $dbType = \OC::$server->getSystemConfig()->getValue('dbtype', 'sqlite'); - if ($dbType === 'pgsql') { - // check PostgreSQL version - try { - $result = \OC_DB::executeAudited('SHOW SERVER_VERSION'); - $data = $result->fetchRow(); - if (isset($data['server_version'])) { - $version = $data['server_version']; - if (version_compare($version, '9.0.0', '<')) { - $errors[] = array( - 'error' => $l->t('PostgreSQL >= 9 required'), - 'hint' => $l->t('Please upgrade your database version') - ); - } - } - } catch (\Doctrine\DBAL\DBALException $e) { - $logger = \OC::$server->getLogger(); - $logger->warning('Error occurred while checking PostgreSQL version, assuming >= 9'); - $logger->logException($e); - } - } - return $errors; - } - - /** - * Check for correct file permissions of data directory - * - * @param string $dataDirectory - * @return array arrays with error messages and hints - */ - public static function checkDataDirectoryPermissions($dataDirectory) { - $l = \OC::$server->getL10N('lib'); - $errors = array(); - if (self::runningOnWindows()) { - //TODO: permissions checks for windows hosts - } else { - $permissionsModHint = $l->t('Please change the permissions to 0770 so that the directory' - . ' cannot be listed by other users.'); - $perms = substr(decoct(@fileperms($dataDirectory)), -3); - if (substr($perms, -1) != '0') { - chmod($dataDirectory, 0770); - clearstatcache(); - $perms = substr(decoct(@fileperms($dataDirectory)), -3); - if (substr($perms, 2, 1) != '0') { - $errors[] = array( - 'error' => $l->t('Data directory (%s) is readable by other users', array($dataDirectory)), - 'hint' => $permissionsModHint - ); - } - } - } - return $errors; - } - - /** - * Check that the data directory exists and is valid by - * checking the existence of the ".ocdata" file. - * - * @param string $dataDirectory data directory path - * @return array errors found - */ - public static function checkDataDirectoryValidity($dataDirectory) { - $l = \OC::$server->getL10N('lib'); - $errors = []; - if (!self::runningOnWindows() && $dataDirectory[0] !== '/') { - $errors[] = [ - 'error' => $l->t('Data directory (%s) must be an absolute path', [$dataDirectory]), - 'hint' => $l->t('Check the value of "datadirectory" in your configuration') - ]; - } - if (!file_exists($dataDirectory . '/.ocdata')) { - $errors[] = [ - 'error' => $l->t('Data directory (%s) is invalid', [$dataDirectory]), - 'hint' => $l->t('Please check that the data directory contains a file' . - ' ".ocdata" in its root.') - ]; - } - return $errors; - } - - /** - * Check if the user is logged in, redirects to home if not. With - * redirect URL parameter to the request URI. - * - * @return void - */ - public static function checkLoggedIn() { - // Check if we are a user - if (!OC_User::isLoggedIn()) { - header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute( - 'core.login.showLoginForm', - [ - 'redirect_url' => \OC::$server->getRequest()->getRequestUri() - ] - ) - ); - exit(); - } - } - - /** - * Check if the user is a admin, redirects to home if not - * - * @return void - */ - public static function checkAdminUser() { - OC_Util::checkLoggedIn(); - if (!OC_User::isAdminUser(OC_User::getUser())) { - header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); - exit(); - } - } - - /** - * Check if it is allowed to remember login. - * - * @note Every app can set 'rememberlogin' to 'false' to disable the remember login feature - * - * @return bool - */ - public static function rememberLoginAllowed() { - - $apps = OC_App::getEnabledApps(); - - foreach ($apps as $app) { - $appInfo = OC_App::getAppInfo($app); - if (isset($appInfo['rememberlogin']) && $appInfo['rememberlogin'] === 'false') { - return false; - } - - } - return true; - } - - /** - * Check if the user is a subadmin, redirects to home if not - * - * @return null|boolean $groups where the current user is subadmin - */ - public static function checkSubAdminUser() { - OC_Util::checkLoggedIn(); - $userObject = \OC::$server->getUserSession()->getUser(); - $isSubAdmin = false; - if($userObject !== null) { - $isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject); - } - - if (!$isSubAdmin) { - header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); - exit(); - } - return true; - } - - /** - * Returns the URL of the default page - * based on the system configuration and - * the apps visible for the current user - * - * @return string URL - */ - public static function getDefaultPageUrl() { - $urlGenerator = \OC::$server->getURLGenerator(); - // Deny the redirect if the URL contains a @ - // This prevents unvalidated redirects like ?redirect_url=:user@domain.com - if (isset($_REQUEST['redirect_url']) && strpos($_REQUEST['redirect_url'], '@') === false) { - $location = $urlGenerator->getAbsoluteURL(urldecode($_REQUEST['redirect_url'])); - } else { - $defaultPage = \OC::$server->getAppConfig()->getValue('core', 'defaultpage'); - if ($defaultPage) { - $location = $urlGenerator->getAbsoluteURL($defaultPage); - } else { - $appId = 'files'; - $defaultApps = explode(',', \OCP\Config::getSystemValue('defaultapp', 'files')); - // find the first app that is enabled for the current user - foreach ($defaultApps as $defaultApp) { - $defaultApp = OC_App::cleanAppId(strip_tags($defaultApp)); - if (static::getAppManager()->isEnabledForUser($defaultApp)) { - $appId = $defaultApp; - break; - } - } - - if(getenv('front_controller_active') === 'true') { - $location = $urlGenerator->getAbsoluteURL('/apps/' . $appId . '/'); - } else { - $location = $urlGenerator->getAbsoluteURL('/index.php/apps/' . $appId . '/'); - } - } - } - return $location; - } - - /** - * Redirect to the user default page - * - * @return void - */ - public static function redirectToDefaultPage() { - $location = self::getDefaultPageUrl(); - header('Location: ' . $location); - exit(); - } - - /** - * get an id unique for this instance - * - * @return string - */ - public static function getInstanceId() { - $id = \OC::$server->getSystemConfig()->getValue('instanceid', null); - if (is_null($id)) { - // We need to guarantee at least one letter in instanceid so it can be used as the session_name - $id = 'oc' . \OC::$server->getSecureRandom()->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_DIGITS); - \OC::$server->getSystemConfig()->setValue('instanceid', $id); - } - return $id; - } - - /** - * Public function to sanitize HTML - * - * This function is used to sanitize HTML and should be applied on any - * string or array of strings before displaying it on a web page. - * - * @param string|array $value - * @return string|array an array of sanitized strings or a single sanitized string, depends on the input parameter. - */ - public static function sanitizeHTML($value) { - if (is_array($value)) { - $value = array_map(function($value) { - return self::sanitizeHTML($value); - }, $value); - } else { - // Specify encoding for PHP<5.4 - $value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); - } - return $value; - } - - /** - * Public function to encode url parameters - * - * This function is used to encode path to file before output. - * Encoding is done according to RFC 3986 with one exception: - * Character '/' is preserved as is. - * - * @param string $component part of URI to encode - * @return string - */ - public static function encodePath($component) { - $encoded = rawurlencode($component); - $encoded = str_replace('%2F', '/', $encoded); - return $encoded; - } - - /** - * Check if the .htaccess file is working - * @param \OCP\IConfig $config - * @return bool - * @throws Exception - * @throws \OC\HintException If the test file can't get written. - */ - public function isHtaccessWorking(\OCP\IConfig $config) { - - if (\OC::$CLI || !$config->getSystemValue('check_for_working_htaccess', true)) { - return true; - } - - // php dev server does not support htaccess - if (php_sapi_name() === 'cli-server') { - return false; - } - - // testdata - $fileName = '/htaccesstest.txt'; - $testContent = 'testcontent'; - - // creating a test file - $testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName; - - if (file_exists($testFile)) {// already running this test, possible recursive call - return false; - } - - $fp = @fopen($testFile, 'w'); - if (!$fp) { - throw new OC\HintException('Can\'t create test file to check for working .htaccess file.', - 'Make sure it is possible for the webserver to write to ' . $testFile); - } - fwrite($fp, $testContent); - fclose($fp); - - // accessing the file via http - $url = \OC::$server->getURLGenerator()->getAbsoluteURL(OC::$WEBROOT . '/data' . $fileName); - try { - $content = \OC::$server->getHTTPClientService()->newClient()->get($url)->getBody(); - } catch (\Exception $e) { - $content = false; - } - - // cleanup - @unlink($testFile); - - /* - * If the content is not equal to test content our .htaccess - * is working as required - */ - return $content !== $testContent; - } - - /** - * Check if the setlocal call does not work. This can happen if the right - * local packages are not available on the server. - * - * @return bool - */ - public static function isSetLocaleWorking() { - // setlocale test is pointless on Windows - if (OC_Util::runningOnWindows()) { - return true; - } - - \Patchwork\Utf8\Bootup::initLocale(); - if ('' === basename('§')) { - return false; - } - return true; - } - - /** - * Check if it's possible to get the inline annotations - * - * @return bool - */ - public static function isAnnotationsWorking() { - $reflection = new \ReflectionMethod(__METHOD__); - $docs = $reflection->getDocComment(); - - return (is_string($docs) && strlen($docs) > 50); - } - - /** - * Check if the PHP module fileinfo is loaded. - * - * @return bool - */ - public static function fileInfoLoaded() { - return function_exists('finfo_open'); - } - - /** - * clear all levels of output buffering - * - * @return void - */ - public static function obEnd() { - while (ob_get_level()) { - ob_end_clean(); - } - } - - /** - * Checks whether the server is running on Windows - * - * @return bool true if running on Windows, false otherwise - */ - public static function runningOnWindows() { - return (substr(PHP_OS, 0, 3) === "WIN"); - } - - /** - * Checks whether the server is running on Mac OS X - * - * @return bool true if running on Mac OS X, false otherwise - */ - public static function runningOnMac() { - return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN'); - } - - /** - * Checks whether server is running on HHVM - * - * @return bool True if running on HHVM, false otherwise - */ - public static function runningOnHhvm() { - return defined('HHVM_VERSION'); - } - - /** - * Handles the case that there may not be a theme, then check if a "default" - * theme exists and take that one - * - * @return string the theme - */ - public static function getTheme() { - $theme = \OC::$server->getSystemConfig()->getValue("theme", ''); - - if ($theme === '') { - if (is_dir(OC::$SERVERROOT . '/themes/default')) { - $theme = 'default'; - } - } - - return $theme; - } - - /** - * Clear a single file from the opcode cache - * This is useful for writing to the config file - * in case the opcode cache does not re-validate files - * Returns true if successful, false if unsuccessful: - * caller should fall back on clearing the entire cache - * with clearOpcodeCache() if unsuccessful - * - * @param string $path the path of the file to clear from the cache - * @return bool true if underlying function returns true, otherwise false - */ - public static function deleteFromOpcodeCache($path) { - $ret = false; - if ($path) { - // APC >= 3.1.1 - if (function_exists('apc_delete_file')) { - $ret = @apc_delete_file($path); - } - // Zend OpCache >= 7.0.0, PHP >= 5.5.0 - if (function_exists('opcache_invalidate')) { - $ret = opcache_invalidate($path); - } - } - return $ret; - } - - /** - * Clear the opcode cache if one exists - * This is necessary for writing to the config file - * in case the opcode cache does not re-validate files - * - * @return void - */ - public static function clearOpcodeCache() { - // APC - if (function_exists('apc_clear_cache')) { - apc_clear_cache(); - } - // Zend Opcache - if (function_exists('accelerator_reset')) { - accelerator_reset(); - } - // XCache - if (function_exists('xcache_clear_cache')) { - if (\OC::$server->getIniWrapper()->getBool('xcache.admin.enable_auth')) { - \OCP\Util::writeLog('core', 'XCache opcode cache will not be cleared because "xcache.admin.enable_auth" is enabled.', \OCP\Util::WARN); - } else { - @xcache_clear_cache(XC_TYPE_PHP, 0); - } - } - // Opcache (PHP >= 5.5) - if (function_exists('opcache_reset')) { - opcache_reset(); - } - } - - /** - * Normalize a unicode string - * - * @param string $value a not normalized string - * @return bool|string - */ - public static function normalizeUnicode($value) { - if(Normalizer::isNormalized($value)) { - return $value; - } - - $normalizedValue = Normalizer::normalize($value); - if ($normalizedValue === null || $normalizedValue === false) { - \OC::$server->getLogger()->warning('normalizing failed for "' . $value . '"', ['app' => 'core']); - return $value; - } - - return $normalizedValue; - } - - /** - * @param boolean|string $file - * @return string - */ - public static function basename($file) { - $file = rtrim($file, '/'); - $t = explode('/', $file); - return array_pop($t); - } - - /** - * A human readable string is generated based on version, channel and build number - * - * @return string - */ - public static function getHumanVersion() { - $version = OC_Util::getVersionString() . ' (' . OC_Util::getChannel() . ')'; - $build = OC_Util::getBuild(); - if (!empty($build) and OC_Util::getChannel() === 'daily') { - $version .= ' Build:' . $build; - } - return $version; - } - - /** - * Returns whether the given file name is valid - * - * @param string $file file name to check - * @return bool true if the file name is valid, false otherwise - * @deprecated use \OC\Files\View::verifyPath() - */ - public static function isValidFileName($file) { - $trimmed = trim($file); - if ($trimmed === '') { - return false; - } - if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) { - return false; - } - foreach (str_split($trimmed) as $char) { - if (strpos(\OCP\Constants::FILENAME_INVALID_CHARS, $char) !== false) { - return false; - } - } - return true; - } - - /** - * Check whether the instance needs to perform an upgrade, - * either when the core version is higher or any app requires - * an upgrade. - * - * @param \OCP\IConfig $config - * @return bool whether the core or any app needs an upgrade - * @throws \OC\HintException When the upgrade from the given version is not allowed - */ - public static function needUpgrade(\OCP\IConfig $config) { - if ($config->getSystemValue('installed', false)) { - $installedVersion = $config->getSystemValue('version', '0.0.0'); - $currentVersion = implode('.', \OCP\Util::getVersion()); - $versionDiff = version_compare($currentVersion, $installedVersion); - if ($versionDiff > 0) { - return true; - } else if ($config->getSystemValue('debug', false) && $versionDiff < 0) { - // downgrade with debug - $installedMajor = explode('.', $installedVersion); - $installedMajor = $installedMajor[0] . '.' . $installedMajor[1]; - $currentMajor = explode('.', $currentVersion); - $currentMajor = $currentMajor[0] . '.' . $currentMajor[1]; - if ($installedMajor === $currentMajor) { - // Same major, allow downgrade for developers - return true; - } else { - // downgrade attempt, throw exception - throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')'); - } - } else if ($versionDiff < 0) { - // downgrade attempt, throw exception - throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')'); - } - - // also check for upgrades for apps (independently from the user) - $apps = \OC_App::getEnabledApps(false, true); - $shouldUpgrade = false; - foreach ($apps as $app) { - if (\OC_App::shouldUpgrade($app)) { - $shouldUpgrade = true; - break; - } - } - return $shouldUpgrade; - } else { - return false; - } - } - -} -- cgit v1.2.3