diff options
author | Thomas Müller <thomas.mueller@tmit.eu> | 2013-09-25 13:36:30 +0200 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2013-09-30 16:36:59 +0200 |
commit | 9c9dc276b7a1d2592c4fb0a887888632dc1f1e29 (patch) | |
tree | bbe3aed3e09c31c68806bdb8acffef70ba08f51c /lib/private | |
parent | a711399e62d5a9f14d4b748efe4354ee37e61f13 (diff) | |
download | nextcloud-server-9c9dc276b7a1d2592c4fb0a887888632dc1f1e29.tar.gz nextcloud-server-9c9dc276b7a1d2592c4fb0a887888632dc1f1e29.zip |
move the private namespace OC into lib/private - OCP will stay in lib/public
Conflicts:
lib/private/vcategories.php
Diffstat (limited to 'lib/private')
307 files changed, 38567 insertions, 0 deletions
diff --git a/lib/private/allconfig.php b/lib/private/allconfig.php new file mode 100644 index 00000000000..72aabf60793 --- /dev/null +++ b/lib/private/allconfig.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + * + */ + +namespace OC; + +/** + * Class to combine all the configuration options ownCloud offers + */ +class AllConfig implements \OCP\IConfig { + /** + * Sets a new system wide value + * @param string $key the key of the value, under which will be saved + * @param string $value the value that should be stored + * @todo need a use case for this + */ +// public function setSystemValue($key, $value) { +// \OCP\Config::setSystemValue($key, $value); +// } + + /** + * Looks up a system wide defined value + * @param string $key the key of the value, under which it was saved + * @return string the saved value + */ + public function getSystemValue($key) { + return \OCP\Config::getSystemValue($key, ''); + } + + + /** + * Writes a new app wide value + * @param string $appName the appName that we want to store the value under + * @param string $key the key of the value, under which will be saved + * @param string $value the value that should be stored + */ + public function setAppValue($appName, $key, $value) { + \OCP\Config::setAppValue($appName, $key, $value); + } + + /** + * Looks up an app wide defined value + * @param string $appName the appName that we stored the value under + * @param string $key the key of the value, under which it was saved + * @return string the saved value + */ + public function getAppValue($appName, $key) { + return \OCP\Config::getAppValue($appName, $key, ''); + } + + + /** + * Set a user defined value + * @param string $userId the userId of the user that we want to store the value under + * @param string $appName the appName that we want to store the value under + * @param string $key the key under which the value is being stored + * @param string $value the value that you want to store + */ + public function setUserValue($userId, $appName, $key, $value) { + \OCP\Config::setUserValue($userId, $appName, $key, $value); + } + + /** + * Shortcut for getting a user defined value + * @param string $userId the userId of the user that we want to store the value under + * @param string $appName the appName that we stored the value under + * @param string $key the key under which the value is being stored + */ + public function getUserValue($userId, $appName, $key){ + return \OCP\Config::getUserValue($userId, $appName, $key); + } +} diff --git a/lib/private/api.php b/lib/private/api.php new file mode 100644 index 00000000000..31f3f968d9b --- /dev/null +++ b/lib/private/api.php @@ -0,0 +1,293 @@ +<?php +/** +* ownCloud +* +* @author Tom Needham +* @author Michael Gapczynski +* @author Bart Visscher +* @copyright 2012 Tom Needham tom@owncloud.com +* @copyright 2012 Michael Gapczynski mtgap@owncloud.com +* @copyright 2012 Bart Visscher bartv@thisnet.nl +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +class OC_API { + + /** + * API authentication levels + */ + const GUEST_AUTH = 0; + const USER_AUTH = 1; + const SUBADMIN_AUTH = 2; + const ADMIN_AUTH = 3; + + /** + * API Response Codes + */ + const RESPOND_UNAUTHORISED = 997; + const RESPOND_SERVER_ERROR = 996; + const RESPOND_NOT_FOUND = 998; + const RESPOND_UNKNOWN_ERROR = 999; + + /** + * api actions + */ + protected static $actions = array(); + + /** + * 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 = OC_API::USER_AUTH, + $defaults = array(), + $requirements = array()) { + $name = strtolower($method).$url; + $name = str_replace(array('/', '{', '}'), '_', $name); + if(!isset(self::$actions[$name])) { + OC::getRouter()->useCollection('ocs'); + OC::getRouter()->create($name, $url) + ->method($method) + ->defaults($defaults) + ->requirements($requirements) + ->action('OC_API', 'call'); + self::$actions[$name] = array(); + } + self::$actions[$name][] = array('app' => $app, 'action' => $action, 'authlevel' => $authLevel); + } + + /** + * handles an api call + * @param array $parameters + */ + public static function call($parameters) { + // Prepare the request variables + if($_SERVER['REQUEST_METHOD'] == 'PUT') { + parse_str(file_get_contents("php://input"), $parameters['_put']); + } else if($_SERVER['REQUEST_METHOD'] == 'DELETE') { + parse_str(file_get_contents("php://input"), $parameters['_delete']); + } + $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, OC_API::RESPOND_UNAUTHORISED, 'Unauthorised'), + ); + continue; + } + if(!is_callable($action['action'])) { + $responses[] = array( + 'app' => $action['app'], + 'response' => new OC_OCS_Result(null, OC_API::RESPOND_NOT_FOUND, 'Api method not found'), + ); + continue; + } + // Run the action + $responses[] = array( + 'app' => $action['app'], + 'response' => call_user_func($action['action'], $parameters), + ); + } + $response = self::mergeResponses($responses); + $formats = array('json', 'xml'); + + $format = !empty($_GET['format']) && in_array($_GET['format'], $formats) ? $_GET['format'] : 'xml'; + OC_User::logout(); + + self::respond($response, $format); + } + + /** + * merge the returned result objects into one response + * @param array $responses + */ + private static function mergeResponses($responses) { + $response = array(); + // Sort into shipped and thirdparty + $shipped = array( + 'succeeded' => array(), + 'failed' => array(), + ); + $thirdparty = array( + 'succeeded' => array(), + 'failed' => array(), + ); + + foreach($responses as $response) { + if(OC_App::isShipped($response['app']) || ($response['app'] === 'core')) { + if($response['response']->succeeded()) { + $shipped['succeeded'][$response['app']] = $response['response']; + } else { + $shipped['failed'][$response['app']] = $response['response']; + } + } else { + if($response['response']->succeeded()) { + $thirdparty['succeeded'][$response['app']] = $response['response']; + } else { + $thirdparty['failed'][$response['app']] = $response['response']; + } + } + } + + // Remove any error responses if there is one shipped response that succeeded + if(!empty($shipped['succeeded'])) { + $responses = array_merge($shipped['succeeded'], $thirdparty['succeeded']); + } else 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 reponse code should we return? + // Maybe any that are not OC_API::RESPOND_SERVER_ERROR + $response = reset($shipped['failed']); + return $response; + } elseif(!empty($thirdparty['failed'])) { + // Return the third party failure result + $response = reset($thirdparty['failed']); + return $response; + } else { + $responses = array_merge($shipped['succeeded'], $thirdparty['succeeded']); + } + // Merge the successful responses + $meta = array(); + $data = array(); + + foreach($responses as $app => $response) { + if(OC_App::isShipped($app)) { + $data = array_merge_recursive($response->getData(), $data); + } else { + $data = array_merge_recursive($data, $response->getData()); + } + } + $result = new OC_OCS_Result($data, 100); + return $result; + } + + /** + * 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 OC_API::GUEST_AUTH: + // Anyone can access + return true; + break; + case OC_API::USER_AUTH: + // User required + return self::loginUser(); + break; + case OC_API::SUBADMIN_AUTH: + // Check for subadmin + $user = self::loginUser(); + if(!$user) { + return false; + } else { + $subAdmin = OC_SubAdmin::isSubAdmin($user); + $admin = OC_User::isAdminUser($user); + if($subAdmin || $admin) { + return true; + } else { + return false; + } + } + break; + case OC_API::ADMIN_AUTH: + // Check for admin + $user = self::loginUser(); + if(!$user) { + return false; + } else { + return OC_User::isAdminUser($user); + } + break; + default: + // oops looks like invalid level supplied + return false; + break; + } + } + + /** + * http basic auth + * @return string|false (username, or false on failure) + */ + private static function loginUser(){ + $authUser = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : ''; + $authPw = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; + return OC_User::login($authUser, $authPw) ? $authUser : false; + } + + /** + * respond to a call + * @param OC_OCS_Result $result + * @param string $format the format xml|json + */ + private static function respond($result, $format='xml') { + // Send 401 headers if unauthorised + if($result->getStatusCode() === self::RESPOND_UNAUTHORISED) { + header('WWW-Authenticate: Basic realm="Authorisation Required"'); + header('HTTP/1.0 401 Unauthorized'); + } + $response = array( + 'ocs' => array( + 'meta' => $result->getMeta(), + 'data' => $result->getData(), + ), + ); + if ($format == 'json') { + OC_JSON::encodedPrint($response); + } else if ($format == 'xml') { + header('Content-type: text/xml; charset=UTF-8'); + $writer = new XMLWriter(); + $writer->openMemory(); + $writer->setIndent( true ); + $writer->startDocument(); + self::toXML($response, $writer); + $writer->endDocument(); + echo $writer->outputMemory(true); + } + } + + 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); + } + } + } + +} diff --git a/lib/private/app.php b/lib/private/app.php new file mode 100644 index 00000000000..0ab1ee57f63 --- /dev/null +++ b/lib/private/app.php @@ -0,0 +1,967 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @author Jakob Sack + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * 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 $settingsForms = array(); + static private $adminForms = array(); + static private $personalForms = array(); + static private $appInfo = array(); + static private $appTypes = array(); + static private $loadedApps = array(); + static private $checkedApps = array(); + static private $altLogin = array(); + + /** + * @brief clean the appid + * @param $app Appid that needs to be cleaned + * @return string + */ + public static function cleanAppId($app) { + return str_replace(array('\0', '/', '\\', '..'), '', $app); + } + + /** + * @brief loads all apps + * @param array $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/app.php + * exists. + * + * if $types is set, only apps of those types will be loaded + */ + public static function loadApps($types=null) { + // Load the enabled apps here + $apps = self::getEnabledApps(); + // 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); + self::$loadedApps[] = $app; + } + } + ob_end_clean(); + + if (!defined('DEBUG') || !DEBUG) { + if (is_null($types) + && empty(OC_Util::$coreScripts) + && empty(OC_Util::$coreStyles)) { + OC_Util::$coreScripts = OC_Util::$scripts; + OC_Util::$scripts = array(); + OC_Util::$coreStyles = OC_Util::$styles; + OC_Util::$styles = array(); + } + } + // return + return true; + } + + /** + * load a single app + * @param string $app + */ + public static function loadApp($app) { + if(is_file(self::getAppPath($app).'/appinfo/app.php')) { + self::checkUpgrade($app); + require_once $app.'/appinfo/app.php'; + } + } + + /** + * 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_Appconfig::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(isset($appData['types'])) { + $appTypes=implode(',', $appData['types']); + }else{ + $appTypes=''; + } + + OC_Appconfig::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){ + $info = self::getAppInfo($appid); + if(isset($info['shipped']) && $info['shipped']=='true') { + return true; + } else { + return false; + } + } + + /** + * get all enabled apps + */ + public static function getEnabledApps() { + if(!OC_Config::getValue('installed', false)) { + return array(); + } + $apps=array('files'); + $sql = 'SELECT `appid` FROM `*PREFIX*appconfig`' + .' WHERE `configkey` = \'enabled\' AND `configvalue`=\'yes\''; + if (OC_Config::getValue( 'dbtype', 'sqlite' ) === 'oci') { + //FIXME oracle hack: need to explicitly cast CLOB to CHAR for comparison + $sql = 'SELECT `appid` FROM `*PREFIX*appconfig`' + .' WHERE `configkey` = \'enabled\' AND to_char(`configvalue`)=\'yes\''; + } + $query = OC_DB::prepare( $sql ); + $result=$query->execute(); + if( \OC_DB::isError($result)) { + throw new DatabaseException($result->getMessage(), $query); + } + while($row=$result->fetchRow()) { + if(array_search($row['appid'], $apps)===false) { + $apps[]=$row['appid']; + } + } + return $apps; + } + + /** + * @brief 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 ) { + if( 'files'==$app or ('yes' == OC_Appconfig::getValue( $app, 'enabled' ))) { + return true; + } + + return false; + } + + /** + * @brief enables an app + * @param mixed $app app + * @throws \Exception + * @return void + * + * This function set an app as enabled in appconfig. + */ + public static function enable( $app ) { + if(!OC_Installer::isInstalled($app)) { + // 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)) { + $app = OC_Installer::installShippedApp($app); + }else{ + $appdata=OC_OCSClient::getApplication($app); + $download=OC_OCSClient::getApplicationDownload($app, 1); + if(isset($download['downloadlink']) and $download['downloadlink']!='') { + $info = array('source'=>'http', 'href'=>$download['downloadlink'], 'appdata'=>$appdata); + $app=OC_Installer::installApp($info); + } + } + } + $l = OC_L10N::get('core'); + if($app!==false) { + // check if the app is compatible with this version of ownCloud + $info=OC_App::getAppInfo($app); + $version=OC_Util::getVersion(); + if(!isset($info['require']) or !self::isAppVersionCompatible($version, $info['require'])) { + throw new \Exception( + $l->t("App \"%s\" can't be installed because it is not compatible with this version of ownCloud.", + array($info['name']) + ) + ); + }else{ + OC_Appconfig::setValue( $app, 'enabled', 'yes' ); + if(isset($appdata['id'])) { + OC_Appconfig::setValue( $app, 'ocsid', $appdata['id'] ); + } + } + }else{ + throw new \Exception($l->t("No app name specified")); + } + } + + /** + * @brief disables an app + * @param string $app app + * @return bool + * + * This function set an app as disabled in appconfig. + */ + public static function disable( $app ) { + // check if app is a shipped app or not. if not delete + \OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app)); + OC_Appconfig::setValue( $app, 'enabled', 'no' ); + + // check if app is a shipped app or not. if not delete + if(!OC_App::isShipped( $app )) { + OC_Installer::removeApp( $app ); + } + } + + /** + * @brief adds an entry to the navigation + * @param array $data array containing the data + * @return bool + * + * This function adds a new entry to the navigation visible to users. $data + * is an associative array. + * The following keys are required: + * - id: unique id for this entry ('addressbook_index') + * - href: link to the page + * - name: Human readable name ('Addressbook') + * + * The following keys are optional: + * - icon: path to the icon of the app + * - order: integer, that influences the position of your application in + * the navigation. Lower values come first. + */ + public static function addNavigationEntry( $data ) { + OC::$server->getNavigationManager()->add($data); + return true; + } + + /** + * @brief marks a navigation entry as active + * @param string $id id of the entry + * @return bool + * + * This function sets a navigation entry as active and removes the 'active' + * property from all other entries. The templates can use this for + * highlighting the current position of the user. + */ + public static function setActiveNavigationEntry( $id ) { + OC::$server->getNavigationManager()->setActiveEntry($id); + return true; + } + + /** + * @brief Get the navigation entries for the $app + * @param string $app app + * @return array of the $data added with addNavigationEntry + * + * Warning: destroys the existing entries + */ + public static function getAppNavigationEntries($app) { + if(is_file(self::getAppPath($app).'/appinfo/app.php')) { + OC::$server->getNavigationManager()->clear(); + require $app.'/appinfo/app.php'; + return OC::$server->getNavigationManager()->getAll(); + } + return array(); + } + + /** + * @brief gets the active Menu entry + * @return string id or empty string + * + * This function returns the id of the active navigation entry (set by + * setActiveNavigationEntry + */ + public static function getActiveNavigationEntry() { + return OC::$server->getNavigationManager()->getActiveEntry(); + } + + /** + * @brief Returns the Settings Navigation + * @return array + * + * 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_L10N::get('lib'); + + $settings = array(); + // by default, settings only contain the help menu + if(OC_Util::getEditionString() === '' && + OC_Config::getValue('knowledgebaseenabled', true)==true) { + $settings = array( + array( + "id" => "help", + "order" => 1000, + "href" => OC_Helper::linkToRoute( "settings_help" ), + "name" => $l->t("Help"), + "icon" => OC_Helper::imagePath( "settings", "help.svg" ) + ) + ); + } + + // if the user is logged-in + if (OC_User::isLoggedIn()) { + // personal menu + $settings[] = array( + "id" => "personal", + "order" => 1, + "href" => OC_Helper::linkToRoute( "settings_personal" ), + "name" => $l->t("Personal"), + "icon" => OC_Helper::imagePath( "settings", "personal.svg" ) + ); + + // if there are some settings forms + if(!empty(self::$settingsForms)) { + // settings menu + $settings[]=array( + "id" => "settings", + "order" => 1000, + "href" => OC_Helper::linkToRoute( "settings_settings" ), + "name" => $l->t("Settings"), + "icon" => OC_Helper::imagePath( "settings", "settings.svg" ) + ); + } + + //SubAdmins are also allowed to access user management + if(OC_SubAdmin::isSubAdmin(OC_User::getUser())) { + // admin users menu + $settings[] = array( + "id" => "core_users", + "order" => 2, + "href" => OC_Helper::linkToRoute( "settings_users" ), + "name" => $l->t("Users"), + "icon" => OC_Helper::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" => OC_Helper::linkToRoute( "settings_admin" ), + "name" => $l->t("Admin"), + "icon" => OC_Helper::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 + */ + public static function getInstallPath() { + if(OC_Config::getValue('appstoreenabled', true)==false) { + return false; + } + + foreach(OC::$APPSROOTS as $dir) { + if(isset($dir['writable']) && $dir['writable']===true) { + return $dir['path']; + } + } + + OC_Log::write('core', 'No application directories are marked as writable.', OC_Log::ERROR); + return null; + } + + + protected static function findAppInDirectories($appid) { + static $app_dir = array(); + if (isset($app_dir[$appid])) { + return $app_dir[$appid]; + } + foreach(OC::$APPSROOTS as $dir) { + if(file_exists($dir['path'].'/'.$appid)) { + return $app_dir[$appid]=$dir; + } + } + return false; + } + /** + * Get the directory for the given app. + * If the app is defined in multiple directories, the first one is taken. (false if not found) + */ + public static function getAppPath($appid) { + if( ($dir = self::findAppInDirectories($appid)) != false) { + return $dir['path'].'/'.$appid; + } + return 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) + */ + 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, either from appinfo/version or from appinfo/info.xml + */ + public static function getAppVersion($appid) { + $file= self::getAppPath($appid).'/appinfo/version'; + if(is_file($file) && $version = trim(file_get_contents($file))) { + return $version; + }else{ + $appData=self::getAppInfo($appid); + return isset($appData['version'])? $appData['version'] : ''; + } + } + + /** + * @brief 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 + * @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]; + } + $file= self::getAppPath($appid).'/appinfo/info.xml'; + } + $data=array(); + $content=@file_get_contents($file); + if(!$content) { + return null; + } + $xml = new SimpleXMLElement($content); + $data['info']=array(); + $data['remote']=array(); + $data['public']=array(); + foreach($xml->children() as $child) { + /** + * @var $child SimpleXMLElement + */ + if($child->getName()=='remote') { + foreach($child->children() as $remote) { + /** + * @var $remote SimpleXMLElement + */ + $data['remote'][$remote->getName()]=(string)$remote; + } + }elseif($child->getName()=='public') { + foreach($child->children() as $public) { + /** + * @var $public SimpleXMLElement + */ + $data['public'][$public->getName()]=(string)$public; + } + }elseif($child->getName()=='types') { + $data['types']=array(); + foreach($child->children() as $type) { + /** + * @var $type SimpleXMLElement + */ + $data['types'][]=$type->getName(); + } + }elseif($child->getName()=='description') { + $xml=(string)$child->asXML(); + $data[$child->getName()]=substr($xml, 13, -14);//script <description> tags + }else{ + $data[$child->getName()]=(string)$child; + } + } + self::$appInfo[$appid]=$data; + + return $data; + } + + /** + * @brief 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() { + $script=substr(OC_Request::scriptName(), strlen(OC::$WEBROOT)+1); + $topFolder=substr($script, 0, strpos($script, '/')); + if (empty($topFolder)) { + $path_info = OC_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; + } + } + + + /** + * get the forms for either settings, admin or personal + */ + public static function getForms($type) { + $forms=array(); + switch($type) { + case 'settings': + $source=self::$settingsForms; + break; + 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 a settings form to be shown + */ + public static function registerSettings($app, $page) { + self::$settingsForms[]= $app.'/'.$page.'.php'; + } + + /** + * register an admin form to be shown + */ + public static function registerAdmin($app, $page) { + self::$adminForms[]= $app.'/'.$page.'.php'; + } + + /** + * register a personal form to be shown + */ + public static function registerPersonal($app, $page) { + self::$personalForms[]= $app.'/'.$page.'.php'; + } + + public static function registerLogIn($entry) { + self::$altLogin[] = $entry; + } + + public static function getAlternativeLogIns() { + return self::$altLogin; + } + + /** + * @brief: get a list of all apps in the apps folder + * @return array or 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'])) { + OC_Log::write('core', 'unable to read app folder : ' .$apps_dir['path'], OC_Log::WARN); + continue; + } + $dh = opendir( $apps_dir['path'] ); + + if(is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + + if ($file[0] != '.' and is_file($apps_dir['path'].'/'.$file.'/appinfo/app.php')) { + + $apps[] = $file; + + } + + } + } + + } + + return $apps; + } + + /** + * @brief: Lists all apps, this is used in apps.php + * @return array + */ + public static function listAllApps() { + $installedApps = OC_App::getAllApps(); + + //TODO which apps do we want to blacklist and how do we integrate + // blacklisting with the multi apps folder feature? + + $blacklist = array('files');//we dont want to show configuration for these + $appList = array(); + + foreach ( $installedApps as $app ) { + if ( array_search( $app, $blacklist ) === false ) { + + $info=OC_App::getAppInfo($app); + + if (!isset($info['name'])) { + OC_Log::write('core', 'App id "'.$app.'" has no name in appinfo', OC_Log::ERROR); + continue; + } + + if ( OC_Appconfig::getValue( $app, 'enabled', 'no') == 'yes' ) { + $active = true; + } else { + $active = false; + } + + $info['active'] = $active; + + if(isset($info['shipped']) and ($info['shipped']=='true')) { + $info['internal']=true; + $info['internallabel']='Internal App'; + $info['internalclass']=''; + $info['update']=false; + } else { + $info['internal']=false; + $info['internallabel']='3rd Party'; + $info['internalclass']='externalapp'; + $info['update']=OC_Installer::isUpdateAvailable($app); + } + + $info['preview'] = OC_Helper::imagePath('settings', 'trans.png'); + $info['version'] = OC_App::getAppVersion($app); + $appList[] = $info; + } + } + $remoteApps = OC_App::getAppstoreApps(); + if ( $remoteApps ) { + // Remove duplicates + foreach ( $appList as $app ) { + foreach ( $remoteApps AS $key => $remote ) { + if ( + $app['name'] == $remote['name'] + // To set duplicate detection to use OCS ID instead of string name, + // enable this code, remove the line of code above, + // and add <ocs_id>[ID]</ocs_id> to info.xml of each 3rd party app: + // OR $app['ocs_id'] == $remote['ocs_id'] + ) { + unset( $remoteApps[$key]); + } + } + } + $combinedApps = array_merge( $appList, $remoteApps ); + } else { + $combinedApps = $appList; + } + return $combinedApps; + } + + /** + * @brief: get a list of all apps on apps.owncloud.com + * @return array, multi-dimensional array of apps. + * Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description + */ + public static function getAppstoreApps( $filter = 'approved' ) { + $categoryNames = OC_OCSClient::getCategories(); + if ( is_array( $categoryNames ) ) { + // Check that categories of apps were retrieved correctly + if ( ! $categories = array_keys( $categoryNames ) ) { + return false; + } + + $page = 0; + $remoteApps = OC_OCSClient::getApplications( $categories, $page, $filter ); + $app1 = array(); + $i = 0; + foreach ( $remoteApps as $app ) { + $app1[$i] = $app; + $app1[$i]['author'] = $app['personid']; + $app1[$i]['ocs_id'] = $app['id']; + $app1[$i]['internal'] = $app1[$i]['active'] = 0; + $app1[$i]['update'] = false; + if($app['label']=='recommended') { + $app1[$i]['internallabel'] = 'Recommended'; + $app1[$i]['internalclass'] = 'recommendedapp'; + }else{ + $app1[$i]['internallabel'] = '3rd Party'; + $app1[$i]['internalclass'] = 'externalapp'; + } + + + // rating img + if($app['score']>=0 and $app['score']<5) $img=OC_Helper::imagePath( "core", "rating/s1.png" ); + elseif($app['score']>=5 and $app['score']<15) $img=OC_Helper::imagePath( "core", "rating/s2.png" ); + elseif($app['score']>=15 and $app['score']<25) $img=OC_Helper::imagePath( "core", "rating/s3.png" ); + elseif($app['score']>=25 and $app['score']<35) $img=OC_Helper::imagePath( "core", "rating/s4.png" ); + elseif($app['score']>=35 and $app['score']<45) $img=OC_Helper::imagePath( "core", "rating/s5.png" ); + elseif($app['score']>=45 and $app['score']<55) $img=OC_Helper::imagePath( "core", "rating/s6.png" ); + elseif($app['score']>=55 and $app['score']<65) $img=OC_Helper::imagePath( "core", "rating/s7.png" ); + elseif($app['score']>=65 and $app['score']<75) $img=OC_Helper::imagePath( "core", "rating/s8.png" ); + elseif($app['score']>=75 and $app['score']<85) $img=OC_Helper::imagePath( "core", "rating/s9.png" ); + elseif($app['score']>=85 and $app['score']<95) $img=OC_Helper::imagePath( "core", "rating/s10.png" ); + elseif($app['score']>=95 and $app['score']<100) $img=OC_Helper::imagePath( "core", "rating/s11.png" ); + + $app1[$i]['score'] = '<img src="'.$img.'"> Score: '.$app['score'].'%'; + $i++; + } + } + + if ( empty( $app1 ) ) { + return false; + } else { + return $app1; + } + } + + /** + * check if the app needs updating and update when needed + */ + public static function checkUpgrade($app) { + if (in_array($app, self::$checkedApps)) { + return; + } + self::$checkedApps[] = $app; + $versions = self::getAppVersions(); + $currentVersion=OC_App::getAppVersion($app); + if ($currentVersion) { + $installedVersion = $versions[$app]; + if (version_compare($currentVersion, $installedVersion, '>')) { + $info = self::getAppInfo($app); + OC_Log::write($app, + 'starting app upgrade from '.$installedVersion.' to '.$currentVersion, + OC_Log::DEBUG); + try { + OC_App::updateApp($app); + OC_Hook::emit('update', 'success', 'Updated '.$info['name'].' app'); + } + catch (Exception $e) { + OC_Hook::emit('update', 'failure', 'Failed to update '.$info['name'].' app: '.$e->getMessage()); + $l = OC_L10N::get('lib'); + throw new RuntimeException($l->t('Failed to upgrade "%s".', array($app)), 0, $e); + } + OC_Appconfig::setValue($app, 'installed_version', OC_App::getAppVersion($app)); + } + } + } + + /** + * check if the current enabled apps are compatible with the current + * ownCloud version. disable them if not. + * This is important if you upgrade ownCloud and have non ported 3rd + * party apps installed. + */ + public static function checkAppsRequirements($apps = array()) { + if (empty($apps)) { + $apps = OC_App::getEnabledApps(); + } + $version = OC_Util::getVersion(); + foreach($apps as $app) { + // check if the app is compatible with this version of ownCloud + $info = OC_App::getAppInfo($app); + if(!isset($info['require']) or !self::isAppVersionCompatible($version, $info['require'])) { + OC_Log::write('core', + 'App "'.$info['name'].'" ('.$app.') can\'t be used because it is' + .' not compatible with this version of ownCloud', + OC_Log::ERROR); + OC_App::disable( $app ); + OC_Hook::emit('update', 'success', 'Disabled '.$info['name'].' app because it is not compatible'); + } + } + } + + + /** + * Compares the app version with the owncloud version to see if the app + * requires a newer version than the currently active one + * @param array $owncloudVersions array with 3 entries: major minor bugfix + * @param string $appRequired the required version from the xml + * major.minor.bugfix + * @return boolean true if compatible, otherwise false + */ + public static function isAppVersionCompatible($owncloudVersions, $appRequired){ + $appVersions = explode('.', $appRequired); + + for($i=0; $i<count($appVersions); $i++){ + $appVersion = (int) $appVersions[$i]; + + if(isset($owncloudVersions[$i])){ + $owncloudVersion = $owncloudVersions[$i]; + } else { + $owncloudVersion = 0; + } + + if($owncloudVersion < $appVersion){ + return false; + } elseif ($owncloudVersion > $appVersion) { + return true; + } + } + + return true; + } + + + /** + * get the installed version of all apps + */ + public static function getAppVersions() { + static $versions; + if (isset($versions)) { // simple cache, needs to be fixed + return $versions; // when function is used besides in checkUpgrade + } + $versions=array(); + $query = OC_DB::prepare( 'SELECT `appid`, `configvalue` FROM `*PREFIX*appconfig`' + .' WHERE `configkey` = \'installed_version\'' ); + $result = $query->execute(); + while($row = $result->fetchRow()) { + $versions[$row['appid']]=$row['configvalue']; + } + return $versions; + } + + /** + * update the database for the app and call the update script + * @param string $appid + */ + public static function updateApp($appid) { + if(file_exists(self::getAppPath($appid).'/appinfo/preupdate.php')) { + self::loadApp($appid); + include self::getAppPath($appid).'/appinfo/preupdate.php'; + } + if(file_exists(self::getAppPath($appid).'/appinfo/database.xml')) { + OC_DB::updateDbFromStructure(self::getAppPath($appid).'/appinfo/database.xml'); + } + if(!self::isEnabled($appid)) { + return; + } + if(file_exists(self::getAppPath($appid).'/appinfo/update.php')) { + self::loadApp($appid); + include self::getAppPath($appid).'/appinfo/update.php'; + } + + //set remote/public handlers + $appData=self::getAppInfo($appid); + foreach($appData['remote'] as $name=>$path) { + OCP\CONFIG::setAppValue('core', 'remote_'.$name, $appid.'/'.$path); + } + foreach($appData['public'] as $name=>$path) { + OCP\CONFIG::setAppValue('core', 'public_'.$name, $appid.'/'.$path); + } + + self::setAppTypes($appid); + } + + /** + * @param string $appid + * @return \OC\Files\View + */ + 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{ + OC_Log::write('core', 'Can\'t get app storage, app '.$appid.', user not logged in', OC_Log::ERROR); + return false; + } + }else{ + OC_Log::write('core', 'Can\'t get app storage, app '.$appid.' not enabled', OC_Log::ERROR); + return false; + } + } +} diff --git a/lib/private/appconfig.php b/lib/private/appconfig.php new file mode 100644 index 00000000000..e615d838173 --- /dev/null +++ b/lib/private/appconfig.php @@ -0,0 +1,203 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @author Jakob Sack + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +/* + * + * The following SQL statement is just a help for developers and will not be + * executed! + * + * CREATE TABLE `appconfig` ( + * `appid` VARCHAR( 255 ) NOT NULL , + * `configkey` VARCHAR( 255 ) NOT NULL , + * `configvalue` VARCHAR( 255 ) NOT NULL + * ) + * + */ + +/** + * This class provides an easy way for apps to store config values in the + * database. + */ +class OC_Appconfig{ + /** + * @brief Get all apps using the config + * @return array with app ids + * + * This function returns a list of all apps that have at least one + * entry in the appconfig table. + */ + public static function getApps() { + // No magic in here! + $query = OC_DB::prepare( 'SELECT DISTINCT `appid` FROM `*PREFIX*appconfig`' ); + $result = $query->execute(); + + $apps = array(); + while( $row = $result->fetchRow()) { + $apps[] = $row["appid"]; + } + + return $apps; + } + + /** + * @brief Get the available keys for an app + * @param string $app the app we are looking for + * @return array with key names + * + * This function gets all keys of an app. Please note that the values are + * not returned. + */ + public static function getKeys( $app ) { + // No magic in here as well + $query = OC_DB::prepare( 'SELECT `configkey` FROM `*PREFIX*appconfig` WHERE `appid` = ?' ); + $result = $query->execute( array( $app )); + + $keys = array(); + while( $row = $result->fetchRow()) { + $keys[] = $row["configkey"]; + } + + return $keys; + } + + /** + * @brief Gets the config value + * @param string $app app + * @param string $key key + * @param string $default = null, default value if the key does not exist + * @return string the value or $default + * + * This function gets a value from the appconfig table. If the key does + * not exist the default value will be returned + */ + public static function getValue( $app, $key, $default = null ) { + // At least some magic in here :-) + $query = OC_DB::prepare( 'SELECT `configvalue` FROM `*PREFIX*appconfig`' + .' WHERE `appid` = ? AND `configkey` = ?' ); + $result = $query->execute( array( $app, $key )); + $row = $result->fetchRow(); + if($row) { + return $row["configvalue"]; + }else{ + return $default; + } + } + + /** + * @brief check if a key is set in the appconfig + * @param string $app + * @param string $key + * @return bool + */ + public static function hasKey($app, $key) { + $exists = self::getKeys( $app ); + return in_array( $key, $exists ); + } + + /** + * @brief sets a value in the appconfig + * @param string $app app + * @param string $key key + * @param string $value value + * @return bool + * + * Sets a value. If the key did not exist before it will be created. + */ + public static function setValue( $app, $key, $value ) { + // Does the key exist? yes: update. No: insert + if(! self::hasKey($app, $key)) { + $query = OC_DB::prepare( 'INSERT INTO `*PREFIX*appconfig` ( `appid`, `configkey`, `configvalue` )' + .' VALUES( ?, ?, ? )' ); + $query->execute( array( $app, $key, $value )); + } + else{ + $query = OC_DB::prepare( 'UPDATE `*PREFIX*appconfig` SET `configvalue` = ?' + .' WHERE `appid` = ? AND `configkey` = ?' ); + $query->execute( array( $value, $app, $key )); + } + } + + /** + * @brief Deletes a key + * @param string $app app + * @param string $key key + * @return bool + * + * Deletes a key. + */ + public static function deleteKey( $app, $key ) { + // Boring! + $query = OC_DB::prepare( 'DELETE FROM `*PREFIX*appconfig` WHERE `appid` = ? AND `configkey` = ?' ); + $query->execute( array( $app, $key )); + + return true; + } + + /** + * @brief Remove app from appconfig + * @param string $app app + * @return bool + * + * Removes all keys in appconfig belonging to the app. + */ + public static function deleteApp( $app ) { + // Nothing special + $query = OC_DB::prepare( 'DELETE FROM `*PREFIX*appconfig` WHERE `appid` = ?' ); + $query->execute( array( $app )); + + return true; + } + + /** + * get multiply values, either the app or key can be used as wildcard by setting it to false + * @param app + * @param key + * @return array + */ + public static function getValues($app, $key) { + if($app!==false and $key!==false) { + return false; + } + $fields='`configvalue`'; + $where='WHERE'; + $params=array(); + if($app!==false) { + $fields.=', `configkey`'; + $where.=' `appid` = ?'; + $params[]=$app; + $key='configkey'; + }else{ + $fields.=', `appid`'; + $where.=' `configkey` = ?'; + $params[]=$key; + $key='appid'; + } + $queryString='SELECT '.$fields.' FROM `*PREFIX*appconfig` '.$where; + $query=OC_DB::prepare($queryString); + $result=$query->execute($params); + $values=array(); + while($row=$result->fetchRow()) { + $values[$row[$key]]=$row['configvalue']; + } + return $values; + } +} diff --git a/lib/private/appframework/app.php b/lib/private/appframework/app.php new file mode 100644 index 00000000000..7ff55bb809d --- /dev/null +++ b/lib/private/appframework/app.php @@ -0,0 +1,98 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework; + +use OC\AppFramework\DependencyInjection\DIContainer; +use OCP\AppFramework\IAppContainer; + + +/** + * Entry point for every request in your app. You can consider this as your + * public static void main() method + * + * Handles all the dependency injection, controllers and output flow + */ +class App { + + + /** + * Shortcut for calling a controller method and printing the result + * @param string $controllerName the name of the controller under which it is + * stored in the DI container + * @param string $methodName the method that you want to call + * @param array $urlParams an array with variables extracted from the routes + * @param DIContainer $container an instance of a pimple container. + */ + public static function main($controllerName, $methodName, array $urlParams, + IAppContainer $container) { + $container['urlParams'] = $urlParams; + $controller = $container[$controllerName]; + + // initialize the dispatcher and run all the middleware before the controller + $dispatcher = $container['Dispatcher']; + + list($httpHeaders, $responseHeaders, $output) = + $dispatcher->dispatch($controller, $methodName); + + if(!is_null($httpHeaders)) { + header($httpHeaders); + } + + foreach($responseHeaders as $name => $value) { + header($name . ': ' . $value); + } + + if(!is_null($output)) { + header('Content-Length: ' . strlen($output)); + print($output); + } + + } + + /** + * Shortcut for calling a controller method and printing the result. + * Similar to App:main except that no headers will be sent. + * This should be used for example when registering sections via + * \OC\AppFramework\Core\API::registerAdmin() + * + * @param string $controllerName the name of the controller under which it is + * stored in the DI container + * @param string $methodName the method that you want to call + * @param array $urlParams an array with variables extracted from the routes + * @param DIContainer $container an instance of a pimple container. + */ + public static function part($controllerName, $methodName, array $urlParams, + DIContainer $container){ + + $container['urlParams'] = $urlParams; + $controller = $container[$controllerName]; + + $dispatcher = $container['Dispatcher']; + + list(, , $output) = $dispatcher->dispatch($controller, $methodName); + return $output; + } + +} diff --git a/lib/private/appframework/controller/controller.php b/lib/private/appframework/controller/controller.php new file mode 100644 index 00000000000..0ea0a38cc09 --- /dev/null +++ b/lib/private/appframework/controller/controller.php @@ -0,0 +1,142 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Controller; + +use OC\AppFramework\Http\Request; +use OC\AppFramework\Core\API; +use OCP\AppFramework\Http\TemplateResponse; + + +/** + * Base class to inherit your controllers from + */ +abstract class Controller { + + /** + * @var API instance of the api layer + */ + protected $api; + + protected $request; + + /** + * @param API $api an api wrapper instance + * @param Request $request an instance of the request + */ + public function __construct(API $api, Request $request){ + $this->api = $api; + $this->request = $request; + } + + + /** + * Lets you access post and get parameters by the index + * @param string $key the key which you want to access in the URL Parameter + * placeholder, $_POST or $_GET array. + * The priority how they're returned is the following: + * 1. URL parameters + * 2. POST parameters + * 3. GET parameters + * @param mixed $default If the key is not found, this value will be returned + * @return mixed the content of the array + */ + public function params($key, $default=null){ + return $this->request->getParam($key, $default); + } + + + /** + * Returns all params that were received, be it from the request + * (as GET or POST) or throuh the URL by the route + * @return array the array with all parameters + */ + public function getParams() { + return $this->request->getParams(); + } + + + /** + * Returns the method of the request + * @return string the method of the request (POST, GET, etc) + */ + public function method() { + return $this->request->getMethod(); + } + + + /** + * Shortcut for accessing an uploaded file through the $_FILES array + * @param string $key the key that will be taken from the $_FILES array + * @return array the file in the $_FILES element + */ + public function getUploadedFile($key) { + return $this->request->getUploadedFile($key); + } + + + /** + * Shortcut for getting env variables + * @param string $key the key that will be taken from the $_ENV array + * @return array the value in the $_ENV element + */ + public function env($key) { + return $this->request->getEnv($key); + } + + + /** + * Shortcut for getting cookie variables + * @param string $key the key that will be taken from the $_COOKIE array + * @return array the value in the $_COOKIE element + */ + public function cookie($key) { + return $this->request->getCookie($key); + } + + + /** + * Shortcut for rendering a template + * @param string $templateName the name of the template + * @param array $params the template parameters in key => value structure + * @param string $renderAs user renders a full page, blank only your template + * admin an entry in the admin settings + * @param array $headers set additional headers in name/value pairs + * @return \OCP\AppFramework\Http\TemplateResponse containing the page + */ + public function render($templateName, array $params=array(), + $renderAs='user', array $headers=array()){ + $response = new TemplateResponse($this->api, $templateName); + $response->setParams($params); + $response->renderAs($renderAs); + + foreach($headers as $name => $value){ + $response->addHeader($name, $value); + } + + return $response; + } + + +} diff --git a/lib/private/appframework/core/api.php b/lib/private/appframework/core/api.php new file mode 100644 index 00000000000..39522ee3dd5 --- /dev/null +++ b/lib/private/appframework/core/api.php @@ -0,0 +1,348 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Core; +use OCP\AppFramework\IApi; + + +/** + * This is used to wrap the owncloud static api calls into an object to make the + * code better abstractable for use in the dependency injection container + * + * Should you find yourself in need for more methods, simply inherit from this + * class and add your methods + */ +class API implements IApi{ + + private $appName; + + /** + * constructor + * @param string $appName the name of your application + */ + public function __construct($appName){ + $this->appName = $appName; + } + + + /** + * Gets the userid of the current user + * @return string the user id of the current user + */ + public function getUserId(){ + return \OCP\User::getUser(); + } + + + /** + * Adds a new javascript file + * @param string $scriptName the name of the javascript in js/ without the suffix + * @param string $appName the name of the app, defaults to the current one + */ + public function addScript($scriptName, $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + \OCP\Util::addScript($appName, $scriptName); + } + + + /** + * Adds a new css file + * @param string $styleName the name of the css file in css/without the suffix + * @param string $appName the name of the app, defaults to the current one + */ + public function addStyle($styleName, $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + \OCP\Util::addStyle($appName, $styleName); + } + + + /** + * shorthand for addScript for files in the 3rdparty directory + * @param string $name the name of the file without the suffix + */ + public function add3rdPartyScript($name){ + \OCP\Util::addScript($this->appName . '/3rdparty', $name); + } + + + /** + * shorthand for addStyle for files in the 3rdparty directory + * @param string $name the name of the file without the suffix + */ + public function add3rdPartyStyle($name){ + \OCP\Util::addStyle($this->appName . '/3rdparty', $name); + } + + + /** + * Returns the translation object + * @return \OC_L10N the translation object + */ + public function getTrans(){ + # TODO: use public api + return \OC_L10N::get($this->appName); + } + + + /** + * Returns the URL for a route + * @param string $routeName the name of the route + * @param array $arguments an array with arguments which will be filled into the url + * @return string the url + */ + public function linkToRoute($routeName, $arguments=array()){ + return \OCP\Util::linkToRoute($routeName, $arguments); + } + + + /** + * Returns an URL for an image or file + * @param string $file the name of the file + * @param string $appName the name of the app, defaults to the current one + */ + public function linkTo($file, $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + return \OCP\Util::linkTo($appName, $file); + } + + + /** + * Returns the link to an image, like link to but only with prepending img/ + * @param string $file the name of the file + * @param string $appName the name of the app, defaults to the current one + */ + public function imagePath($file, $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + return \OCP\Util::imagePath($appName, $file); + } + + + /** + * Makes an URL absolute + * @param string $url the url + * @return string the absolute url + */ + public function getAbsoluteURL($url){ + # TODO: use public api + return \OC_Helper::makeURLAbsolute($url); + } + + + /** + * links to a file + * @param string $file the name of the file + * @param string $appName the name of the app, defaults to the current one + * @deprecated replaced with linkToRoute() + * @return string the url + */ + public function linkToAbsolute($file, $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + return \OCP\Util::linkToAbsolute($appName, $file); + } + + + /** + * Checks if the CSRF check was correct + * @return bool true if CSRF check passed + */ + public function passesCSRFCheck(){ + # TODO: use public api + return \OC_Util::isCallRegistered(); + } + + + /** + * Checks if an app is enabled + * @param string $appName the name of an app + * @return bool true if app is enabled + */ + public function isAppEnabled($appName){ + return \OCP\App::isEnabled($appName); + } + + + /** + * Writes a function into the error log + * @param string $msg the error message to be logged + * @param int $level the error level + */ + public function log($msg, $level=null){ + switch($level){ + case 'debug': + $level = \OCP\Util::DEBUG; + break; + case 'info': + $level = \OCP\Util::INFO; + break; + case 'warn': + $level = \OCP\Util::WARN; + break; + case 'fatal': + $level = \OCP\Util::FATAL; + break; + default: + $level = \OCP\Util::ERROR; + break; + } + \OCP\Util::writeLog($this->appName, $msg, $level); + } + + + /** + * turns an owncloud path into a path on the filesystem + * @param string path the path to the file on the oc filesystem + * @return string the filepath in the filesystem + */ + public function getLocalFilePath($path){ + # TODO: use public api + return \OC_Filesystem::getLocalFile($path); + } + + + /** + * used to return and open a new eventsource + * @return \OC_EventSource a new open EventSource class + */ + public function openEventSource(){ + # TODO: use public api + return new \OC_EventSource(); + } + + /** + * @brief connects a function to a hook + * @param string $signalClass class name of emitter + * @param string $signalName name of signal + * @param string $slotClass class name of slot + * @param string $slotName name of slot, in another word, this is the + * name of the method that will be called when registered + * signal is emitted. + * @return bool, always true + */ + public function connectHook($signalClass, $signalName, $slotClass, $slotName) { + return \OCP\Util::connectHook($signalClass, $signalName, $slotClass, $slotName); + } + + /** + * @brief Emits a signal. To get data from the slot use references! + * @param string $signalClass class name of emitter + * @param string $signalName name of signal + * @param array $params defautl: array() array with additional data + * @return bool, true if slots exists or false if not + */ + public function emitHook($signalClass, $signalName, $params = array()) { + return \OCP\Util::emitHook($signalClass, $signalName, $params); + } + + /** + * @brief clear hooks + * @param string $signalClass + * @param string $signalName + */ + public function clearHook($signalClass=false, $signalName=false) { + if ($signalClass) { + \OC_Hook::clear($signalClass, $signalName); + } + } + + /** + * Gets the content of an URL by using CURL or a fallback if it is not + * installed + * @param string $url the url that should be fetched + * @return string the content of the webpage + */ + public function getUrlContent($url) { + return \OC_Util::getUrlContent($url); + } + + /** + * Register a backgroundjob task + * @param string $className full namespace and class name of the class + * @param string $methodName the name of the static method that should be + * called + */ + public function addRegularTask($className, $methodName) { + \OCP\Backgroundjob::addRegularTask($className, $methodName); + } + + /** + * Returns a template + * @param string $templateName the name of the template + * @param string $renderAs how it should be rendered + * @param string $appName the name of the app + * @return \OCP\Template a new template + */ + public function getTemplate($templateName, $renderAs='user', $appName=null){ + if($appName === null){ + $appName = $this->appName; + } + + if($renderAs === 'blank'){ + return new \OCP\Template($appName, $templateName); + } else { + return new \OCP\Template($appName, $templateName, $renderAs); + } + } + + + /** + * Tells ownCloud to include a template in the admin overview + * @param string $mainPath the path to the main php file without the php + * suffix, relative to your apps directory! not the template directory + * @param string $appName the name of the app, defaults to the current one + */ + public function registerAdmin($mainPath, $appName=null) { + if($appName === null){ + $appName = $this->appName; + } + + \OCP\App::registerAdmin($appName, $mainPath); + } + + + /** + * get the filesystem info + * + * @param string $path + * @return array with the following keys: + * - size + * - mtime + * - mimetype + * - encrypted + * - versioned + */ + public function getFileInfo($path) { + return \OC\Files\Filesystem::getFileInfo($path); + } + +} diff --git a/lib/private/appframework/dependencyinjection/dicontainer.php b/lib/private/appframework/dependencyinjection/dicontainer.php new file mode 100644 index 00000000000..3755d45fa09 --- /dev/null +++ b/lib/private/appframework/dependencyinjection/dicontainer.php @@ -0,0 +1,146 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\DependencyInjection; + +use OC\AppFramework\Http\Http; +use OC\AppFramework\Http\Request; +use OC\AppFramework\Http\Dispatcher; +use OC\AppFramework\Core\API; +use OC\AppFramework\Middleware\MiddlewareDispatcher; +use OC\AppFramework\Middleware\Security\SecurityMiddleware; +use OC\AppFramework\Utility\SimpleContainer; +use OC\AppFramework\Utility\TimeFactory; +use OCP\AppFramework\IApi; +use OCP\AppFramework\IAppContainer; +use OCP\AppFramework\IMiddleWare; +use OCP\IServerContainer; + + +class DIContainer extends SimpleContainer implements IAppContainer{ + + /** + * @var array + */ + private $middleWares = array(); + + /** + * Put your class dependencies in here + * @param string $appName the name of the app + */ + public function __construct($appName){ + + $this['AppName'] = $appName; + + $this->registerParameter('ServerContainer', \OC::$server); + + $this['API'] = $this->share(function($c){ + return new API($c['AppName']); + }); + + /** + * Http + */ + $this['Request'] = $this->share(function($c) { + /** @var $c SimpleContainer */ + /** @var $server IServerContainer */ + $server = $c->query('ServerContainer'); + return $server->getRequest(); + }); + + $this['Protocol'] = $this->share(function($c){ + if(isset($_SERVER['SERVER_PROTOCOL'])) { + return new Http($_SERVER, $_SERVER['SERVER_PROTOCOL']); + } else { + return new Http($_SERVER); + } + }); + + $this['Dispatcher'] = $this->share(function($c) { + return new Dispatcher($c['Protocol'], $c['MiddlewareDispatcher']); + }); + + + /** + * Middleware + */ + $this['SecurityMiddleware'] = $this->share(function($c){ + return new SecurityMiddleware($c['API'], $c['Request']); + }); + + $this['MiddlewareDispatcher'] = $this->share(function($c){ + $dispatcher = new MiddlewareDispatcher(); + $dispatcher->registerMiddleware($c['SecurityMiddleware']); + + foreach($this->middleWares as $middleWare) { + $dispatcher->registerMiddleware($middleWare); + } + + return $dispatcher; + }); + + + /** + * Utilities + */ + $this['TimeFactory'] = $this->share(function($c){ + return new TimeFactory(); + }); + + + } + + + /** + * @return IApi + */ + function getCoreApi() + { + return $this->query('API'); + } + + /** + * @return \OCP\IServerContainer + */ + function getServer() + { + return $this->query('ServerContainer'); + } + + /** + * @param IMiddleWare $middleWare + * @return boolean + */ + function registerMiddleWare(IMiddleWare $middleWare) { + array_push($this->middleWares, $middleWare); + } + + /** + * used to return the appname of the set application + * @return string the name of your application + */ + function getAppName() { + return $this->query('AppName'); + } +} diff --git a/lib/private/appframework/http/dispatcher.php b/lib/private/appframework/http/dispatcher.php new file mode 100644 index 00000000000..ea57a6860cc --- /dev/null +++ b/lib/private/appframework/http/dispatcher.php @@ -0,0 +1,100 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt, Thomas Tanghus, Bart Visscher + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Http; + +use \OC\AppFramework\Controller\Controller; +use \OC\AppFramework\Middleware\MiddlewareDispatcher; + + +/** + * Class to dispatch the request to the middleware dispatcher + */ +class Dispatcher { + + private $middlewareDispatcher; + private $protocol; + + + /** + * @param Http $protocol the http protocol with contains all status headers + * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which + * runs the middleware + */ + public function __construct(Http $protocol, + MiddlewareDispatcher $middlewareDispatcher) { + $this->protocol = $protocol; + $this->middlewareDispatcher = $middlewareDispatcher; + } + + + /** + * Handles a request and calls the dispatcher on the controller + * @param Controller $controller the controller which will be called + * @param string $methodName the method name which will be called on + * the controller + * @return array $array[0] contains a string with the http main header, + * $array[1] contains headers in the form: $key => value, $array[2] contains + * the response output + */ + public function dispatch(Controller $controller, $methodName) { + $out = array(null, array(), null); + + try { + + $this->middlewareDispatcher->beforeController($controller, + $methodName); + $response = $controller->$methodName(); + + // if an exception appears, the middleware checks if it can handle the + // exception and creates a response. If no response is created, it is + // assumed that theres no middleware who can handle it and the error is + // thrown again + } catch(\Exception $exception){ + $response = $this->middlewareDispatcher->afterException( + $controller, $methodName, $exception); + if (is_null($response)) { + throw $exception; + } + } + + $response = $this->middlewareDispatcher->afterController( + $controller, $methodName, $response); + + // get the output which should be printed and run the after output + // middleware to modify the response + $output = $response->render(); + $out[2] = $this->middlewareDispatcher->beforeOutput( + $controller, $methodName, $output); + + // depending on the cache object the headers need to be changed + $out[0] = $this->protocol->getStatusHeader($response->getStatus(), + $response->getLastModified(), $response->getETag()); + $out[1] = $response->getHeaders(); + + return $out; + } + + +} diff --git a/lib/private/appframework/http/downloadresponse.php b/lib/private/appframework/http/downloadresponse.php new file mode 100644 index 00000000000..67b9542dba6 --- /dev/null +++ b/lib/private/appframework/http/downloadresponse.php @@ -0,0 +1,50 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Http; + + +/** + * Prompts the user to download the a file + */ +class DownloadResponse extends \OCP\AppFramework\Http\Response { + + private $filename; + private $contentType; + + /** + * Creates a response that prompts the user to download the file + * @param string $filename the name that the downloaded file should have + * @param string $contentType the mimetype that the downloaded file should have + */ + public function __construct($filename, $contentType) { + $this->filename = $filename; + $this->contentType = $contentType; + + $this->addHeader('Content-Disposition', 'attachment; filename="' . $filename . '"'); + $this->addHeader('Content-Type', $contentType); + } + + +} diff --git a/lib/private/appframework/http/http.php b/lib/private/appframework/http/http.php new file mode 100644 index 00000000000..e00dc9cdc4a --- /dev/null +++ b/lib/private/appframework/http/http.php @@ -0,0 +1,148 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt, Thomas Tanghus, Bart Visscher + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Http; + + +class Http extends \OCP\AppFramework\Http\Http{ + + private $server; + private $protocolVersion; + protected $headers; + + /** + * @param $_SERVER $server + * @param string $protocolVersion the http version to use defaults to HTTP/1.1 + */ + public function __construct($server, $protocolVersion='HTTP/1.1') { + $this->server = $server; + $this->protocolVersion = $protocolVersion; + + $this->headers = array( + self::STATUS_CONTINUE => 'Continue', + self::STATUS_SWITCHING_PROTOCOLS => 'Switching Protocols', + self::STATUS_PROCESSING => 'Processing', + self::STATUS_OK => 'OK', + self::STATUS_CREATED => 'Created', + self::STATUS_ACCEPTED => 'Accepted', + self::STATUS_NON_AUTHORATIVE_INFORMATION => 'Non-Authorative Information', + self::STATUS_NO_CONTENT => 'No Content', + self::STATUS_RESET_CONTENT => 'Reset Content', + self::STATUS_PARTIAL_CONTENT => 'Partial Content', + self::STATUS_MULTI_STATUS => 'Multi-Status', // RFC 4918 + self::STATUS_ALREADY_REPORTED => 'Already Reported', // RFC 5842 + self::STATUS_IM_USED => 'IM Used', // RFC 3229 + self::STATUS_MULTIPLE_CHOICES => 'Multiple Choices', + self::STATUS_MOVED_PERMANENTLY => 'Moved Permanently', + self::STATUS_FOUND => 'Found', + self::STATUS_SEE_OTHER => 'See Other', + self::STATUS_NOT_MODIFIED => 'Not Modified', + self::STATUS_USE_PROXY => 'Use Proxy', + self::STATUS_RESERVED => 'Reserved', + self::STATUS_TEMPORARY_REDIRECT => 'Temporary Redirect', + self::STATUS_BAD_REQUEST => 'Bad request', + self::STATUS_UNAUTHORIZED => 'Unauthorized', + self::STATUS_PAYMENT_REQUIRED => 'Payment Required', + self::STATUS_FORBIDDEN => 'Forbidden', + self::STATUS_NOT_FOUND => 'Not Found', + self::STATUS_METHOD_NOT_ALLOWED => 'Method Not Allowed', + self::STATUS_NOT_ACCEPTABLE => 'Not Acceptable', + self::STATUS_PROXY_AUTHENTICATION_REQUIRED => 'Proxy Authentication Required', + self::STATUS_REQUEST_TIMEOUT => 'Request Timeout', + self::STATUS_CONFLICT => 'Conflict', + self::STATUS_GONE => 'Gone', + self::STATUS_LENGTH_REQUIRED => 'Length Required', + self::STATUS_PRECONDITION_FAILED => 'Precondition failed', + self::STATUS_REQUEST_ENTITY_TOO_LARGE => 'Request Entity Too Large', + self::STATUS_REQUEST_URI_TOO_LONG => 'Request-URI Too Long', + self::STATUS_UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type', + self::STATUS_REQUEST_RANGE_NOT_SATISFIABLE => 'Requested Range Not Satisfiable', + self::STATUS_EXPECTATION_FAILED => 'Expectation Failed', + self::STATUS_IM_A_TEAPOT => 'I\'m a teapot', // RFC 2324 + self::STATUS_UNPROCESSABLE_ENTITY => 'Unprocessable Entity', // RFC 4918 + self::STATUS_LOCKED => 'Locked', // RFC 4918 + self::STATUS_FAILED_DEPENDENCY => 'Failed Dependency', // RFC 4918 + self::STATUS_UPGRADE_REQUIRED => 'Upgrade required', + self::STATUS_PRECONDITION_REQUIRED => 'Precondition required', // draft-nottingham-http-new-status + self::STATUS_TOO_MANY_REQUESTS => 'Too Many Requests', // draft-nottingham-http-new-status + self::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE => 'Request Header Fields Too Large', // draft-nottingham-http-new-status + self::STATUS_INTERNAL_SERVER_ERROR => 'Internal Server Error', + self::STATUS_NOT_IMPLEMENTED => 'Not Implemented', + self::STATUS_BAD_GATEWAY => 'Bad Gateway', + self::STATUS_SERVICE_UNAVAILABLE => 'Service Unavailable', + self::STATUS_GATEWAY_TIMEOUT => 'Gateway Timeout', + self::STATUS_HTTP_VERSION_NOT_SUPPORTED => 'HTTP Version not supported', + self::STATUS_VARIANT_ALSO_NEGOTIATES => 'Variant Also Negotiates', + self::STATUS_INSUFFICIENT_STORAGE => 'Insufficient Storage', // RFC 4918 + self::STATUS_LOOP_DETECTED => 'Loop Detected', // RFC 5842 + self::STATUS_BANDWIDTH_LIMIT_EXCEEDED => 'Bandwidth Limit Exceeded', // non-standard + self::STATUS_NOT_EXTENDED => 'Not extended', + self::STATUS_NETWORK_AUTHENTICATION_REQUIRED => 'Network Authentication Required', // draft-nottingham-http-new-status + ); + } + + + /** + * Gets the correct header + * @param Http::CONSTANT $status the constant from the Http class + * @param \DateTime $lastModified formatted last modified date + * @param string $Etag the etag + */ + public function getStatusHeader($status, \DateTime $lastModified=null, + $ETag=null) { + + if(!is_null($lastModified)) { + $lastModified = $lastModified->format(\DateTime::RFC2822); + } + + // if etag or lastmodified have not changed, return a not modified + if ((isset($this->server['HTTP_IF_NONE_MATCH']) + && trim($this->server['HTTP_IF_NONE_MATCH']) === $ETag) + + || + + (isset($this->server['HTTP_IF_MODIFIED_SINCE']) + && trim($this->server['HTTP_IF_MODIFIED_SINCE']) === + $lastModified)) { + + $status = self::STATUS_NOT_MODIFIED; + } + + // we have one change currently for the http 1.0 header that differs + // from 1.1: STATUS_TEMPORARY_REDIRECT should be STATUS_FOUND + // if this differs any more, we want to create childclasses for this + if($status === self::STATUS_TEMPORARY_REDIRECT + && $this->protocolVersion === 'HTTP/1.0') { + + $status = self::STATUS_FOUND; + } + + return $this->protocolVersion . ' ' . $status . ' ' . + $this->headers[$status]; + } + + +} + + diff --git a/lib/private/appframework/http/redirectresponse.php b/lib/private/appframework/http/redirectresponse.php new file mode 100644 index 00000000000..688447f1618 --- /dev/null +++ b/lib/private/appframework/http/redirectresponse.php @@ -0,0 +1,56 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Http; + +use OCP\AppFramework\Http\Response; + + +/** + * Redirects to a different URL + */ +class RedirectResponse extends Response { + + private $redirectURL; + + /** + * Creates a response that redirects to a url + * @param string $redirectURL the url to redirect to + */ + public function __construct($redirectURL) { + $this->redirectURL = $redirectURL; + $this->setStatus(Http::STATUS_TEMPORARY_REDIRECT); + $this->addHeader('Location', $redirectURL); + } + + + /** + * @return string the url to redirect + */ + public function getRedirectURL() { + return $this->redirectURL; + } + + +} diff --git a/lib/private/appframework/http/request.php b/lib/private/appframework/http/request.php new file mode 100644 index 00000000000..34605acdfea --- /dev/null +++ b/lib/private/appframework/http/request.php @@ -0,0 +1,307 @@ +<?php +/** + * ownCloud - Request + * + * @author Thomas Tanghus + * @copyright 2013 Thomas Tanghus (thomas@tanghus.net) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\AppFramework\Http; + +use OCP\IRequest; + +/** + * Class for accessing variables in the request. + * This class provides an immutable object with request variables. + */ + +class Request implements \ArrayAccess, \Countable, IRequest { + + protected $items = array(); + protected $allowedKeys = array( + 'get', + 'post', + 'files', + 'server', + 'env', + 'cookies', + 'urlParams', + 'params', + 'parameters', + 'method' + ); + + /** + * @param array $vars An associative array with the following optional values: + * @param array 'params' the parsed json array + * @param array 'urlParams' the parameters which were matched from the URL + * @param array 'get' the $_GET array + * @param array 'post' the $_POST array + * @param array 'files' the $_FILES array + * @param array 'server' the $_SERVER array + * @param array 'env' the $_ENV array + * @param array 'session' the $_SESSION array + * @param array 'cookies' the $_COOKIE array + * @param string 'method' the request method (GET, POST etc) + * @see http://www.php.net/manual/en/reserved.variables.php + */ + public function __construct(array $vars=array()) { + + foreach($this->allowedKeys as $name) { + $this->items[$name] = isset($vars[$name]) + ? $vars[$name] + : array(); + } + + $this->items['parameters'] = array_merge( + $this->items['params'], + $this->items['get'], + $this->items['post'], + $this->items['urlParams'] + ); + + } + + // Countable method. + public function count() { + return count(array_keys($this->items['parameters'])); + } + + /** + * ArrayAccess methods + * + * Gives access to the combined GET, POST and urlParams arrays + * + * Examples: + * + * $var = $request['myvar']; + * + * or + * + * if(!isset($request['myvar']) { + * // Do something + * } + * + * $request['myvar'] = 'something'; // This throws an exception. + * + * @param string $offset The key to lookup + * @return string|null + */ + public function offsetExists($offset) { + return isset($this->items['parameters'][$offset]); + } + + /** + * @see offsetExists + */ + public function offsetGet($offset) { + return isset($this->items['parameters'][$offset]) + ? $this->items['parameters'][$offset] + : null; + } + + /** + * @see offsetExists + */ + public function offsetSet($offset, $value) { + throw new \RuntimeException('You cannot change the contents of the request object'); + } + + /** + * @see offsetExists + */ + public function offsetUnset($offset) { + throw new \RuntimeException('You cannot change the contents of the request object'); + } + + // Magic property accessors + public function __set($name, $value) { + throw new \RuntimeException('You cannot change the contents of the request object'); + } + + /** + * Access request variables by method and name. + * Examples: + * + * $request->post['myvar']; // Only look for POST variables + * $request->myvar; or $request->{'myvar'}; or $request->{$myvar} + * Looks in the combined GET, POST and urlParams array. + * + * if($request->method !== 'POST') { + * throw new Exception('This function can only be invoked using POST'); + * } + * + * @param string $name The key to look for. + * @return mixed|null + */ + public function __get($name) { + switch($name) { + case 'get': + case 'post': + case 'files': + case 'server': + case 'env': + case 'cookies': + case 'parameters': + case 'params': + case 'urlParams': + return isset($this->items[$name]) + ? $this->items[$name] + : null; + break; + case 'method': + return $this->items['method']; + break; + default; + return isset($this[$name]) + ? $this[$name] + : null; + break; + } + } + + + public function __isset($name) { + return isset($this->items['parameters'][$name]); + } + + + public function __unset($id) { + throw new \RunTimeException('You cannot change the contents of the request object'); + } + + /** + * Returns the value for a specific http header. + * + * This method returns null if the header did not exist. + * + * @param string $name + * @return string + */ + public function getHeader($name) { + + $name = strtoupper(str_replace(array('-'),array('_'),$name)); + if (isset($this->server['HTTP_' . $name])) { + return $this->server['HTTP_' . $name]; + } + + // There's a few headers that seem to end up in the top-level + // server array. + switch($name) { + case 'CONTENT_TYPE' : + case 'CONTENT_LENGTH' : + if (isset($this->server[$name])) { + return $this->server[$name]; + } + break; + + } + + return null; + } + + /** + * Lets you access post and get parameters by the index + * In case of json requests the encoded json body is accessed + * + * @param string $key the key which you want to access in the URL Parameter + * placeholder, $_POST or $_GET array. + * The priority how they're returned is the following: + * 1. URL parameters + * 2. POST parameters + * 3. GET parameters + * @param mixed $default If the key is not found, this value will be returned + * @return mixed the content of the array + */ + public function getParam($key, $default = null) { + return isset($this->parameters[$key]) + ? $this->parameters[$key] + : $default; + } + + /** + * Returns all params that were received, be it from the request + * (as GET or POST) or throuh the URL by the route + * @return array the array with all parameters + */ + public function getParams() { + return $this->parameters; + } + + /** + * Returns the method of the request + * @return string the method of the request (POST, GET, etc) + */ + public function getMethod() { + return $this->method; + } + + /** + * Shortcut for accessing an uploaded file through the $_FILES array + * @param string $key the key that will be taken from the $_FILES array + * @return array the file in the $_FILES element + */ + public function getUploadedFile($key) { + return isset($this->files[$key]) ? $this->files[$key] : null; + } + + /** + * Shortcut for getting env variables + * @param string $key the key that will be taken from the $_ENV array + * @return array the value in the $_ENV element + */ + public function getEnv($key) { + return isset($this->env[$key]) ? $this->env[$key] : null; + } + + /** + * Shortcut for getting cookie variables + * @param string $key the key that will be taken from the $_COOKIE array + * @return array the value in the $_COOKIE element + */ + function getCookie($key) { + return isset($this->cookies[$key]) ? $this->cookies[$key] : null; + } + + /** + * Returns the request body content. + * + * @param Boolean $asResource If true, a resource will be returned + * + * @return string|resource The request body content or a resource to read the body stream. + * + * @throws \LogicException + */ + function getContent($asResource = false) { + return null; +// if (false === $this->content || (true === $asResource && null !== $this->content)) { +// throw new \LogicException('getContent() can only be called once when using the resource return type.'); +// } +// +// if (true === $asResource) { +// $this->content = false; +// +// return fopen('php://input', 'rb'); +// } +// +// if (null === $this->content) { +// $this->content = file_get_contents('php://input'); +// } +// +// return $this->content; + } +} diff --git a/lib/private/appframework/middleware/middleware.php b/lib/private/appframework/middleware/middleware.php new file mode 100644 index 00000000000..b12c03c3eb8 --- /dev/null +++ b/lib/private/appframework/middleware/middleware.php @@ -0,0 +1,100 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Middleware; + +use OCP\AppFramework\Http\Response; + + +/** + * Middleware is used to provide hooks before or after controller methods and + * deal with possible exceptions raised in the controller methods. + * They're modeled after Django's middleware system: + * https://docs.djangoproject.com/en/dev/topics/http/middleware/ + */ +abstract class Middleware { + + + /** + * This is being run in normal order before the controller is being + * called which allows several modifications and checks + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + */ + public function beforeController($controller, $methodName){ + + } + + + /** + * This is being run when either the beforeController method or the + * controller method itself is throwing an exception. The middleware is + * asked in reverse order to handle the exception and to return a response. + * If the response is null, it is assumed that the exception could not be + * handled and the error will be thrown again + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param \Exception $exception the thrown exception + * @throws \Exception the passed in exception if it cant handle it + * @return Response a Response object in case that the exception was handled + */ + public function afterException($controller, $methodName, \Exception $exception){ + throw $exception; + } + + + /** + * This is being run after a successful controllermethod call and allows + * the manipulation of a Response object. The middleware is run in reverse order + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param Response $response the generated response from the controller + * @return Response a Response object + */ + public function afterController($controller, $methodName, Response $response){ + return $response; + } + + + /** + * This is being run after the response object has been rendered and + * allows the manipulation of the output. The middleware is run in reverse order + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param string $output the generated output from a response + * @return string the output that should be printed + */ + public function beforeOutput($controller, $methodName, $output){ + return $output; + } + +} diff --git a/lib/private/appframework/middleware/middlewaredispatcher.php b/lib/private/appframework/middleware/middlewaredispatcher.php new file mode 100644 index 00000000000..70ab108e6b8 --- /dev/null +++ b/lib/private/appframework/middleware/middlewaredispatcher.php @@ -0,0 +1,159 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Middleware; + +use OC\AppFramework\Controller\Controller; +use OCP\AppFramework\Http\Response; + + +/** + * This class is used to store and run all the middleware in correct order + */ +class MiddlewareDispatcher { + + /** + * @var array array containing all the middlewares + */ + private $middlewares; + + /** + * @var int counter which tells us what middlware was executed once an + * exception occurs + */ + private $middlewareCounter; + + + /** + * Constructor + */ + public function __construct(){ + $this->middlewares = array(); + $this->middlewareCounter = 0; + } + + + /** + * Adds a new middleware + * @param Middleware $middleware the middleware which will be added + */ + public function registerMiddleware(Middleware $middleWare){ + array_push($this->middlewares, $middleWare); + } + + + /** + * returns an array with all middleware elements + * @return array the middlewares + */ + public function getMiddlewares(){ + return $this->middlewares; + } + + + /** + * This is being run in normal order before the controller is being + * called which allows several modifications and checks + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + */ + public function beforeController(Controller $controller, $methodName){ + // we need to count so that we know which middlewares we have to ask in + // case theres an exception + for($i=0; $i<count($this->middlewares); $i++){ + $this->middlewareCounter++; + $middleware = $this->middlewares[$i]; + $middleware->beforeController($controller, $methodName); + } + } + + + /** + * This is being run when either the beforeController method or the + * controller method itself is throwing an exception. The middleware is asked + * in reverse order to handle the exception and to return a response. + * If the response is null, it is assumed that the exception could not be + * handled and the error will be thrown again + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param \Exception $exception the thrown exception + * @return Response a Response object if the middleware can handle the + * exception + * @throws \Exception the passed in exception if it cant handle it + */ + public function afterException(Controller $controller, $methodName, \Exception $exception){ + for($i=$this->middlewareCounter-1; $i>=0; $i--){ + $middleware = $this->middlewares[$i]; + try { + return $middleware->afterException($controller, $methodName, $exception); + } catch(\Exception $exception){ + continue; + } + } + throw $exception; + } + + + /** + * This is being run after a successful controllermethod call and allows + * the manipulation of a Response object. The middleware is run in reverse order + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param Response $response the generated response from the controller + * @return Response a Response object + */ + public function afterController(Controller $controller, $methodName, Response $response){ + for($i=count($this->middlewares)-1; $i>=0; $i--){ + $middleware = $this->middlewares[$i]; + $response = $middleware->afterController($controller, $methodName, $response); + } + return $response; + } + + + /** + * This is being run after the response object has been rendered and + * allows the manipulation of the output. The middleware is run in reverse order + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param string $output the generated output from a response + * @return string the output that should be printed + */ + public function beforeOutput(Controller $controller, $methodName, $output){ + for($i=count($this->middlewares)-1; $i>=0; $i--){ + $middleware = $this->middlewares[$i]; + $output = $middleware->beforeOutput($controller, $methodName, $output); + } + return $output; + } + +} diff --git a/lib/private/appframework/middleware/security/securityexception.php b/lib/private/appframework/middleware/security/securityexception.php new file mode 100644 index 00000000000..b32a2769ff5 --- /dev/null +++ b/lib/private/appframework/middleware/security/securityexception.php @@ -0,0 +1,41 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Middleware\Security; + + +/** + * Thrown when the security middleware encounters a security problem + */ +class SecurityException extends \Exception { + + /** + * @param string $msg the security error message + * @param bool $ajax true if it resulted because of an ajax request + */ + public function __construct($msg, $code = 0) { + parent::__construct($msg, $code); + } + +} diff --git a/lib/private/appframework/middleware/security/securitymiddleware.php b/lib/private/appframework/middleware/security/securitymiddleware.php new file mode 100644 index 00000000000..4f1447e1afb --- /dev/null +++ b/lib/private/appframework/middleware/security/securitymiddleware.php @@ -0,0 +1,136 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Controller\Controller; +use OC\AppFramework\Http\Http; +use OC\AppFramework\Http\Request; +use OC\AppFramework\Http\RedirectResponse; +use OC\AppFramework\Utility\MethodAnnotationReader; +use OC\AppFramework\Middleware\Middleware; +use OC\AppFramework\Core\API; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\JSONResponse; + + +/** + * Used to do all the authentication and checking stuff for a controller method + * It reads out the annotations of a controller method and checks which if + * security things should be checked and also handles errors in case a security + * check fails + */ +class SecurityMiddleware extends Middleware { + + private $api; + + /** + * @var \OC\AppFramework\Http\Request + */ + private $request; + + /** + * @param API $api an instance of the api + */ + public function __construct(API $api, Request $request){ + $this->api = $api; + $this->request = $request; + } + + + /** + * This runs all the security checks before a method call. The + * security checks are determined by inspecting the controller method + * annotations + * @param string/Controller $controller the controllername or string + * @param string $methodName the name of the method + * @throws SecurityException when a security check fails + */ + public function beforeController($controller, $methodName){ + + // get annotations from comments + $annotationReader = new MethodAnnotationReader($controller, $methodName); + + // this will set the current navigation entry of the app, use this only + // for normal HTML requests and not for AJAX requests + $this->api->activateNavigationEntry(); + + // security checks + $isPublicPage = $annotationReader->hasAnnotation('PublicPage'); + if(!$isPublicPage) { + if(!$this->api->isLoggedIn()) { + throw new SecurityException('Current user is not logged in', Http::STATUS_UNAUTHORIZED); + } + + if(!$annotationReader->hasAnnotation('NoAdminRequired')) { + if(!$this->api->isAdminUser($this->api->getUserId())) { + throw new SecurityException('Logged in user must be an admin', Http::STATUS_FORBIDDEN); + } + } + } + + if(!$annotationReader->hasAnnotation('NoCSRFRequired')) { + if(!$this->api->passesCSRFCheck()) { + throw new SecurityException('CSRF check failed', Http::STATUS_PRECONDITION_FAILED); + } + } + + } + + + /** + * If an SecurityException is being caught, ajax requests return a JSON error + * response and non ajax requests redirect to the index + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param \Exception $exception the thrown exception + * @throws \Exception the passed in exception if it cant handle it + * @return Response a Response object or null in case that the exception could not be handled + */ + public function afterException($controller, $methodName, \Exception $exception){ + if($exception instanceof SecurityException){ + + if (stripos($this->request->getHeader('Accept'),'html')===false) { + + $response = new JSONResponse( + array('message' => $exception->getMessage()), + $exception->getCode() + ); + $this->api->log($exception->getMessage(), 'debug'); + } else { + + $url = $this->api->linkToAbsolute('index.php', ''); // TODO: replace with link to route + $response = new RedirectResponse($url); + $this->api->log($exception->getMessage(), 'debug'); + } + + return $response; + + } + + throw $exception; + } + +} diff --git a/lib/private/appframework/routing/routeactionhandler.php b/lib/private/appframework/routing/routeactionhandler.php new file mode 100644 index 00000000000..7fb56f14eab --- /dev/null +++ b/lib/private/appframework/routing/routeactionhandler.php @@ -0,0 +1,42 @@ +<?php +/** + * ownCloud - App Framework + * + * @author Thomas Müller + * @copyright 2013 Thomas Müller thomas.mueller@tmit.eu + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\AppFramework\routing; + +use \OC\AppFramework\App; +use \OC\AppFramework\DependencyInjection\DIContainer; + +class RouteActionHandler { + private $controllerName; + private $actionName; + private $container; + + public function __construct(DIContainer $container, $controllerName, $actionName) { + $this->controllerName = $controllerName; + $this->actionName = $actionName; + $this->container = $container; + } + + public function __invoke($params) { + App::main($this->controllerName, $this->actionName, $params, $this->container); + } +} diff --git a/lib/private/appframework/routing/routeconfig.php b/lib/private/appframework/routing/routeconfig.php new file mode 100644 index 00000000000..53ab11bf2f5 --- /dev/null +++ b/lib/private/appframework/routing/routeconfig.php @@ -0,0 +1,186 @@ +<?php +/** + * ownCloud - App Framework + * + * @author Thomas Müller + * @copyright 2013 Thomas Müller thomas.mueller@tmit.eu + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\AppFramework\routing; + +use OC\AppFramework\DependencyInjection\DIContainer; + +/** + * Class RouteConfig + * @package OC\AppFramework\routing + */ +class RouteConfig { + private $container; + private $router; + private $routes; + private $appName; + + /** + * @param \OC\AppFramework\DependencyInjection\DIContainer $container + * @param \OC_Router $router + * @param string $pathToYml + * @internal param $appName + */ + public function __construct(DIContainer $container, \OC_Router $router, $routes) { + $this->routes = $routes; + $this->container = $container; + $this->router = $router; + $this->appName = $container['AppName']; + } + + /** + * The routes and resource will be registered to the \OC_Router + */ + public function register() { + + // parse simple + $this->processSimpleRoutes($this->routes); + + // parse resources + $this->processResources($this->routes); + } + + /** + * Creates one route base on the give configuration + * @param $routes + * @throws \UnexpectedValueException + */ + private function processSimpleRoutes($routes) + { + $simpleRoutes = isset($routes['routes']) ? $routes['routes'] : array(); + foreach ($simpleRoutes as $simpleRoute) { + $name = $simpleRoute['name']; + $url = $simpleRoute['url']; + $verb = isset($simpleRoute['verb']) ? strtoupper($simpleRoute['verb']) : 'GET'; + + $split = explode('#', $name, 2); + if (count($split) != 2) { + throw new \UnexpectedValueException('Invalid route name'); + } + $controller = $split[0]; + $action = $split[1]; + + $controllerName = $this->buildControllerName($controller); + $actionName = $this->buildActionName($action); + + // register the route + $handler = new RouteActionHandler($this->container, $controllerName, $actionName); + $this->router->create($this->appName.'.'.$controller.'.'.$action, $url)->method($verb)->action($handler); + } + } + + /** + * For a given name and url restful routes are created: + * - index + * - show + * - new + * - create + * - update + * - destroy + * + * @param $routes + */ + private function processResources($routes) + { + // declaration of all restful actions + $actions = array( + array('name' => 'index', 'verb' => 'GET', 'on-collection' => true), + array('name' => 'show', 'verb' => 'GET'), + array('name' => 'create', 'verb' => 'POST', 'on-collection' => true), + array('name' => 'update', 'verb' => 'PUT'), + array('name' => 'destroy', 'verb' => 'DELETE'), + ); + + $resources = isset($routes['resources']) ? $routes['resources'] : array(); + foreach ($resources as $resource => $config) { + + // the url parameter used as id to the resource + $resourceId = $this->buildResourceId($resource); + foreach($actions as $action) { + $url = $config['url']; + $method = $action['name']; + $verb = isset($action['verb']) ? strtoupper($action['verb']) : 'GET'; + $collectionAction = isset($action['on-collection']) ? $action['on-collection'] : false; + if (!$collectionAction) { + $url = $url . '/' . $resourceId; + } + if (isset($action['url-postfix'])) { + $url = $url . '/' . $action['url-postfix']; + } + + $controller = $resource; + + $controllerName = $this->buildControllerName($controller); + $actionName = $this->buildActionName($method); + + $routeName = $this->appName . '.' . strtolower($resource) . '.' . strtolower($method); + + $this->router->create($routeName, $url)->method($verb)->action( + new RouteActionHandler($this->container, $controllerName, $actionName) + ); + } + } + } + + /** + * Based on a given route name the controller name is generated + * @param $controller + * @return string + */ + private function buildControllerName($controller) + { + return $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; + } + + /** + * Based on the action part of the route name the controller method name is generated + * @param $action + * @return string + */ + private function buildActionName($action) { + return $this->underScoreToCamelCase($action); + } + + /** + * Generates the id used in the url part o the route url + * @param $resource + * @return string + */ + private function buildResourceId($resource) { + return '{'.$this->underScoreToCamelCase(rtrim($resource, 's')).'Id}'; + } + + /** + * Underscored strings are converted to camel case strings + * @param $str string + * @return string + */ + private function underScoreToCamelCase($str) { + $pattern = "/_[a-z]?/"; + return preg_replace_callback( + $pattern, + function ($matches) { + return strtoupper(ltrim($matches[0], "_")); + }, + $str); + } +} diff --git a/lib/private/appframework/utility/methodannotationreader.php b/lib/private/appframework/utility/methodannotationreader.php new file mode 100644 index 00000000000..42060a08529 --- /dev/null +++ b/lib/private/appframework/utility/methodannotationreader.php @@ -0,0 +1,61 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Utility; + + +/** + * Reads and parses annotations from doc comments + */ +class MethodAnnotationReader { + + private $annotations; + + /** + * @param object $object an object or classname + * @param string $method the method which we want to inspect for annotations + */ + public function __construct($object, $method){ + $this->annotations = array(); + + $reflection = new \ReflectionMethod($object, $method); + $docs = $reflection->getDocComment(); + + // extract everything prefixed by @ and first letter uppercase + preg_match_all('/@([A-Z]\w+)/', $docs, $matches); + $this->annotations = $matches[1]; + } + + + /** + * Check if a method contains an annotation + * @param string $name the name of the annotation + * @return bool true if the annotation is found + */ + public function hasAnnotation($name){ + return in_array($name, $this->annotations); + } + + +} diff --git a/lib/private/appframework/utility/simplecontainer.php b/lib/private/appframework/utility/simplecontainer.php new file mode 100644 index 00000000000..7e4db63bde5 --- /dev/null +++ b/lib/private/appframework/utility/simplecontainer.php @@ -0,0 +1,44 @@ +<?php + +namespace OC\AppFramework\Utility; + +// register 3rdparty autoloaders +require_once __DIR__ . '/../../../../3rdparty/Pimple/Pimple.php'; + +/** + * Class SimpleContainer + * + * SimpleContainer is a simple implementation of IContainer on basis of \Pimple + */ +class SimpleContainer extends \Pimple implements \OCP\IContainer { + + /** + * @param string $name name of the service to query for + * @return object registered service for the given $name + */ + public function query($name) { + return $this->offsetGet($name); + } + + function registerParameter($name, $value) + { + $this[$name] = $value; + } + + /** + * The given closure is call the first time the given service is queried. + * The closure has to return the instance for the given service. + * Created instance will be cached in case $shared is true. + * + * @param string $name name of the service to register another backend for + * @param callable $closure the closure to be called on service creation + */ + function registerService($name, \Closure $closure, $shared = true) + { + if ($shared) { + $this[$name] = \Pimple::share($closure); + } else { + $this[$name] = $closure; + } + } +} diff --git a/lib/private/appframework/utility/timefactory.php b/lib/private/appframework/utility/timefactory.php new file mode 100644 index 00000000000..2c3dd6cf5e3 --- /dev/null +++ b/lib/private/appframework/utility/timefactory.php @@ -0,0 +1,42 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Utility; + + +/** + * Needed to mock calls to time() + */ +class TimeFactory { + + + /** + * @return int the result of a call to time() + */ + public function getTime() { + return time(); + } + + +} diff --git a/lib/private/archive.php b/lib/private/archive.php new file mode 100644 index 00000000000..85bfae57295 --- /dev/null +++ b/lib/private/archive.php @@ -0,0 +1,137 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +abstract class OC_Archive{ + /** + * open any of the supported archive types + * @param string path + * @return OC_Archive + */ + public static function open($path) { + $ext=substr($path, strrpos($path, '.')); + switch($ext) { + case '.zip': + return new OC_Archive_ZIP($path); + case '.gz': + case '.bz': + case '.bz2': + if(strpos($path, '.tar.')) { + return new OC_Archive_TAR($path); + } + break; + case '.tgz': + return new OC_Archive_TAR($path); + } + } + + 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 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 path + * @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 bool + */ + 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/archive/tar.php b/lib/private/archive/tar.php new file mode 100644 index 00000000000..a1c0535b1c3 --- /dev/null +++ b/lib/private/archive/tar.php @@ -0,0 +1,339 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once OC::$THIRDPARTYROOT . '/3rdparty/Archive/Tar.php'; + +class OC_Archive_TAR extends OC_Archive{ + const PLAIN=0; + const GZIP=1; + const BZIP=2; + + private $fileList; + private $cachedHeaders; + + /** + * @var Archive_Tar tar + */ + private $tar=null; + private $path; + + function __construct($source) { + $types=array(null, 'gz', 'bz'); + $this->path=$source; + $this->tar=new Archive_Tar($source, $types[self::getTarType($source)]); + } + + /** + * try to detect the type of tar compression + * @param string file + * @return str + */ + static public function getTarType($file) { + if(strpos($file, '.')) { + $extension=substr($file, strrpos($file, '.')); + switch($extension) { + case 'gz': + case 'tgz': + return self::GZIP; + case 'bz': + case 'bz2': + return self::BZIP; + default: + return self::PLAIN; + } + }else{ + return self::PLAIN; + } + } + + /** + * add an empty folder to the archive + * @param string path + * @return bool + */ + function addFolder($path) { + $tmpBase=OC_Helper::tmpFolder(); + if(substr($path, -1, 1)!='/') { + $path.='/'; + } + if($this->fileExists($path)) { + return false; + } + $parts=explode('/', $path); + $folder=$tmpBase; + foreach($parts as $part) { + $folder.='/'.$part; + if(!is_dir($folder)) { + mkdir($folder); + } + } + $result=$this->tar->addModify(array($tmpBase.$path), '', $tmpBase); + rmdir($tmpBase.$path); + $this->fileList=false; + $this->cachedHeaders=false; + return $result; + } + /** + * add a file to the archive + * @param string path + * @param string source either a local file or string data + * @return bool + */ + function addFile($path, $source='') { + if($this->fileExists($path)) { + $this->remove($path); + } + if($source and $source[0]=='/' and file_exists($source)) { + $header=array(); + $dummy=''; + $this->tar->_openAppend(); + $result=$this->tar->_addfile($source, $header, $dummy, $dummy, $path); + }else{ + $result=$this->tar->addString($path, $source); + } + $this->fileList=false; + $this->cachedHeaders=false; + return $result; + } + + /** + * rename a file or folder in the archive + * @param string source + * @param string dest + * @return bool + */ + function rename($source, $dest) { + //no proper way to delete, rename entire archive, rename file and remake archive + $tmp=OCP\Files::tmpFolder(); + $this->tar->extract($tmp); + rename($tmp.$source, $tmp.$dest); + $this->tar=null; + unlink($this->path); + $types=array(null, 'gz', 'bz'); + $this->tar=new Archive_Tar($this->path, $types[self::getTarType($this->path)]); + $this->tar->createModify(array($tmp), '', $tmp.'/'); + $this->fileList=false; + $this->cachedHeaders=false; + return true; + } + + private function getHeader($file) { + if ( ! $this->cachedHeaders ) { + $this->cachedHeaders = $this->tar->listContent(); + } + foreach($this->cachedHeaders as $header) { + if( $file == $header['filename'] + or $file.'/' == $header['filename'] + or '/'.$file.'/' == $header['filename'] + or '/'.$file == $header['filename']) { + return $header; + } + } + return null; + } + + /** + * get the uncompressed size of a file in the archive + * @param string path + * @return int + */ + function filesize($path) { + $stat=$this->getHeader($path); + return $stat['size']; + } + /** + * get the last modified time of a file in the archive + * @param string path + * @return int + */ + function mtime($path) { + $stat=$this->getHeader($path); + return $stat['mtime']; + } + + /** + * get the files in a folder + * @param path + * @return array + */ + function getFolder($path) { + $files=$this->getFiles(); + $folderContent=array(); + $pathLength=strlen($path); + foreach($files as $file) { + if($file[0]=='/') { + $file=substr($file, 1); + } + if(substr($file, 0, $pathLength)==$path and $file!=$path) { + $result=substr($file, $pathLength); + if($pos=strpos($result, '/')) { + $result=substr($result, 0, $pos+1); + } + if(array_search($result, $folderContent)===false) { + $folderContent[]=$result; + } + } + } + return $folderContent; + } + /** + * get all files in the archive + * @return array + */ + function getFiles() { + if($this->fileList) { + return $this->fileList; + } + if ( ! $this->cachedHeaders ) { + $this->cachedHeaders = $this->tar->listContent(); + } + $files=array(); + foreach($this->cachedHeaders as $header) { + $files[]=$header['filename']; + } + $this->fileList=$files; + return $files; + } + /** + * get the content of a file + * @param string path + * @return string + */ + function getFile($path) { + return $this->tar->extractInString($path); + } + /** + * extract a single file from the archive + * @param string path + * @param string dest + * @return bool + */ + function extractFile($path, $dest) { + $tmp=OCP\Files::tmpFolder(); + if(!$this->fileExists($path)) { + return false; + } + if($this->fileExists('/'.$path)) { + $success=$this->tar->extractList(array('/'.$path), $tmp); + }else{ + $success=$this->tar->extractList(array($path), $tmp); + } + if($success) { + rename($tmp.$path, $dest); + } + OCP\Files::rmdirr($tmp); + return $success; + } + /** + * extract the archive + * @param string path + * @param string dest + * @return bool + */ + function extract($dest) { + return $this->tar->extract($dest); + } + /** + * check if a file or folder exists in the archive + * @param string path + * @return bool + */ + function fileExists($path) { + $files=$this->getFiles(); + if((array_search($path, $files)!==false) or (array_search($path.'/', $files)!==false)) { + return true; + }else{ + $folderPath=$path; + if(substr($folderPath, -1, 1)!='/') { + $folderPath.='/'; + } + $pathLength=strlen($folderPath); + foreach($files as $file) { + if(strlen($file)>$pathLength and substr($file, 0, $pathLength)==$folderPath) { + return true; + } + } + } + if($path[0]!='/') {//not all programs agree on the use of a leading / + return $this->fileExists('/'.$path); + }else{ + return false; + } + } + + /** + * remove a file or folder from the archive + * @param string path + * @return bool + */ + function remove($path) { + if(!$this->fileExists($path)) { + return false; + } + $this->fileList=false; + $this->cachedHeaders=false; + //no proper way to delete, extract entire archive, delete file and remake archive + $tmp=OCP\Files::tmpFolder(); + $this->tar->extract($tmp); + OCP\Files::rmdirr($tmp.$path); + $this->tar=null; + unlink($this->path); + $this->reopen(); + $this->tar->createModify(array($tmp), '', $tmp); + return true; + } + /** + * get a file handler + * @param string path + * @param string mode + * @return resource + */ + function getStream($path, $mode) { + if(strrpos($path, '.')!==false) { + $ext=substr($path, strrpos($path, '.')); + }else{ + $ext=''; + } + $tmpFile=OCP\Files::tmpFile($ext); + if($this->fileExists($path)) { + $this->extractFile($path, $tmpFile); + }elseif($mode=='r' or $mode=='rb') { + return false; + } + if($mode=='r' or $mode=='rb') { + return fopen($tmpFile, $mode); + }else{ + \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); + self::$tempFiles[$tmpFile]=$path; + return fopen('close://'.$tmpFile, $mode); + } + } + + private static $tempFiles=array(); + /** + * write back temporary files + */ + function writeBack($tmpFile) { + if(isset(self::$tempFiles[$tmpFile])) { + $this->addFile(self::$tempFiles[$tmpFile], $tmpFile); + unlink($tmpFile); + } + } + + /** + * reopen the archive to ensure everything is written + */ + private function reopen() { + if($this->tar) { + $this->tar->_close(); + $this->tar=null; + } + $types=array(null, 'gz', 'bz'); + $this->tar=new Archive_Tar($this->path, $types[self::getTarType($this->path)]); + } +} diff --git a/lib/private/archive/zip.php b/lib/private/archive/zip.php new file mode 100644 index 00000000000..8a866716a79 --- /dev/null +++ b/lib/private/archive/zip.php @@ -0,0 +1,201 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Archive_ZIP extends OC_Archive{ + /** + * @var ZipArchive zip + */ + private $zip=null; + private $path; + + function __construct($source) { + $this->path=$source; + $this->zip=new ZipArchive(); + if($this->zip->open($source, ZipArchive::CREATE)) { + }else{ + OCP\Util::writeLog('files_archive', 'Error while opening archive '.$source, OCP\Util::WARN); + } + } + /** + * add an empty folder to the archive + * @param string path + * @return bool + */ + function addFolder($path) { + return $this->zip->addEmptyDir($path); + } + /** + * add a file to the archive + * @param string path + * @param string source either a local file or string data + * @return bool + */ + function addFile($path, $source='') { + if($source and $source[0]=='/' and file_exists($source)) { + $result=$this->zip->addFile($source, $path); + }else{ + $result=$this->zip->addFromString($path, $source); + } + if($result) { + $this->zip->close();//close and reopen to save the zip + $this->zip->open($this->path); + } + return $result; + } + /** + * rename a file or folder in the archive + * @param string source + * @param string dest + * @return bool + */ + function rename($source, $dest) { + $source=$this->stripPath($source); + $dest=$this->stripPath($dest); + $this->zip->renameName($source, $dest); + } + /** + * get the uncompressed size of a file in the archive + * @param string path + * @return int + */ + function filesize($path) { + $stat=$this->zip->statName($path); + return $stat['size']; + } + /** + * get the last modified time of a file in the archive + * @param string path + * @return int + */ + function mtime($path) { + return filemtime($this->path); + } + /** + * get the files in a folder + * @param path + * @return array + */ + function getFolder($path) { + $files=$this->getFiles(); + $folderContent=array(); + $pathLength=strlen($path); + foreach($files as $file) { + if(substr($file, 0, $pathLength)==$path and $file!=$path) { + if(strrpos(substr($file, 0, -1), '/')<=$pathLength) { + $folderContent[]=substr($file, $pathLength); + } + } + } + return $folderContent; + } + /** + * get all files in the archive + * @return array + */ + function getFiles() { + $fileCount=$this->zip->numFiles; + $files=array(); + for($i=0;$i<$fileCount;$i++) { + $files[]=$this->zip->getNameIndex($i); + } + return $files; + } + /** + * get the content of a file + * @param string path + * @return string + */ + function getFile($path) { + return $this->zip->getFromName($path); + } + /** + * extract a single file from the archive + * @param string path + * @param string dest + * @return bool + */ + function extractFile($path, $dest) { + $fp = $this->zip->getStream($path); + file_put_contents($dest, $fp); + } + /** + * extract the archive + * @param string path + * @param string dest + * @return bool + */ + function extract($dest) { + return $this->zip->extractTo($dest); + } + /** + * check if a file or folder exists in the archive + * @param string path + * @return bool + */ + function fileExists($path) { + return ($this->zip->locateName($path)!==false) or ($this->zip->locateName($path.'/')!==false); + } + /** + * remove a file or folder from the archive + * @param string path + * @return bool + */ + function remove($path) { + if($this->fileExists($path.'/')) { + return $this->zip->deleteName($path.'/'); + }else{ + return $this->zip->deleteName($path); + } + } + /** + * get a file handler + * @param string path + * @param string mode + * @return resource + */ + function getStream($path, $mode) { + if($mode=='r' or $mode=='rb') { + return $this->zip->getStream($path); + } else { + //since we cant directly get a writable stream, + //make a temp copy of the file and put it back + //in the archive when the stream is closed + if(strrpos($path, '.')!==false) { + $ext=substr($path, strrpos($path, '.')); + }else{ + $ext=''; + } + $tmpFile=OCP\Files::tmpFile($ext); + \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); + if($this->fileExists($path)) { + $this->extractFile($path, $tmpFile); + } + self::$tempFiles[$tmpFile]=$path; + return fopen('close://'.$tmpFile, $mode); + } + } + + private static $tempFiles=array(); + /** + * write back temporary files + */ + function writeBack($tmpFile) { + if(isset(self::$tempFiles[$tmpFile])) { + $this->addFile(self::$tempFiles[$tmpFile], $tmpFile); + unlink($tmpFile); + } + } + + private function stripPath($path) { + if(!$path || $path[0]=='/') { + return substr($path, 1); + }else{ + return $path; + } + } +} diff --git a/lib/private/arrayparser.php b/lib/private/arrayparser.php new file mode 100644 index 00000000000..3bb394a5163 --- /dev/null +++ b/lib/private/arrayparser.php @@ -0,0 +1,189 @@ +<?php + +/** + * @author Robin Appelman + * @copyright 2013 Robin Appelman icewind@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC; + +class SyntaxException extends \Exception { +} + +class ArrayParser { + const TYPE_NUM = 1; + const TYPE_BOOL = 2; + const TYPE_STRING = 3; + const TYPE_ARRAY = 4; + + function parsePHP($string) { + $string = $this->stripPHPTags($string); + $string = $this->stripAssignAndReturn($string); + return $this->parse($string); + } + + function stripPHPTags($string) { + $string = trim($string); + if (substr($string, 0, 5) === '<?php') { + $string = substr($string, 5); + } + if (substr($string, -2) === '?>') { + $string = substr($string, 0, -2); + } + return $string; + } + + function stripAssignAndReturn($string) { + $string = trim($string); + if (substr($string, 0, 6) === 'return') { + $string = substr($string, 6); + } + if (substr($string, 0, 1) === '$') { + list(, $string) = explode('=', $string, 2); + } + return $string; + } + + function parse($string) { + $string = trim($string); + $string = trim($string, ';'); + switch ($this->getType($string)) { + case self::TYPE_NUM: + return $this->parseNum($string); + case self::TYPE_BOOL: + return $this->parseBool($string); + case self::TYPE_STRING: + return $this->parseString($string); + case self::TYPE_ARRAY: + return $this->parseArray($string); + } + return null; + } + + function getType($string) { + $string = strtolower($string); + $first = substr($string, 0, 1); + $last = substr($string, -1, 1); + $arrayFirst = substr($string, 0, 5); + if (($first === '"' or $first === "'") and ($last === '"' or $last === "'")) { + return self::TYPE_STRING; + } elseif ($string === 'false' or $string === 'true') { + return self::TYPE_BOOL; + } elseif ($arrayFirst === 'array' and $last === ')') { + return self::TYPE_ARRAY; + } else { + return self::TYPE_NUM; + } + } + + function parseString($string) { + return substr($string, 1, -1); + } + + function parseNum($string) { + return intval($string); + } + + function parseBool($string) { + $string = strtolower($string); + return $string === 'true'; + } + + function parseArray($string) { + $body = substr($string, 5); + $body = trim($body); + $body = substr($body, 1, -1); + $items = $this->splitArray($body); + $result = array(); + $lastKey = -1; + foreach ($items as $item) { + $item = trim($item); + if ($item) { + if (strpos($item, '=>')) { + list($key, $value) = explode('=>', $item, 2); + $key = $this->parse($key); + $value = $this->parse($value); + } else { + $key = ++$lastKey; + $value = $item; + } + + if (is_numeric($key)) { + $lastKey = $key; + } + $result[$key] = $value; + } + } + return $result; + } + + function splitArray($body) { + $inSingleQuote = false;//keep track if we are inside quotes + $inDoubleQuote = false; + $bracketDepth = 0;//keep track if we are inside brackets + $parts = array(); + $start = 0; + $escaped = false;//keep track if we are after an escape character + $skips = array();//keep track of the escape characters we need to remove from the result + if (substr($body, -1, 1) !== ',') { + $body .= ','; + } + for ($i = 0; $i < strlen($body); $i++) { + $char = substr($body, $i, 1); + if ($char === '\\') { + if ($escaped) { + array_unshift($skips, $i - 1); + } + $escaped = !$escaped; + } else { + if ($char === '"' and !$inSingleQuote) { + if ($escaped) { + array_unshift($skips, $i - 1); + } else { + $inDoubleQuote = !$inDoubleQuote; + } + } elseif ($char === "'" and !$inDoubleQuote) { + if ($escaped) { + array_unshift($skips, $i - 1); + } else { + $inSingleQuote = !$inSingleQuote; + } + } elseif (!$inDoubleQuote and !$inSingleQuote) { + if ($char === '(') { + $bracketDepth++; + } elseif ($char === ')') { + if ($bracketDepth <= 0) { + throw new SyntaxException; + } else { + $bracketDepth--; + } + } elseif ($bracketDepth === 0 and $char === ',') { + $part = substr($body, $start, $i - $start); + foreach ($skips as $skip) { + $part = substr($part, 0, $skip - $start) . substr($part, $skip - $start + 1); + } + $parts[] = $part; + $start = $i + 1; + $skips = array(); + } + } + $escaped = false; + } + } + return $parts; + } +} diff --git a/lib/private/avatar.php b/lib/private/avatar.php new file mode 100644 index 00000000000..f20980c364b --- /dev/null +++ b/lib/private/avatar.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright (c) 2013 Christopher Schäpers <christopher@schaepers.it> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * This class gets and sets users avatars. + */ + +class OC_Avatar { + + private $view; + + /** + * @brief constructor + * @param $user string user to do avatar-management with + */ + public function __construct ($user) { + $this->view = new \OC\Files\View('/'.$user); + } + + /** + * @brief get the users avatar + * @param $size integer size in px of the avatar, defaults to 64 + * @return boolean|\OC_Image containing the avatar or false if there's no image + */ + public function get ($size = 64) { + if ($this->view->file_exists('avatar.jpg')) { + $ext = 'jpg'; + } elseif ($this->view->file_exists('avatar.png')) { + $ext = 'png'; + } else { + return false; + } + + $avatar = new OC_Image(); + $avatar->loadFromData($this->view->file_get_contents('avatar.'.$ext)); + $avatar->resize($size); + return $avatar; + } + + /** + * @brief sets the users avatar + * @param $data mixed imagedata or path to set a new avatar + * @throws Exception if the provided file is not a jpg or png image + * @throws Exception if the provided image is not valid + * @throws \OC\NotSquareException if the image is not square + * @return void + */ + public function set ($data) { + if (\OC_App::isEnabled('files_encryption')) { + $l = \OC_L10N::get('lib'); + throw new \Exception($l->t("Custom profile pictures don't work with encryption yet")); + } + + $img = new OC_Image($data); + $type = substr($img->mimeType(), -3); + if ($type === 'peg') { $type = 'jpg'; } + if ($type !== 'jpg' && $type !== 'png') { + $l = \OC_L10N::get('lib'); + throw new \Exception($l->t("Unknown filetype")); + } + + if (!$img->valid()) { + $l = \OC_L10N::get('lib'); + throw new \Exception($l->t("Invalid image")); + } + + if (!($img->height() === $img->width())) { + throw new \OC\NotSquareException(); + } + + $this->view->unlink('avatar.jpg'); + $this->view->unlink('avatar.png'); + $this->view->file_put_contents('avatar.'.$type, $data); + } + + /** + * @brief remove the users avatar + * @return void + */ + public function remove () { + $this->view->unlink('avatar.jpg'); + $this->view->unlink('avatar.png'); + } +} diff --git a/lib/private/backgroundjob.php b/lib/private/backgroundjob.php new file mode 100644 index 00000000000..9619dcb732c --- /dev/null +++ b/lib/private/backgroundjob.php @@ -0,0 +1,52 @@ +<?php +/** +* ownCloud +* +* @author Jakob Sack +* @copyright 2012 Jakob Sack owncloud@jakobsack.de +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +/** + * This class does the dirty work. + */ +class OC_BackgroundJob{ + /** + * @brief get the execution type of background jobs + * @return string + * + * This method returns the type how background jobs are executed. If the user + * did not select something, the type is ajax. + */ + public static function getExecutionType() { + return OC_Appconfig::getValue( 'core', 'backgroundjobs_mode', 'ajax' ); + } + + /** + * @brief sets the background jobs execution type + * @param $type execution type + * @return boolean + * + * This method sets the execution type of the background jobs. Possible types + * are "none", "ajax", "webcron", "cron" + */ + public static function setExecutionType( $type ) { + if( !in_array( $type, array('none', 'ajax', 'webcron', 'cron'))) { + return false; + } + return OC_Appconfig::setValue( 'core', 'backgroundjobs_mode', $type ); + } +} diff --git a/lib/private/backgroundjob/job.php b/lib/private/backgroundjob/job.php new file mode 100644 index 00000000000..49fbffbd684 --- /dev/null +++ b/lib/private/backgroundjob/job.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\BackgroundJob; + +abstract class Job { + protected $id; + protected $lastRun; + protected $argument; + + /** + * @param JobList $jobList + */ + public function execute($jobList) { + $jobList->setLastRun($this); + $this->run($this->argument); + } + + abstract protected function run($argument); + + public function setId($id) { + $this->id = $id; + } + + public function setLastRun($lastRun) { + $this->lastRun = $lastRun; + } + + public function setArgument($argument) { + $this->argument = $argument; + } + + public function getId() { + return $this->id; + } + + public function getLastRun() { + return $this->lastRun; + } + + public function getArgument() { + return $this->argument; + } +} diff --git a/lib/private/backgroundjob/joblist.php b/lib/private/backgroundjob/joblist.php new file mode 100644 index 00000000000..cc803dd9b5f --- /dev/null +++ b/lib/private/backgroundjob/joblist.php @@ -0,0 +1,172 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\BackgroundJob; + +/** + * Class QueuedJob + * + * create a background job that is to be executed once + * + * @package OC\BackgroundJob + */ +class JobList { + /** + * @param Job|string $job + * @param mixed $argument + */ + public function add($job, $argument = null) { + if (!$this->has($job, $argument)) { + if ($job instanceof Job) { + $class = get_class($job); + } else { + $class = $job; + } + $argument = json_encode($argument); + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*jobs`(`class`, `argument`, `last_run`) VALUES(?, ?, 0)'); + $query->execute(array($class, $argument)); + } + } + + /** + * @param Job|string $job + * @param mixed $argument + */ + public function remove($job, $argument = null) { + if ($job instanceof Job) { + $class = get_class($job); + } else { + $class = $job; + } + if (!is_null($argument)) { + $argument = json_encode($argument); + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*jobs` WHERE `class` = ? AND `argument` = ?'); + $query->execute(array($class, $argument)); + } else { + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*jobs` WHERE `class` = ?'); + $query->execute(array($class)); + } + } + + /** + * check if a job is in the list + * + * @param $job + * @param mixed $argument + * @return bool + */ + public function has($job, $argument) { + if ($job instanceof Job) { + $class = get_class($job); + } else { + $class = $job; + } + $argument = json_encode($argument); + $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*jobs` WHERE `class` = ? AND `argument` = ?'); + $result = $query->execute(array($class, $argument)); + return (bool)$result->fetchRow(); + } + + /** + * get all jobs in the list + * + * @return Job[] + */ + public function getAll() { + $query = \OC_DB::prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs`'); + $result = $query->execute(); + $jobs = array(); + while ($row = $result->fetchRow()) { + $jobs[] = $this->buildJob($row); + } + return $jobs; + } + + /** + * get the next job in the list + * + * @return Job + */ + public function getNext() { + $lastId = $this->getLastJob(); + $query = \OC_DB::prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` WHERE `id` > ? ORDER BY `id` ASC', 1); + $result = $query->execute(array($lastId)); + if ($row = $result->fetchRow()) { + return $this->buildJob($row); + } else { + //begin at the start of the queue + $query = \OC_DB::prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` ORDER BY `id` ASC', 1); + $result = $query->execute(); + if ($row = $result->fetchRow()) { + return $this->buildJob($row); + } else { + return null; //empty job list + } + } + } + + /** + * @param int $id + * @return Job + */ + public function getById($id) { + $query = \OC_DB::prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` WHERE `id` = ?'); + $result = $query->execute(array($id)); + if ($row = $result->fetchRow()) { + return $this->buildJob($row); + } else { + return null; + } + } + + /** + * get the job object from a row in the db + * + * @param array $row + * @return Job + */ + private function buildJob($row) { + $class = $row['class']; + /** + * @var Job $job + */ + $job = new $class(); + $job->setId($row['id']); + $job->setLastRun($row['last_run']); + $job->setArgument(json_decode($row['argument'])); + return $job; + } + + /** + * set the job that was last ran + * + * @param Job $job + */ + public function setLastJob($job) { + \OC_Appconfig::setValue('backgroundjob', 'lastjob', $job->getId()); + } + + /** + * get the id of the last ran job + * + * @return int + */ + public function getLastJob() { + return \OC_Appconfig::getValue('backgroundjob', 'lastjob', 0); + } + + /** + * set the lastRun of $job to now + * + * @param Job $job + */ + public function setLastRun($job) { + $query = \OC_DB::prepare('UPDATE `*PREFIX*jobs` SET `last_run` = ? WHERE `id` = ?'); + $query->execute(array(time(), $job->getId())); + } +} diff --git a/lib/private/backgroundjob/legacy/queuedjob.php b/lib/private/backgroundjob/legacy/queuedjob.php new file mode 100644 index 00000000000..2bc001103b8 --- /dev/null +++ b/lib/private/backgroundjob/legacy/queuedjob.php @@ -0,0 +1,18 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\BackgroundJob\Legacy; + +class QueuedJob extends \OC\BackgroundJob\QueuedJob { + public function run($argument) { + $class = $argument['klass']; + $method = $argument['method']; + $parameters = $argument['parameters']; + call_user_func(array($class, $method), $parameters); + } +} diff --git a/lib/private/backgroundjob/legacy/regularjob.php b/lib/private/backgroundjob/legacy/regularjob.php new file mode 100644 index 00000000000..d4cfa348cea --- /dev/null +++ b/lib/private/backgroundjob/legacy/regularjob.php @@ -0,0 +1,15 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\BackgroundJob\Legacy; + +class RegularJob extends \OC\BackgroundJob\Job { + public function run($argument) { + call_user_func($argument); + } +} diff --git a/lib/private/backgroundjob/queuedjob.php b/lib/private/backgroundjob/queuedjob.php new file mode 100644 index 00000000000..1714182820d --- /dev/null +++ b/lib/private/backgroundjob/queuedjob.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\BackgroundJob; + +/** + * Class QueuedJob + * + * create a background job that is to be executed once + * + * @package OC\BackgroundJob + */ +abstract class QueuedJob extends Job { + /** + * run the job, then remove it from the joblist + * + * @param JobList $jobList + */ + public function execute($jobList) { + $jobList->remove($this); + $this->run($this->argument); + } +} diff --git a/lib/private/backgroundjob/timedjob.php b/lib/private/backgroundjob/timedjob.php new file mode 100644 index 00000000000..ae9f33505ab --- /dev/null +++ b/lib/private/backgroundjob/timedjob.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\BackgroundJob; + +/** + * Class QueuedJob + * + * create a background job that is to be executed at an interval + * + * @package OC\BackgroundJob + */ +abstract class TimedJob extends Job { + protected $interval = 0; + + /** + * set the interval for the job + * + * @param int $interval + */ + public function setInterval($interval) { + $this->interval = $interval; + } + + /** + * run the job if + * + * @param JobList $jobList + */ + public function execute($jobList) { + if ((time() - $this->lastRun) > $this->interval) { + $jobList->setLastRun($this); + $this->run($this->argument); + } + } +} diff --git a/lib/private/cache.php b/lib/private/cache.php new file mode 100644 index 00000000000..a311f10a00f --- /dev/null +++ b/lib/private/cache.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC; + +class Cache { + /** + * @var Cache $user_cache + */ + static protected $user_cache; + /** + * @var Cache $global_cache + */ + static protected $global_cache; + + /** + * get the global cache + * @return Cache + */ + static public function getGlobalCache() { + if (!self::$global_cache) { + self::$global_cache = new Cache\FileGlobal(); + } + return self::$global_cache; + } + + /** + * get the user cache + * @return Cache + */ + static public function getUserCache() { + if (!self::$user_cache) { + self::$user_cache = new Cache\File(); + } + return self::$user_cache; + } + + /** + * get a value from the user cache + * @param string $key + * @return mixed + */ + static public function get($key) { + $user_cache = self::getUserCache(); + return $user_cache->get($key); + } + + /** + * set a value in the user cache + * @param string $key + * @param mixed $value + * @param int $ttl + * @return bool + */ + static public function set($key, $value, $ttl=0) { + if (empty($key)) { + return false; + } + $user_cache = self::getUserCache(); + return $user_cache->set($key, $value, $ttl); + } + + /** + * check if a value is set in the user cache + * @param string $key + * @return bool + */ + static public function hasKey($key) { + $user_cache = self::getUserCache(); + return $user_cache->hasKey($key); + } + + /** + * remove an item from the user cache + * @param string $key + * @return bool + */ + static public function remove($key) { + $user_cache = self::getUserCache(); + return $user_cache->remove($key); + } + + /** + * clear the user cache of all entries starting with a prefix + * @param string $prefix (optional) + * @return bool + */ + static public function clear($prefix='') { + $user_cache = self::getUserCache(); + return $user_cache->clear($prefix); + } + + /** + * creates cache key based on the files given + * @param $files + * @return string + */ + static public function generateCacheKeyFromFiles($files) { + $key = ''; + sort($files); + foreach($files as $file) { + $stat = stat($file); + $key .= $file.$stat['mtime'].$stat['size']; + } + return md5($key); + } +} diff --git a/lib/private/cache/broker.php b/lib/private/cache/broker.php new file mode 100644 index 00000000000..9b7e837e1bc --- /dev/null +++ b/lib/private/cache/broker.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Cache; + +class Broker { + + /** + * @var \OC\Cache + */ + protected $fast_cache; + + /** + * @var \OC\Cache + */ + protected $slow_cache; + + public function __construct($fast_cache, $slow_cache) { + $this->fast_cache = $fast_cache; + $this->slow_cache = $slow_cache; + } + + public function get($key) { + if ($r = $this->fast_cache->get($key)) { + return $r; + } + return $this->slow_cache->get($key); + } + + public function set($key, $value, $ttl=0) { + if (!$this->fast_cache->set($key, $value, $ttl)) { + if ($this->fast_cache->hasKey($key)) { + $this->fast_cache->remove($key); + } + return $this->slow_cache->set($key, $value, $ttl); + } + return true; + } + + public function hasKey($key) { + if ($this->fast_cache->hasKey($key)) { + return true; + } + return $this->slow_cache->hasKey($key); + } + + public function remove($key) { + if ($this->fast_cache->remove($key)) { + return true; + } + return $this->slow_cache->remove($key); + } + + public function clear($prefix='') { + $this->fast_cache->clear($prefix); + $this->slow_cache->clear($prefix); + } +} diff --git a/lib/private/cache/file.php b/lib/private/cache/file.php new file mode 100644 index 00000000000..2ab914d17b8 --- /dev/null +++ b/lib/private/cache/file.php @@ -0,0 +1,118 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Cache; + +class File { + protected $storage; + protected function getStorage() { + if (isset($this->storage)) { + return $this->storage; + } + if(\OC_User::isLoggedIn()) { + \OC\Files\Filesystem::initMountPoints(\OC_User::getUser()); + $subdir = 'cache'; + $view = new \OC\Files\View('/' . \OC_User::getUser()); + if(!$view->file_exists($subdir)) { + $view->mkdir($subdir); + } + $this->storage = new \OC\Files\View('/' . \OC_User::getUser().'/'.$subdir); + return $this->storage; + }else{ + \OC_Log::write('core', 'Can\'t get cache storage, user not logged in', \OC_Log::ERROR); + return false; + } + } + + public function get($key) { + $result = null; + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + if ($this->hasKey($key)) { + $storage = $this->getStorage(); + $result = $storage->file_get_contents($key); + } + \OC_FileProxy::$enabled = $proxyStatus; + return $result; + } + + public function set($key, $value, $ttl=0) { + $storage = $this->getStorage(); + $result = false; + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + if ($storage and $storage->file_put_contents($key, $value)) { + if ($ttl === 0) { + $ttl = 86400; // 60*60*24 + } + $result = $storage->touch($key, time() + $ttl); + } + \OC_FileProxy::$enabled = $proxyStatus; + return $result; + } + + public function hasKey($key) { + $storage = $this->getStorage(); + if ($storage && $storage->is_file($key)) { + $mtime = $storage->filemtime($key); + if ($mtime < time()) { + $storage->unlink($key); + return false; + } + return true; + } + return false; + } + + public function remove($key) { + $storage = $this->getStorage(); + if(!$storage) { + return false; + } + return $storage->unlink($key); + } + + public function clear($prefix='') { + $storage = $this->getStorage(); + if($storage and $storage->is_dir('/')) { + $dh=$storage->opendir('/'); + if(is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if($file!='.' and $file!='..' and ($prefix==='' || strpos($file, $prefix) === 0)) { + $storage->unlink('/'.$file); + } + } + } + } + return true; + } + + public function gc() { + $storage = $this->getStorage(); + if($storage and $storage->is_dir('/')) { + $now = time(); + $dh=$storage->opendir('/'); + if(!is_resource($dh)) { + return null; + } + while (($file = readdir($dh)) !== false) { + if($file!='.' and $file!='..') { + $mtime = $storage->filemtime('/'.$file); + if ($mtime < $now) { + $storage->unlink('/'.$file); + } + } + } + } + } + + public static function loginListener() { + $c = new self(); + $c->gc(); + } +} diff --git a/lib/private/cache/fileglobal.php b/lib/private/cache/fileglobal.php new file mode 100644 index 00000000000..bd049bba4d0 --- /dev/null +++ b/lib/private/cache/fileglobal.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Cache; + +class FileGlobal { + static protected function getCacheDir() { + $cache_dir = get_temp_dir().'/owncloud-' . \OC_Util::getInstanceId().'/'; + if (!is_dir($cache_dir)) { + mkdir($cache_dir); + } + return $cache_dir; + } + + protected function fixKey($key) { + return str_replace('/', '_', $key); + } + + public function get($key) { + $key = $this->fixKey($key); + if ($this->hasKey($key)) { + $cache_dir = self::getCacheDir(); + return file_get_contents($cache_dir.$key); + } + return null; + } + + public function set($key, $value, $ttl=0) { + $key = $this->fixKey($key); + $cache_dir = self::getCacheDir(); + if ($cache_dir and file_put_contents($cache_dir.$key, $value)) { + if ($ttl === 0) { + $ttl = 86400; // 60*60*24 + } + return touch($cache_dir.$key, time() + $ttl); + } + return false; + } + + public function hasKey($key) { + $key = $this->fixKey($key); + $cache_dir = self::getCacheDir(); + if ($cache_dir && is_file($cache_dir.$key)) { + $mtime = filemtime($cache_dir.$key); + if ($mtime < time()) { + unlink($cache_dir.$key); + return false; + } + return true; + } + return false; + } + + public function remove($key) { + $cache_dir = self::getCacheDir(); + if(!$cache_dir) { + return false; + } + $key = $this->fixKey($key); + return unlink($cache_dir.$key); + } + + public function clear($prefix='') { + $cache_dir = self::getCacheDir(); + $prefix = $this->fixKey($prefix); + if($cache_dir and is_dir($cache_dir)) { + $dh=opendir($cache_dir); + if(is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if($file!='.' and $file!='..' and ($prefix==='' || strpos($file, $prefix) === 0)) { + unlink($cache_dir.$file); + } + } + } + } + } + + static public function gc() { + $last_run = \OC_AppConfig::getValue('core', 'global_cache_gc_lastrun', 0); + $now = time(); + if (($now - $last_run) < 300) { + // only do cleanup every 5 minutes + return; + } + \OC_AppConfig::setValue('core', 'global_cache_gc_lastrun', $now); + $cache_dir = self::getCacheDir(); + if($cache_dir and is_dir($cache_dir)) { + $dh=opendir($cache_dir); + if(is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if($file!='.' and $file!='..') { + $mtime = filemtime($cache_dir.$file); + if ($mtime < $now) { + unlink($cache_dir.$file); + } + } + } + } + } + } +} diff --git a/lib/private/cache/fileglobalgc.php b/lib/private/cache/fileglobalgc.php new file mode 100644 index 00000000000..399dd5e6f94 --- /dev/null +++ b/lib/private/cache/fileglobalgc.php @@ -0,0 +1,9 @@ +<?php + +namespace OC\Cache; + +class FileGlobalGC extends \OC\BackgroundJob\Job{ + public function run($argument){ + FileGlobal::gc(); + } +} diff --git a/lib/private/cache/usercache.php b/lib/private/cache/usercache.php new file mode 100644 index 00000000000..baa8820700b --- /dev/null +++ b/lib/private/cache/usercache.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright (c) 2013 Thomas Tanghus (thomas@tanghus.net) + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace OC\Cache; + +/** + * This interface defines method for accessing the file based user cache. + */ +class UserCache implements \OCP\ICache { + + /** + * @var \OC\Cache\File $userCache + */ + protected $userCache; + + public function __construct() { + $this->userCache = new File(); + } + + /** + * Get a value from the user cache + * + * @param string $key + * @return mixed + */ + public function get($key) { + return $this->userCache->get($key); + } + + /** + * Set a value in the user cache + * + * @param string $key + * @param mixed $value + * @param int $ttl Time To Live in seconds. Defaults to 60*60*24 + * @return bool + */ + public function set($key, $value, $ttl = 0) { + if (empty($key)) { + return false; + } + return $this->userCache->set($key, $value, $ttl); + } + + /** + * Check if a value is set in the user cache + * + * @param string $key + * @return bool + */ + public function hasKey($key) { + return $this->userCache->hasKey($key); + } + + /** + * Remove an item from the user cache + * + * @param string $key + * @return bool + */ + public function remove($key) { + return $this->userCache->remove($key); + } + + /** + * clear the user cache of all entries starting with a prefix + * @param string $prefix (optional) + * @return bool + */ + public function clear($prefix = '') { + return $this->userCache->clear($prefix); + } +} diff --git a/lib/private/config.php b/lib/private/config.php new file mode 100644 index 00000000000..e773e6e2eb0 --- /dev/null +++ b/lib/private/config.php @@ -0,0 +1,186 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @author Jakob Sack + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +/* + * + * An example of config.php + * + * <?php + * $CONFIG = array( + * "database" => "mysql", + * "firstrun" => false, + * "pi" => 3.14 + * ); + * ?> + * + */ + +namespace OC; + +/** + * This class is responsible for reading and writing config.php, the very basic + * configuration file of ownCloud. + */ +class Config { + // associative array key => value + protected $cache = array(); + + protected $configDir; + protected $configFilename; + + protected $debugMode; + + /** + * @param $configDir path to the config dir, needs to end with '/' + */ + public function __construct($configDir) { + $this->configDir = $configDir; + $this->configFilename = $this->configDir.'config.php'; + $this->readData(); + $this->setDebugMode(defined('DEBUG') && DEBUG); + } + + public function setDebugMode($enable) { + $this->debugMode = $enable; + } + + /** + * @brief Lists all available config keys + * @return array with key names + * + * This function returns all keys saved in config.php. Please note that it + * does not return the values. + */ + public function getKeys() { + return array_keys($this->cache); + } + + /** + * @brief Gets a value from config.php + * @param string $key key + * @param string $default = null default value + * @return string the value or $default + * + * This function gets the value from config.php. If it does not exist, + * $default will be returned. + */ + public function getValue($key, $default = null) { + if (isset($this->cache[$key])) { + return $this->cache[$key]; + } + + return $default; + } + + /** + * @brief Sets a value + * @param string $key key + * @param string $value value + * + * This function sets the value and writes the config.php. + * + */ + public function setValue($key, $value) { + // Add change + $this->cache[$key] = $value; + + // Write changes + $this->writeData(); + } + + /** + * @brief Removes a key from the config + * @param string $key key + * + * This function removes a key from the config.php. + * + */ + public function deleteKey($key) { + if (isset($this->cache[$key])) { + // Delete key from cache + unset($this->cache[$key]); + + // Write changes + $this->writeData(); + } + } + + /** + * @brief Loads the config file + * + * Reads the config file and saves it to the cache + */ + private function readData() { + // Default config + $configFiles = array($this->configFilename); + // Add all files in the config dir ending with config.php + $extra = glob($this->configDir.'*.config.php'); + if (is_array($extra)) { + natsort($extra); + $configFiles = array_merge($configFiles, $extra); + } + // Include file and merge config + foreach ($configFiles as $file) { + if (!file_exists($file)) { + continue; + } + unset($CONFIG); + // ignore errors on include, this can happen when doing a fresh install + @include $file; + if (isset($CONFIG) && is_array($CONFIG)) { + $this->cache = array_merge($this->cache, $CONFIG); + } + } + } + + /** + * @brief Writes the config file + * + * Saves the config to the config file. + * + */ + private function writeData() { + // Create a php file ... + $defaults = new \OC_Defaults; + $content = "<?php\n"; + if ($this->debugMode) { + $content .= "define('DEBUG',true);\n"; + } + $content .= '$CONFIG = '; + $content .= var_export($this->cache, true); + $content .= ";\n"; + + // Write the file + $result = @file_put_contents($this->configFilename, $content); + if (!$result) { + $url = $defaults->getDocBaseUrl() . '/server/5.0/admin_manual/installation/installation_source.html#set-the-directory-permissions'; + throw new HintException( + "Can't write into config directory!", + 'This can usually be fixed by ' + .'<a href="' . $url . '" target="_blank">giving the webserver write access to the config directory</a>.'); + } + // Prevent others not to read the config + @chmod($this->configFilename, 0640); + \OC_Util::clearOpcodeCache(); + } +} + diff --git a/lib/private/connector/sabre/ServiceUnavailable.php b/lib/private/connector/sabre/ServiceUnavailable.php new file mode 100644 index 00000000000..c1cc815c989 --- /dev/null +++ b/lib/private/connector/sabre/ServiceUnavailable.php @@ -0,0 +1,22 @@ +<?php +/** + * ownCloud + * + * @author Thomas Müller + * @copyright 2013 Thomas Müller <thomas.mueller@tmit.eu> + * + * @license AGPL3 + */ + +class Sabre_DAV_Exception_ServiceUnavailable extends Sabre_DAV_Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 503; + } +} diff --git a/lib/private/connector/sabre/auth.php b/lib/private/connector/sabre/auth.php new file mode 100644 index 00000000000..bf3a49593cb --- /dev/null +++ b/lib/private/connector/sabre/auth.php @@ -0,0 +1,84 @@ +<?php + +/** + * ownCloud + * + * @author Jakob Sack + * @copyright 2011 Jakob Sack kde@jakobsack.de + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +class OC_Connector_Sabre_Auth extends Sabre_DAV_Auth_Backend_AbstractBasic { + /** + * Validates a username and password + * + * This method should return true or false depending on if login + * succeeded. + * + * @return bool + */ + protected function validateUserPass($username, $password) { + if (OC_User::isLoggedIn()) { + OC_Util::setupFS(OC_User::getUser()); + return true; + } else { + OC_Util::setUpFS();//login hooks may need early access to the filesystem + if(OC_User::login($username, $password)) { + OC_Util::setUpFS(OC_User::getUser()); + return true; + } + else{ + return false; + } + } + } + + /** + * Returns information about the currently logged in username. + * + * If nobody is currently logged in, this method should return null. + * + * @return string|null + */ + public function getCurrentUser() { + $user = OC_User::getUser(); + if(!$user) { + return null; + } + return $user; + } + + /** + * Override function here. We want to cache authentication cookies + * in the syncing client to avoid HTTP-401 roundtrips. + * If the sync client supplies the cookies, then OC_User::isLoggedIn() + * will return true and we can see this WebDAV request as already authenticated, + * even if there are no HTTP Basic Auth headers. + * In other case, just fallback to the parent implementation. + * + * @return bool + */ + public function authenticate(Sabre_DAV_Server $server, $realm) { + if (OC_User::isLoggedIn()) { + $user = OC_User::getUser(); + OC_Util::setupFS($user); + $this->currentUser = $user; + return true; + } + + return parent::authenticate($server, $realm); + } +} diff --git a/lib/private/connector/sabre/directory.php b/lib/private/connector/sabre/directory.php new file mode 100644 index 00000000000..382bdf06df1 --- /dev/null +++ b/lib/private/connector/sabre/directory.php @@ -0,0 +1,256 @@ +<?php + +/** + * ownCloud + * + * @author Jakob Sack + * @copyright 2011 Jakob Sack kde@jakobsack.de + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sabre_DAV_ICollection, Sabre_DAV_IQuota { + + /** + * Creates a new file in the directory + * + * Data will either be supplied as a stream resource, or in certain cases + * as a string. Keep in mind that you may have to support either. + * + * After succesful creation of the file, you may choose to return the ETag + * of the new file here. + * + * The returned ETag must be surrounded by double-quotes (The quotes should + * be part of the actual string). + * + * If you cannot accurately determine the ETag, you should not return it. + * If you don't store the file exactly as-is (you're transforming it + * somehow) you should also not return an ETag. + * + * This means that if a subsequent GET to this new file does not exactly + * return the same contents of what was submitted here, you are strongly + * recommended to omit the ETag. + * + * @param string $name Name of the file + * @param resource|string $data Initial payload + * @throws Sabre_DAV_Exception_Forbidden + * @return null|string + */ + public function createFile($name, $data = null) { + + if (!\OC\Files\Filesystem::isCreatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + + if (isset($_SERVER['HTTP_OC_CHUNKED'])) { + $info = OC_FileChunking::decodeName($name); + if (empty($info)) { + throw new Sabre_DAV_Exception_NotImplemented(); + } + $chunk_handler = new OC_FileChunking($info); + $chunk_handler->store($info['index'], $data); + if ($chunk_handler->isComplete()) { + $newPath = $this->path . '/' . $info['name']; + $chunk_handler->file_assemble($newPath); + return OC_Connector_Sabre_Node::getETagPropertyForPath($newPath); + } + } else { + $newPath = $this->path . '/' . $name; + + // mark file as partial while uploading (ignored by the scanner) + $partpath = $newPath . '.part'; + + \OC\Files\Filesystem::file_put_contents($partpath, $data); + + // rename to correct path + $renameOkay = \OC\Files\Filesystem::rename($partpath, $newPath); + $fileExists = \OC\Files\Filesystem::file_exists($newPath); + if ($renameOkay === false || $fileExists === false) { + \OC_Log::write('webdav', '\OC\Files\Filesystem::rename() failed', \OC_Log::ERROR); + \OC\Files\Filesystem::unlink($partpath); + throw new Sabre_DAV_Exception(); + } + + // allow sync clients to send the mtime along in a header + $mtime = OC_Request::hasModificationTime(); + if ($mtime !== false) { + if(\OC\Files\Filesystem::touch($newPath, $mtime)) { + header('X-OC-MTime: accepted'); + } + } + + return OC_Connector_Sabre_Node::getETagPropertyForPath($newPath); + } + + return null; + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @throws Sabre_DAV_Exception_Forbidden + * @return void + */ + public function createDirectory($name) { + + if (!\OC\Files\Filesystem::isCreatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + + $newPath = $this->path . '/' . $name; + if(!\OC\Files\Filesystem::mkdir($newPath)) { + throw new Sabre_DAV_Exception_Forbidden('Could not create directory '.$newPath); + } + + } + + /** + * Returns a specific child node, referenced by its name + * + * @param string $name + * @throws Sabre_DAV_Exception_FileNotFound + * @return Sabre_DAV_INode + */ + public function getChild($name, $info = null) { + + $path = $this->path . '/' . $name; + if (is_null($info)) { + $info = \OC\Files\Filesystem::getFileInfo($path); + } + + if (!$info) { + throw new Sabre_DAV_Exception_NotFound('File with name ' . $path . ' could not be located'); + } + + if ($info['mimetype'] == 'httpd/unix-directory') { + $node = new OC_Connector_Sabre_Directory($path); + } else { + $node = new OC_Connector_Sabre_File($path); + } + + $node->setFileinfoCache($info); + return $node; + } + + /** + * Returns an array with all the child nodes + * + * @return Sabre_DAV_INode[] + */ + public function getChildren() { + + $folder_content = \OC\Files\Filesystem::getDirectoryContent($this->path); + $paths = array(); + foreach($folder_content as $info) { + $paths[] = $this->path.'/'.$info['name']; + $properties[$this->path.'/'.$info['name']][self::GETETAG_PROPERTYNAME] = '"' . $info['etag'] . '"'; + } + if(count($paths)>0) { + // + // the number of arguments within IN conditions are limited in most databases + // we chunk $paths into arrays of 200 items each to meet this criteria + // + $chunks = array_chunk($paths, 200, false); + foreach ($chunks as $pack) { + $placeholders = join(',', array_fill(0, count($pack), '?')); + $query = OC_DB::prepare( 'SELECT * FROM `*PREFIX*properties`' + .' WHERE `userid` = ?' . ' AND `propertypath` IN ('.$placeholders.')' ); + array_unshift($pack, OC_User::getUser()); // prepend userid + $result = $query->execute( $pack ); + while($row = $result->fetchRow()) { + $propertypath = $row['propertypath']; + $propertyname = $row['propertyname']; + $propertyvalue = $row['propertyvalue']; + if($propertyname !== self::GETETAG_PROPERTYNAME) { + $properties[$propertypath][$propertyname] = $propertyvalue; + } + } + } + } + + $nodes = array(); + foreach($folder_content as $info) { + $node = $this->getChild($info['name'], $info); + $node->setPropertyCache($properties[$this->path.'/'.$info['name']]); + $nodes[] = $node; + } + return $nodes; + } + + /** + * Checks if a child exists. + * + * @param string $name + * @return bool + */ + public function childExists($name) { + + $path = $this->path . '/' . $name; + return \OC\Files\Filesystem::file_exists($path); + + } + + /** + * Deletes all files in this directory, and then itself + * + * @return void + * @throws Sabre_DAV_Exception_Forbidden + */ + public function delete() { + + if (!\OC\Files\Filesystem::isDeletable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + if ($this->path != "/Shared") { + \OC\Files\Filesystem::rmdir($this->path); + } + + } + + /** + * Returns available diskspace information + * + * @return array + */ + public function getQuotaInfo() { + $storageInfo = OC_Helper::getStorageInfo($this->path); + return array( + $storageInfo['used'], + $storageInfo['free'] + ); + + } + + /** + * Returns a list of properties for this nodes.; + * + * The properties list is a list of propertynames the client requested, + * encoded as xmlnamespace#tagName, for example: + * http://www.example.org/namespace#author + * If the array is empty, all properties should be returned + * + * @param array $properties + * @return void + */ + public function getProperties($properties) { + $props = parent::getProperties($properties); + if (in_array(self::GETETAG_PROPERTYNAME, $properties) && !isset($props[self::GETETAG_PROPERTYNAME])) { + $props[self::GETETAG_PROPERTYNAME] + = OC_Connector_Sabre_Node::getETagPropertyForPath($this->path); + } + return $props; + } +} diff --git a/lib/private/connector/sabre/file.php b/lib/private/connector/sabre/file.php new file mode 100644 index 00000000000..433b1148552 --- /dev/null +++ b/lib/private/connector/sabre/file.php @@ -0,0 +1,176 @@ +<?php + +/** + * ownCloud + * + * @author Jakob Sack + * @copyright 2011 Jakob Sack kde@jakobsack.de + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_DAV_IFile { + + /** + * Updates the data + * + * The data argument is a readable stream resource. + * + * After a succesful put operation, you may choose to return an ETag. The + * etag must always be surrounded by double-quotes. These quotes must + * appear in the actual string you're returning. + * + * Clients may use the ETag from a PUT request to later on make sure that + * when they update the file, the contents haven't changed in the mean + * time. + * + * If you don't plan to store the file byte-by-byte, and you return a + * different object on a subsequent GET you are strongly recommended to not + * return an ETag, and just return null. + * + * @param resource $data + * @throws Sabre_DAV_Exception_Forbidden + * @return string|null + */ + public function put($data) { + + if (!\OC\Files\Filesystem::isUpdatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + + // throw an exception if encryption was disabled but the files are still encrypted + if (\OC_Util::encryptedFiles()) { + throw new \Sabre_DAV_Exception_ServiceUnavailable(); + } + + // mark file as partial while uploading (ignored by the scanner) + $partpath = $this->path . '.part'; + + \OC\Files\Filesystem::file_put_contents($partpath, $data); + + //detect aborted upload + if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') { + if (isset($_SERVER['CONTENT_LENGTH'])) { + $expected = $_SERVER['CONTENT_LENGTH']; + $actual = \OC\Files\Filesystem::filesize($partpath); + if ($actual != $expected) { + \OC\Files\Filesystem::unlink($partpath); + throw new Sabre_DAV_Exception_BadRequest( + 'expected filesize ' . $expected . ' got ' . $actual); + } + } + } + + // rename to correct path + $renameOkay = \OC\Files\Filesystem::rename($partpath, $this->path); + $fileExists = \OC\Files\Filesystem::file_exists($this->path); + if ($renameOkay === false || $fileExists === false) { + \OC_Log::write('webdav', '\OC\Files\Filesystem::rename() failed', \OC_Log::ERROR); + \OC\Files\Filesystem::unlink($partpath); + throw new Sabre_DAV_Exception(); + } + + + //allow sync clients to send the mtime along in a header + $mtime = OC_Request::hasModificationTime(); + if ($mtime !== false) { + if (\OC\Files\Filesystem::touch($this->path, $mtime)) { + header('X-OC-MTime: accepted'); + } + } + + return OC_Connector_Sabre_Node::getETagPropertyForPath($this->path); + } + + /** + * Returns the data + * + * @return string + */ + public function get() { + + //throw execption if encryption is disabled but files are still encrypted + if (\OC_Util::encryptedFiles()) { + throw new \Sabre_DAV_Exception_ServiceUnavailable(); + } else { + return \OC\Files\Filesystem::fopen($this->path, 'rb'); + } + + } + + /** + * Delete the current file + * + * @return void + * @throws Sabre_DAV_Exception_Forbidden + */ + public function delete() { + + if (!\OC\Files\Filesystem::isDeletable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + \OC\Files\Filesystem::unlink($this->path); + + } + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + public function getSize() { + $this->getFileinfoCache(); + if ($this->fileinfo_cache['size'] > -1) { + return $this->fileinfo_cache['size']; + } else { + return null; + } + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the + * file. If the file changes, the ETag MUST change. The ETag is an + * arbritrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * + * @return mixed + */ + public function getETag() { + $properties = $this->getProperties(array(self::GETETAG_PROPERTYNAME)); + if (isset($properties[self::GETETAG_PROPERTYNAME])) { + return $properties[self::GETETAG_PROPERTYNAME]; + } + return null; + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return mixed + */ + public function getContentType() { + if (isset($this->fileinfo_cache['mimetype'])) { + return $this->fileinfo_cache['mimetype']; + } + + return \OC\Files\Filesystem::getMimeType($this->path); + + } +} diff --git a/lib/private/connector/sabre/locks.php b/lib/private/connector/sabre/locks.php new file mode 100644 index 00000000000..69496c15ada --- /dev/null +++ b/lib/private/connector/sabre/locks.php @@ -0,0 +1,189 @@ +<?php + +/** + * ownCloud + * + * @author Jakob Sack + * @copyright 2011 Jakob Sack kde@jakobsack.de + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +class OC_Connector_Sabre_Locks extends Sabre_DAV_Locks_Backend_Abstract { + + /** + * Returns a list of Sabre_DAV_Locks_LockInfo objects + * + * This method should return all the locks for a particular uri, including + * locks that might be set on a parent uri. + * + * If returnChildLocks is set to true, this method should also look for + * any locks in the subtree of the uri for locks. + * + * @param string $uri + * @param bool $returnChildLocks + * @return array + */ + public function getLocks($uri, $returnChildLocks) { + + // NOTE: the following 10 lines or so could be easily replaced by + // pure sql. MySQL's non-standard string concatination prevents us + // from doing this though. + // NOTE: SQLite requires time() to be inserted directly. That's ugly + // but otherwise reading locks from SQLite Databases will return + // nothing + $query = 'SELECT * FROM `*PREFIX*locks`' + .' WHERE `userid` = ? AND (`created` + `timeout`) > '.time().' AND (( `uri` = ?)'; + if (OC_Config::getValue( "dbtype") === 'oci') { + //FIXME oracle hack: need to explicitly cast CLOB to CHAR for comparison + $query = 'SELECT * FROM `*PREFIX*locks`' + .' WHERE `userid` = ? AND (`created` + `timeout`) > '.time().' AND (( to_char(`uri`) = ?)'; + } + $params = array(OC_User::getUser(), $uri); + + // We need to check locks for every part in the uri. + $uriParts = explode('/', $uri); + + // We already covered the last part of the uri + array_pop($uriParts); + + $currentPath=''; + + foreach($uriParts as $part) { + + if ($currentPath) $currentPath.='/'; + $currentPath.=$part; + //FIXME oracle hack: need to explicitly cast CLOB to CHAR for comparison + if (OC_Config::getValue( "dbtype") === 'oci') { + $query.=' OR (`depth` != 0 AND to_char(`uri`) = ?)'; + } else { + $query.=' OR (`depth` != 0 AND `uri` = ?)'; + } + $params[] = $currentPath; + + } + + if ($returnChildLocks) { + + //FIXME oracle hack: need to explicitly cast CLOB to CHAR for comparison + if (OC_Config::getValue( "dbtype") === 'oci') { + $query.=' OR (to_char(`uri`) LIKE ?)'; + } else { + $query.=' OR (`uri` LIKE ?)'; + } + $params[] = $uri . '/%'; + + } + $query.=')'; + + $result = OC_DB::executeAudited( $query, $params ); + + $lockList = array(); + while( $row = $result->fetchRow()) { + + $lockInfo = new Sabre_DAV_Locks_LockInfo(); + $lockInfo->owner = $row['owner']; + $lockInfo->token = $row['token']; + $lockInfo->timeout = $row['timeout']; + $lockInfo->created = $row['created']; + $lockInfo->scope = $row['scope']; + $lockInfo->depth = $row['depth']; + $lockInfo->uri = $row['uri']; + $lockList[] = $lockInfo; + + } + + return $lockList; + + } + + /** + * Locks a uri + * + * @param string $uri + * @param Sabre_DAV_Locks_LockInfo $lockInfo + * @return bool + */ + public function lock($uri, Sabre_DAV_Locks_LockInfo $lockInfo) { + + // We're making the lock timeout 5 minutes + $lockInfo->timeout = 300; + $lockInfo->created = time(); + $lockInfo->uri = $uri; + + $locks = $this->getLocks($uri, false); + $exists = false; + foreach($locks as $lock) { + if ($lock->token == $lockInfo->token) { + $exists = true; + break; + } + } + + if ($exists) { + $sql = 'UPDATE `*PREFIX*locks`' + .' SET `owner` = ?, `timeout` = ?, `scope` = ?, `depth` = ?, `uri` = ?, `created` = ?' + .' WHERE `userid` = ? AND `token` = ?'; + $result = OC_DB::executeAudited( $sql, array( + $lockInfo->owner, + $lockInfo->timeout, + $lockInfo->scope, + $lockInfo->depth, + $uri, + $lockInfo->created, + OC_User::getUser(), + $lockInfo->token) + ); + } else { + $sql = 'INSERT INTO `*PREFIX*locks`' + .' (`userid`,`owner`,`timeout`,`scope`,`depth`,`uri`,`created`,`token`)' + .' VALUES (?,?,?,?,?,?,?,?)'; + $result = OC_DB::executeAudited( $sql, array( + OC_User::getUser(), + $lockInfo->owner, + $lockInfo->timeout, + $lockInfo->scope, + $lockInfo->depth, + $uri, + $lockInfo->created, + $lockInfo->token) + ); + } + + return true; + + } + + /** + * Removes a lock from a uri + * + * @param string $uri + * @param Sabre_DAV_Locks_LockInfo $lockInfo + * @return bool + */ + public function unlock($uri, Sabre_DAV_Locks_LockInfo $lockInfo) { + + $sql = 'DELETE FROM `*PREFIX*locks` WHERE `userid` = ? AND `uri` = ? AND `token` = ?'; + if (OC_Config::getValue( "dbtype") === 'oci') { + //FIXME oracle hack: need to explicitly cast CLOB to CHAR for comparison + $sql = 'DELETE FROM `*PREFIX*locks` WHERE `userid` = ? AND to_char(`uri`) = ? AND `token` = ?'; + } + $result = OC_DB::executeAudited( $sql, array(OC_User::getUser(), $uri, $lockInfo->token)); + + return $result === 1; + + } + +} diff --git a/lib/private/connector/sabre/maintenanceplugin.php b/lib/private/connector/sabre/maintenanceplugin.php new file mode 100644 index 00000000000..2eda269afc2 --- /dev/null +++ b/lib/private/connector/sabre/maintenanceplugin.php @@ -0,0 +1,59 @@ +<?php + +/** + * ownCloud + * + * @author Thomas Müller + * @copyright 2013 Thomas Müller <thomas.mueller@tmit.eu> + * + * @license AGPL3 + */ + +require 'ServiceUnavailable.php'; + +class OC_Connector_Sabre_MaintenancePlugin extends Sabre_DAV_ServerPlugin +{ + + /** + * Reference to main server object + * + * @var Sabre_DAV_Server + */ + private $server; + + /** + * This initializes the plugin. + * + * This function is called by Sabre_DAV_Server, after + * addPlugin is called. + * + * This method should set up the required event subscriptions. + * + * @param Sabre_DAV_Server $server + * @return void + */ + public function initialize(Sabre_DAV_Server $server) { + + $this->server = $server; + $this->server->subscribeEvent('beforeMethod', array($this, 'checkMaintenanceMode'), 10); + } + + /** + * This method is called before any HTTP method and returns http status code 503 + * in case the system is in maintenance mode. + * + * @throws Sabre_DAV_Exception_ServiceUnavailable + * @internal param string $method + * @return bool + */ + public function checkMaintenanceMode() { + if (OC_Config::getValue('maintenance', false)) { + throw new Sabre_DAV_Exception_ServiceUnavailable(); + } + if (OC::checkUpgrade(false)) { + throw new Sabre_DAV_Exception_ServiceUnavailable('Upgrade needed'); + } + + return true; + } +} diff --git a/lib/private/connector/sabre/node.php b/lib/private/connector/sabre/node.php new file mode 100644 index 00000000000..29b7f9e53a5 --- /dev/null +++ b/lib/private/connector/sabre/node.php @@ -0,0 +1,238 @@ +<?php + +/** + * ownCloud + * + * @author Jakob Sack + * @copyright 2011 Jakob Sack kde@jakobsack.de + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IProperties { + const GETETAG_PROPERTYNAME = '{DAV:}getetag'; + const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified'; + + /** + * Allow configuring the method used to generate Etags + * + * @var array(class_name, function_name) + */ + public static $ETagFunction = null; + + /** + * The path to the current node + * + * @var string + */ + protected $path; + /** + * node fileinfo cache + * @var array + */ + protected $fileinfo_cache; + /** + * node properties cache + * @var array + */ + protected $property_cache = null; + + /** + * @brief Sets up the node, expects a full path name + * @param string $path + * @return void + */ + public function __construct($path) { + $this->path = $path; + } + + + + /** + * @brief Returns the name of the node + * @return string + */ + public function getName() { + + list(, $name) = Sabre_DAV_URLUtil::splitPath($this->path); + return $name; + + } + + /** + * @brief Renames the node + * @param string $name The new name + * @return void + */ + public function setName($name) { + + // rename is only allowed if the update privilege is granted + if (!\OC\Files\Filesystem::isUpdatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + + list($parentPath, ) = Sabre_DAV_URLUtil::splitPath($this->path); + list(, $newName) = Sabre_DAV_URLUtil::splitPath($name); + + $newPath = $parentPath . '/' . $newName; + $oldPath = $this->path; + + \OC\Files\Filesystem::rename($this->path, $newPath); + + $this->path = $newPath; + + $query = OC_DB::prepare( 'UPDATE `*PREFIX*properties` SET `propertypath` = ?' + .' WHERE `userid` = ? AND `propertypath` = ?' ); + $query->execute( array( $newPath, OC_User::getUser(), $oldPath )); + + } + + public function setFileinfoCache($fileinfo_cache) + { + $this->fileinfo_cache = $fileinfo_cache; + } + + /** + * @brief Ensure that the fileinfo cache is filled + * @note Uses OC_FileCache or a direct stat + */ + protected function getFileinfoCache() { + if (!isset($this->fileinfo_cache)) { + if ($fileinfo_cache = \OC\Files\Filesystem::getFileInfo($this->path)) { + } else { + $fileinfo_cache = \OC\Files\Filesystem::stat($this->path); + } + + $this->fileinfo_cache = $fileinfo_cache; + } + } + + public function setPropertyCache($property_cache) + { + $this->property_cache = $property_cache; + } + + /** + * @brief Returns the last modification time, as a unix timestamp + * @return int + */ + public function getLastModified() { + $this->getFileinfoCache(); + return $this->fileinfo_cache['mtime']; + + } + + /** + * sets the last modification time of the file (mtime) to the value given + * in the second parameter or to now if the second param is empty. + * Even if the modification time is set to a custom value the access time is set to now. + */ + public function touch($mtime) { + + // touch is only allowed if the update privilege is granted + if (!\OC\Files\Filesystem::isUpdatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + + \OC\Files\Filesystem::touch($this->path, $mtime); + } + + /** + * @brief Updates properties on this node, + * @param array $mutations + * @see Sabre_DAV_IProperties::updateProperties + * @return bool|array + */ + public function updateProperties($properties) { + $existing = $this->getProperties(array()); + foreach($properties as $propertyName => $propertyValue) { + // If it was null, we need to delete the property + if (is_null($propertyValue)) { + if(array_key_exists( $propertyName, $existing )) { + $query = OC_DB::prepare( 'DELETE FROM `*PREFIX*properties`' + .' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?' ); + $query->execute( array( OC_User::getUser(), $this->path, $propertyName )); + } + } + else { + if( strcmp( $propertyName, self::GETETAG_PROPERTYNAME) === 0 ) { + \OC\Files\Filesystem::putFileInfo($this->path, array('etag'=> $propertyValue)); + } elseif( strcmp( $propertyName, self::LASTMODIFIED_PROPERTYNAME) === 0 ) { + $this->touch($propertyValue); + } else { + if(!array_key_exists( $propertyName, $existing )) { + $query = OC_DB::prepare( 'INSERT INTO `*PREFIX*properties`' + .' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)' ); + $query->execute( array( OC_User::getUser(), $this->path, $propertyName,$propertyValue )); + } else { + $query = OC_DB::prepare( 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' + .' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?' ); + $query->execute( array( $propertyValue,OC_User::getUser(), $this->path, $propertyName )); + } + } + } + + } + $this->setPropertyCache(null); + return true; + } + + /** + * @brief Returns a list of properties for this nodes.; + * @param array $properties + * @return array + * @note The properties list is a list of propertynames the client + * requested, encoded as xmlnamespace#tagName, for example: + * http://www.example.org/namespace#author If the array is empty, all + * properties should be returned + */ + public function getProperties($properties) { + if (is_null($this->property_cache)) { + $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'; + $result = OC_DB::executeAudited( $sql, array( OC_User::getUser(), $this->path ) ); + + $this->property_cache = array(); + while( $row = $result->fetchRow()) { + $this->property_cache[$row['propertyname']] = $row['propertyvalue']; + } + $this->property_cache[self::GETETAG_PROPERTYNAME] = $this->getETagPropertyForPath($this->path); + } + + // if the array was empty, we need to return everything + if(count($properties) == 0) { + return $this->property_cache; + } + + $props = array(); + foreach($properties as $property) { + if (isset($this->property_cache[$property])) $props[$property] = $this->property_cache[$property]; + } + return $props; + } + + /** + * Returns the ETag surrounded by double-quotes for this path. + * @param string $path Path of the file + * @return string|null Returns null if the ETag can not effectively be determined + */ + static public function getETagPropertyForPath($path) { + $data = \OC\Files\Filesystem::getFileInfo($path); + if (isset($data['etag'])) { + return '"'.$data['etag'].'"'; + } + return null; + } + +} diff --git a/lib/private/connector/sabre/objecttree.php b/lib/private/connector/sabre/objecttree.php new file mode 100644 index 00000000000..80c3840b99d --- /dev/null +++ b/lib/private/connector/sabre/objecttree.php @@ -0,0 +1,142 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Connector\Sabre; + +use OC\Files\Filesystem; + +class ObjectTree extends \Sabre_DAV_ObjectTree { + + /** + * keep this public to allow mock injection during unit test + * + * @var \OC\Files\View + */ + public $fileView; + + /** + * Returns the INode object for the requested path + * + * @param string $path + * @throws \Sabre_DAV_Exception_NotFound + * @return \Sabre_DAV_INode + */ + public function getNodeForPath($path) { + + $path = trim($path, '/'); + if (isset($this->cache[$path])) { + return $this->cache[$path]; + } + + // Is it the root node? + if (!strlen($path)) { + return $this->rootNode; + } + + $info = $this->getFileView()->getFileInfo($path); + + if (!$info) { + throw new \Sabre_DAV_Exception_NotFound('File with name ' . $path . ' could not be located'); + } + + if ($info['mimetype'] === 'httpd/unix-directory') { + $node = new \OC_Connector_Sabre_Directory($path); + } else { + $node = new \OC_Connector_Sabre_File($path); + } + + $node->setFileinfoCache($info); + + $this->cache[$path] = $node; + return $node; + + } + + /** + * Moves a file from one location to another + * + * @param string $sourcePath The path to the file which should be moved + * @param string $destinationPath The full destination path, so not just the destination parent node + * @throws \Sabre_DAV_Exception_Forbidden + * @return int + */ + public function move($sourcePath, $destinationPath) { + + $sourceNode = $this->getNodeForPath($sourcePath); + if ($sourceNode instanceof \Sabre_DAV_ICollection and $this->nodeExists($destinationPath)) { + throw new \Sabre_DAV_Exception_Forbidden('Could not copy directory ' . $sourceNode . ', target exists'); + } + list($sourceDir,) = \Sabre_DAV_URLUtil::splitPath($sourcePath); + list($destinationDir,) = \Sabre_DAV_URLUtil::splitPath($destinationPath); + + // check update privileges + $fs = $this->getFileView(); + if (!$fs->isUpdatable($sourcePath)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + if ($sourceDir !== $destinationDir) { + // for a full move we need update privileges on sourcePath and sourceDir as well as destinationDir + if (!$fs->isUpdatable($sourceDir)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + if (!$fs->isUpdatable($destinationDir)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + } + + $renameOkay = $fs->rename($sourcePath, $destinationPath); + if (!$renameOkay) { + throw new \Sabre_DAV_Exception_Forbidden(''); + } + + $this->markDirty($sourceDir); + $this->markDirty($destinationDir); + + } + + /** + * Copies a file or directory. + * + * This method must work recursively and delete the destination + * if it exists + * + * @param string $source + * @param string $destination + * @return void + */ + public function copy($source, $destination) { + + if (Filesystem::is_file($source)) { + Filesystem::copy($source, $destination); + } else { + Filesystem::mkdir($destination); + $dh = Filesystem::opendir($source); + if(is_resource($dh)) { + while (($subnode = readdir($dh)) !== false) { + + if ($subnode == '.' || $subnode == '..') continue; + $this->copy($source . '/' . $subnode, $destination . '/' . $subnode); + + } + } + } + + list($destinationDir,) = \Sabre_DAV_URLUtil::splitPath($destination); + $this->markDirty($destinationDir); + } + + /** + * @return \OC\Files\View + */ + public function getFileView() { + if (is_null($this->fileView)) { + $this->fileView = \OC\Files\Filesystem::getView(); + } + return $this->fileView; + } +} diff --git a/lib/private/connector/sabre/principal.php b/lib/private/connector/sabre/principal.php new file mode 100644 index 00000000000..59a96797c16 --- /dev/null +++ b/lib/private/connector/sabre/principal.php @@ -0,0 +1,128 @@ +<?php +/** + * Copyright (c) 2011 Jakob Sack mail@jakobsack.de + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Connector_Sabre_Principal implements Sabre_DAVACL_IPrincipalBackend { + /** + * Returns a list of principals based on a prefix. + * + * This prefix will often contain something like 'principals'. You are only + * expected to return principals that are in this base path. + * + * You are expected to return at least a 'uri' for every user, you can + * return any additional properties if you wish so. Common properties are: + * {DAV:}displayname + * + * @param string $prefixPath + * @return array + */ + public function getPrincipalsByPrefix( $prefixPath ) { + $principals = array(); + + if ($prefixPath == 'principals') { + foreach(OC_User::getUsers() as $user) { + $user_uri = 'principals/'.$user; + $principals[] = array( + 'uri' => $user_uri, + '{DAV:}displayname' => $user, + ); + } + } + + return $principals; + } + + /** + * Returns a specific principal, specified by it's path. + * The returned structure should be the exact same as from + * getPrincipalsByPrefix. + * + * @param string $path + * @return array + */ + public function getPrincipalByPath($path) { + list($prefix, $name) = explode('/', $path); + + if ($prefix == 'principals' && OC_User::userExists($name)) { + return array( + 'uri' => 'principals/'.$name, + '{DAV:}displayname' => $name, + ); + } + + return null; + } + + /** + * Returns the list of members for a group-principal + * + * @param string $principal + * @return array + */ + public function getGroupMemberSet($principal) { + // TODO: for now the group principal has only one member, the user itself + $principal = $this->getPrincipalByPath($principal); + if (!$principal) { + throw new Sabre_DAV_Exception('Principal not found'); + } + + return array( + $principal['uri'] + ); + } + + /** + * Returns the list of groups a principal is a member of + * + * @param string $principal + * @return array + */ + public function getGroupMembership($principal) { + list($prefix, $name) = Sabre_DAV_URLUtil::splitPath($principal); + + $group_membership = array(); + if ($prefix == 'principals') { + $principal = $this->getPrincipalByPath($principal); + if (!$principal) { + throw new Sabre_DAV_Exception('Principal not found'); + } + + // TODO: for now the user principal has only its own groups + return array( + 'principals/'.$name.'/calendar-proxy-read', + 'principals/'.$name.'/calendar-proxy-write', + // The addressbook groups are not supported in Sabre, + // see http://groups.google.com/group/sabredav-discuss/browse_thread/thread/ef2fa9759d55f8c#msg_5720afc11602e753 + //'principals/'.$name.'/addressbook-proxy-read', + //'principals/'.$name.'/addressbook-proxy-write', + ); + } + return $group_membership; + } + + /** + * Updates the list of group members for a group principal. + * + * The principals should be passed as a list of uri's. + * + * @param string $principal + * @param array $members + * @return void + */ + public function setGroupMemberSet($principal, array $members) { + throw new Sabre_DAV_Exception('Setting members of the group is not supported yet'); + } + + function updatePrincipal($path, $mutations) { + return 0; + } + + function searchPrincipals($prefixPath, array $searchProperties) { + return array(); + } +} diff --git a/lib/private/connector/sabre/quotaplugin.php b/lib/private/connector/sabre/quotaplugin.php new file mode 100644 index 00000000000..ea2cb81d1f7 --- /dev/null +++ b/lib/private/connector/sabre/quotaplugin.php @@ -0,0 +1,97 @@ +<?php + +/** + * This plugin check user quota and deny creating files when they exceeds the quota. + * + * @author Sergio Cambra + * @copyright Copyright (C) 2012 entreCables S.L. All rights reserved. + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class OC_Connector_Sabre_QuotaPlugin extends Sabre_DAV_ServerPlugin { + + /** + * Reference to main server object + * + * @var Sabre_DAV_Server + */ + private $server; + + /** + * is kept public to allow overwrite for unit testing + * + * @var \OC\Files\View + */ + public $fileView; + + /** + * This initializes the plugin. + * + * This function is called by Sabre_DAV_Server, after + * addPlugin is called. + * + * This method should set up the requires event subscriptions. + * + * @param Sabre_DAV_Server $server + * @return void + */ + public function initialize(Sabre_DAV_Server $server) { + + $this->server = $server; + + $server->subscribeEvent('beforeWriteContent', array($this, 'checkQuota'), 10); + $server->subscribeEvent('beforeCreateFile', array($this, 'checkQuota'), 10); + } + + /** + * This method is called before any HTTP method and validates there is enough free space to store the file + * + * @param string $method + * @throws Sabre_DAV_Exception + * @return bool + */ + public function checkQuota($uri, $data = null) { + $length = $this->getLength(); + if ($length) { + if (substr($uri, 0, 1)!=='/') { + $uri='/'.$uri; + } + list($parentUri, $newName) = Sabre_DAV_URLUtil::splitPath($uri); + $freeSpace = $this->getFreeSpace($parentUri); + if ($freeSpace !== \OC\Files\SPACE_UNKNOWN && $length > $freeSpace) { + throw new Sabre_DAV_Exception_InsufficientStorage(); + } + } + return true; + } + + public function getLength() + { + $req = $this->server->httpRequest; + $length = $req->getHeader('X-Expected-Entity-Length'); + if (!$length) { + $length = $req->getHeader('Content-Length'); + } + + $ocLength = $req->getHeader('OC-Total-Length'); + if ($length && $ocLength) { + return max($length, $ocLength); + } + + return $length; + } + + /** + * @param $parentUri + * @return mixed + */ + public function getFreeSpace($parentUri) + { + if (is_null($this->fileView)) { + // initialize fileView + $this->fileView = \OC\Files\Filesystem::getView(); + } + + $freeSpace = $this->fileView->free_space($parentUri); + return $freeSpace; + } +} diff --git a/lib/private/connector/sabre/request.php b/lib/private/connector/sabre/request.php new file mode 100644 index 00000000000..d70c25c4e70 --- /dev/null +++ b/lib/private/connector/sabre/request.php @@ -0,0 +1,50 @@ +<?php + +/** + * ownCloud + * + * @author Stefan Herbrechtsmeier + * @copyright 2012 Stefan Herbrechtsmeier <stefan@herbrechtsmeier.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +class OC_Connector_Sabre_Request extends Sabre_HTTP_Request { + /** + * Returns the requested uri + * + * @return string + */ + public function getUri() { + return OC_Request::requestUri(); + } + + /** + * Returns a specific item from the _SERVER array. + * + * Do not rely on this feature, it is for internal use only. + * + * @param string $field + * @return string + */ + public function getRawServerValue($field) { + if($field == 'REQUEST_URI') { + return $this->getUri(); + } + else{ + return isset($this->_SERVER[$field])?$this->_SERVER[$field]:null; + } + } +} diff --git a/lib/private/contactsmanager.php b/lib/private/contactsmanager.php new file mode 100644 index 00000000000..fc6745b4505 --- /dev/null +++ b/lib/private/contactsmanager.php @@ -0,0 +1,145 @@ +<?php +/** + * ownCloud + * + * @author Thomas Müller + * @copyright 2013 Thomas Müller thomas.mueller@tmit.eu + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC { + + class ContactsManager implements \OCP\Contacts\IManager { + + /** + * This function is used to search and find contacts within the users address books. + * In case $pattern is empty all contacts will be returned. + * + * @param string $pattern which should match within the $searchProperties + * @param array $searchProperties defines the properties within the query pattern should match + * @param array $options - for future use. One should always have options! + * @return array of contacts which are arrays of key-value-pairs + */ + public function search($pattern, $searchProperties = array(), $options = array()) { + $result = array(); + foreach($this->address_books as $address_book) { + $r = $address_book->search($pattern, $searchProperties, $options); + $result = array_merge($result, $r); + } + + return $result; + } + + /** + * This function can be used to delete the contact identified by the given id + * + * @param object $id the unique identifier to a contact + * @param $address_book_key + * @return bool successful or not + */ + public function delete($id, $address_book_key) { + if (!array_key_exists($address_book_key, $this->address_books)) + return null; + + $address_book = $this->address_books[$address_book_key]; + if ($address_book->getPermissions() & \OCP\PERMISSION_DELETE) + return null; + + return $address_book->delete($id); + } + + /** + * This function is used to create a new contact if 'id' is not given or not present. + * Otherwise the contact will be updated by replacing the entire data set. + * + * @param array $properties this array if key-value-pairs defines a contact + * @param $address_book_key string to identify the address book in which the contact shall be created or updated + * @return array representing the contact just created or updated + */ + public function createOrUpdate($properties, $address_book_key) { + + if (!array_key_exists($address_book_key, $this->address_books)) + return null; + + $address_book = $this->address_books[$address_book_key]; + if ($address_book->getPermissions() & \OCP\PERMISSION_CREATE) + return null; + + return $address_book->createOrUpdate($properties); + } + + /** + * Check if contacts are available (e.g. contacts app enabled) + * + * @return bool true if enabled, false if not + */ + public function isEnabled() { + return !empty($this->address_books); + } + + /** + * @param \OCP\IAddressBook $address_book + */ + public function registerAddressBook(\OCP\IAddressBook $address_book) { + $this->address_books[$address_book->getKey()] = $address_book; + } + + /** + * @param \OCP\IAddressBook $address_book + */ + public function unregisterAddressBook(\OCP\IAddressBook $address_book) { + unset($this->address_books[$address_book->getKey()]); + } + + /** + * @return array + */ + public function getAddressBooks() { + $result = array(); + foreach($this->address_books as $address_book) { + $result[$address_book->getKey()] = $address_book->getDisplayName(); + } + + return $result; + } + + /** + * removes all registered address book instances + */ + public function clear() { + $this->address_books = array(); + } + + /** + * @var \OCP\IAddressBook[] which holds all registered address books + */ + private $address_books = array(); + + /** + * In order to improve lazy loading a closure can be registered which will be called in case + * address books are actually requested + * + * @param string $key + * @param \Closure $callable + */ + function register($key, \Closure $callable) + { + // + //TODO: implement me + // + } + } +} diff --git a/lib/private/db.php b/lib/private/db.php new file mode 100644 index 00000000000..1e5d12649df --- /dev/null +++ b/lib/private/db.php @@ -0,0 +1,462 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +define('MDB2_SCHEMA_DUMP_STRUCTURE', '1'); + +class DatabaseException extends Exception { + private $query; + + //FIXME getQuery seems to be unused, maybe use parent constructor with $message, $code and $previous + public function __construct($message, $query = null){ + parent::__construct($message); + $this->query = $query; + } + + public function getQuery() { + return $this->query; + } +} + +/** + * This class manages the access to the database. It basically is a wrapper for + * Doctrine with some adaptions. + */ +class OC_DB { + /** + * @var \OC\DB\Connection $connection + */ + static private $connection; //the prefered connection to use, only Doctrine + + static private $prefix=null; + static private $type=null; + + /** + * @brief connects to the database + * @return bool true if connection can be established or false on error + * + * Connects to the database as specified in config.php + */ + public static function connect() { + if(self::$connection) { + return true; + } + + // The global data we need + $name = OC_Config::getValue( "dbname", "owncloud" ); + $host = OC_Config::getValue( "dbhost", "" ); + $user = OC_Config::getValue( "dbuser", "" ); + $pass = OC_Config::getValue( "dbpassword", "" ); + $type = OC_Config::getValue( "dbtype", "sqlite" ); + if(strpos($host, ':')) { + list($host, $port)=explode(':', $host, 2); + } else { + $port=false; + } + + // do nothing if the connection already has been established + if (!self::$connection) { + $config = new \Doctrine\DBAL\Configuration(); + $eventManager = new \Doctrine\Common\EventManager(); + switch($type) { + case 'sqlite': + case 'sqlite3': + $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); + $connectionParams = array( + 'user' => $user, + 'password' => $pass, + 'path' => $datadir.'/'.$name.'.db', + 'driver' => 'pdo_sqlite', + ); + $connectionParams['adapter'] = '\OC\DB\AdapterSqlite'; + $connectionParams['wrapperClass'] = 'OC\DB\Connection'; + break; + case 'mysql': + $connectionParams = array( + 'user' => $user, + 'password' => $pass, + 'host' => $host, + 'port' => $port, + 'dbname' => $name, + 'charset' => 'UTF8', + 'driver' => 'pdo_mysql', + ); + $connectionParams['adapter'] = '\OC\DB\Adapter'; + $connectionParams['wrapperClass'] = 'OC\DB\Connection'; + break; + case 'pgsql': + $connectionParams = array( + 'user' => $user, + 'password' => $pass, + 'host' => $host, + 'port' => $port, + 'dbname' => $name, + 'driver' => 'pdo_pgsql', + ); + $connectionParams['adapter'] = '\OC\DB\AdapterPgSql'; + $connectionParams['wrapperClass'] = 'OC\DB\Connection'; + break; + case 'oci': + $connectionParams = array( + 'user' => $user, + 'password' => $pass, + 'host' => $host, + 'dbname' => $name, + 'charset' => 'AL32UTF8', + 'driver' => 'oci8', + ); + if (!empty($port)) { + $connectionParams['port'] = $port; + } + $connectionParams['adapter'] = '\OC\DB\AdapterOCI8'; + $connectionParams['wrapperClass'] = 'OC\DB\OracleConnection'; + $eventManager->addEventSubscriber(new \Doctrine\DBAL\Event\Listeners\OracleSessionInit); + break; + case 'mssql': + $connectionParams = array( + 'user' => $user, + 'password' => $pass, + 'host' => $host, + 'port' => $port, + 'dbname' => $name, + 'charset' => 'UTF8', + 'driver' => 'pdo_sqlsrv', + ); + $connectionParams['adapter'] = '\OC\DB\AdapterSQLSrv'; + $connectionParams['wrapperClass'] = 'OC\DB\Connection'; + break; + default: + return false; + } + $connectionParams['tablePrefix'] = OC_Config::getValue('dbtableprefix', 'oc_' ); + try { + self::$connection = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config, $eventManager); + if ($type === 'sqlite' || $type === 'sqlite3') { + // Sqlite doesn't handle query caching and schema changes + // TODO: find a better way to handle this + self::$connection->disableQueryStatementCaching(); + } + } catch(\Doctrine\DBAL\DBALException $e) { + OC_Log::write('core', $e->getMessage(), OC_Log::FATAL); + OC_User::setUserId(null); + + // send http status 503 + header('HTTP/1.1 503 Service Temporarily Unavailable'); + header('Status: 503 Service Temporarily Unavailable'); + OC_Template::printErrorPage('Failed to connect to database'); + die(); + } + } + return true; + } + + /** + * @return \OC\DB\Connection + */ + static public function getConnection() { + self::connect(); + return self::$connection; + } + + /** + * get MDB2 schema manager + * + * @return \OC\DB\MDB2SchemaManager + */ + private static function getMDB2SchemaManager() + { + return new \OC\DB\MDB2SchemaManager(self::getConnection()); + } + + /** + * @brief Prepare a SQL query + * @param string $query Query string + * @param int $limit + * @param int $offset + * @param bool $isManipulation + * @throws DatabaseException + * @return \Doctrine\DBAL\Statement prepared SQL query + * + * SQL query via Doctrine prepare(), needs to be execute()'d! + */ + static public function prepare( $query , $limit = null, $offset = null, $isManipulation = null) { + self::connect(); + + if ($isManipulation === null) { + //try to guess, so we return the number of rows on manipulations + $isManipulation = self::isManipulation($query); + } + + // return the result + try { + $result = self::$connection->prepare($query, $limit, $offset); + } catch (\Doctrine\DBAL\DBALException $e) { + throw new \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; + } + + /** + * @brief 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 result + * @throws 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, see fixLimitClauseForMSSQL + $message = 'LIMIT and OFFSET are forbidden for portability reasons,' + . ' pass an array with \'limit\' and \'offset\' instead'; + throw new 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 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 DatabaseException($message); + } + return $result; + } + + /** + * @brief gets last value of autoincrement + * @param string $table The optional table name (will replace *PREFIX*) and add sequence suffix + * @return int id + * @throws DatabaseException + * + * \Doctrine\DBAL\Connection lastInsertId + * + * Call this method right after the insert command or other functions may + * cause trouble! + */ + public static function insertid($table=null) { + self::connect(); + return self::$connection->lastInsertId($table); + } + + /** + * @brief Insert a row if a matching row doesn't exists. + * @param string $table. The table to insert into in the form '*PREFIX*tableName' + * @param array $input. An array of fieldname/value pairs + * @return int number of updated rows + */ + public static function insertIfNotExist($table, $input) { + self::connect(); + return self::$connection->insertIfNotExist($table, $input); + } + + /** + * Start a transaction + */ + public static function beginTransaction() { + self::connect(); + self::$connection->beginTransaction(); + } + + /** + * Commit the database changes done during a transaction that is in progress + */ + public static function commit() { + self::connect(); + self::$connection->commit(); + } + + /** + * @brief 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, $mode = 0) { + $schemaManager = self::getMDB2SchemaManager(); + return $schemaManager->getDbStructure($file); + } + + /** + * @brief 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; + } + + /** + * @brief update the database schema + * @param string $file file to read structure from + * @throws Exception + * @return bool + */ + public static function updateDbFromStructure($file) { + $schemaManager = self::getMDB2SchemaManager(); + try { + $result = $schemaManager->updateDbFromStructure($file); + } catch (Exception $e) { + OC_Log::write('core', 'Failed to update database structure ('.$e.')', OC_Log::FATAL); + throw $e; + } + return $result; + } + + /** + * @brief drop a table + * @param string $tableName the table to drop + */ + public static function dropTable($tableName) { + $schemaManager = self::getMDB2SchemaManager(); + $schemaManager->dropTable($tableName); + } + + /** + * 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); + } + + /** + * @brief replaces the ownCloud tables with a new set + * @param $file string path to the MDB2 xml db export file + */ + public static function replaceDB( $file ) { + $schemaManager = self::getMDB2SchemaManager(); + $schemaManager->replaceDB($file); + } + + /** + * check if a result is an error, works with Doctrine + * @param mixed $result + * @return bool + */ + public static function isError($result) { + //Doctrine returns false on error (and throws an exception) + return $result === false; + } + /** + * 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 DatabaseException + */ + public static function raiseExceptionOnError($result, $message = null) { + if(self::isError($result)) { + if ($message === null) { + $message = self::getErrorMessage($result); + } else { + $message .= ', Root cause:' . self::getErrorMessage($result); + } + throw new DatabaseException($message, self::getErrorCode($result)); + } + } + + public static function getErrorCode($error) { + $code = self::$connection->errorCode(); + return $code; + } + /** + * returns the error code and message as a string for logging + * works with DoctrineException + * @param mixed $error + * @return string + */ + public static function getErrorMessage($error) { + if (self::$connection) { + return self::$connection->getError(); + } + return ''; + } + + /** + * @param bool $enabled + */ + static public function enableCaching($enabled) { + if ($enabled) { + self::$connection->enableQueryStatementCaching(); + } else { + self::$connection->disableQueryStatementCaching(); + } + } +} diff --git a/lib/private/db/adapter.php b/lib/private/db/adapter.php new file mode 100644 index 00000000000..6b31f37dd98 --- /dev/null +++ b/lib/private/db/adapter.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\DB; + +/** + * This handles the way we use to write queries, into something that can be + * handled by the database abstraction layer. + */ +class Adapter { + + /** + * @var \OC\DB\Connection $conn + */ + protected $conn; + + public function __construct($conn) { + $this->conn = $conn; + } + + /** + * @param string $table name + * @return int id of last insert statement + */ + public function lastInsertId($table) { + return $this->conn->realLastInsertId($table); + } + + /** + * @param string $statement that needs to be changed so the db can handle it + * @return string changed statement + */ + public function fixupStatement($statement) { + return $statement; + } + + /** + * @brief insert the @input values when they do not exist yet + * @param string $table name + * @param array $input key->value pairs + * @return int count of inserted rows + */ + public function insertIfNotExist($table, $input) { + $query = 'INSERT INTO `' .$table . '` (`' + . implode('`,`', array_keys($input)) . '`) SELECT ' + . str_repeat('?,', count($input)-1).'? ' // Is there a prettier alternative? + . 'FROM `' . $table . '` WHERE '; + + foreach($input as $key => $value) { + $query .= '`' . $key . '` = ? AND '; + } + $query = substr($query, 0, strlen($query) - 5); + $query .= ' HAVING COUNT(*) = 0'; + $inserts = array_values($input); + $inserts = array_merge($inserts, $inserts); + + try { + return $this->conn->executeUpdate($query, $inserts); + } catch(\Doctrine\DBAL\DBALException $e) { + $entry = 'DB Error: "'.$e->getMessage() . '"<br />'; + $entry .= 'Offending command was: ' . $query.'<br />'; + \OC_Log::write('core', $entry, \OC_Log::FATAL); + error_log('DB error: ' . $entry); + \OC_Template::printErrorPage( $entry ); + } + } +} diff --git a/lib/private/db/adapteroci8.php b/lib/private/db/adapteroci8.php new file mode 100644 index 00000000000..bc226e979ec --- /dev/null +++ b/lib/private/db/adapteroci8.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + + +namespace OC\DB; + +class AdapterOCI8 extends Adapter { + public function lastInsertId($table) { + if($table !== null) { + $suffix = '_SEQ'; + $table = '"'.$table.$suffix.'"'; + } + return $this->conn->realLastInsertId($table); + } + + const UNIX_TIMESTAMP_REPLACEMENT = "(cast(sys_extract_utc(systimestamp) as date) - date'1970-01-01') * 86400"; + public function fixupStatement($statement) { + $statement = str_replace( '`', '"', $statement ); + $statement = str_ireplace( 'NOW()', 'CURRENT_TIMESTAMP', $statement ); + $statement = str_ireplace( 'UNIX_TIMESTAMP()', self::UNIX_TIMESTAMP_REPLACEMENT, $statement ); + return $statement; + } +} diff --git a/lib/private/db/adapterpgsql.php b/lib/private/db/adapterpgsql.php new file mode 100644 index 00000000000..990d71c9f29 --- /dev/null +++ b/lib/private/db/adapterpgsql.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + + +namespace OC\DB; + +class AdapterPgSql extends Adapter { + public function lastInsertId($table) { + return $this->conn->fetchColumn('SELECT lastval()'); + } + + const UNIX_TIMESTAMP_REPLACEMENT = 'cast(extract(epoch from current_timestamp) as integer)'; + public function fixupStatement($statement) { + $statement = str_replace( '`', '"', $statement ); + $statement = str_ireplace( 'UNIX_TIMESTAMP()', self::UNIX_TIMESTAMP_REPLACEMENT, $statement ); + return $statement; + } +} diff --git a/lib/private/db/adaptersqlite.php b/lib/private/db/adaptersqlite.php new file mode 100644 index 00000000000..fa6d308ae32 --- /dev/null +++ b/lib/private/db/adaptersqlite.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + + +namespace OC\DB; + +class AdapterSqlite extends Adapter { + public function fixupStatement($statement) { + $statement = str_replace( '`', '"', $statement ); + $statement = str_ireplace( 'NOW()', 'datetime(\'now\')', $statement ); + $statement = str_ireplace( 'UNIX_TIMESTAMP()', 'strftime(\'%s\',\'now\')', $statement ); + return $statement; + } + + public function insertIfNotExist($table, $input) { + // NOTE: For SQLite we have to use this clumsy approach + // otherwise all fieldnames used must have a unique key. + $query = 'SELECT COUNT(*) FROM `' . $table . '` WHERE '; + foreach($input as $key => $value) { + $query .= '`' . $key . '` = ? AND '; + } + $query = substr($query, 0, strlen($query) - 5); + try { + $stmt = $this->conn->prepare($query); + $result = $stmt->execute(array_values($input)); + } catch(\Doctrine\DBAL\DBALException $e) { + $entry = 'DB Error: "'.$e->getMessage() . '"<br />'; + $entry .= 'Offending command was: ' . $query . '<br />'; + \OC_Log::write('core', $entry, \OC_Log::FATAL); + error_log('DB error: '.$entry); + \OC_Template::printErrorPage( $entry ); + } + + if ($stmt->fetchColumn() === '0') { + $query = 'INSERT INTO `' . $table . '` (`' + . implode('`,`', array_keys($input)) . '`) VALUES(' + . str_repeat('?,', count($input)-1).'? ' . ')'; + } else { + return 0; //no rows updated + } + + try { + $statement = $this->conn->prepare($query); + $result = $statement->execute(array_values($input)); + } catch(\Doctrine\DBAL\DBALException $e) { + $entry = 'DB Error: "'.$e->getMessage() . '"<br />'; + $entry .= 'Offending command was: ' . $query.'<br />'; + \OC_Log::write('core', $entry, \OC_Log::FATAL); + error_log('DB error: ' . $entry); + \OC_Template::printErrorPage( $entry ); + } + + return $result; + } +} diff --git a/lib/private/db/adaptersqlsrv.php b/lib/private/db/adaptersqlsrv.php new file mode 100644 index 00000000000..d0a67af28a7 --- /dev/null +++ b/lib/private/db/adaptersqlsrv.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + + +namespace OC\DB; + +class AdapterSQLSrv extends Adapter { + public function lastInsertId($table) { + if($table !== null) { + $table = $this->conn->replaceTablePrefix( $table ); + } + return $this->conn->lastInsertId($table); + } + + public function fixupStatement($statement) { + $statement = preg_replace( "/\`(.*?)`/", "[$1]", $statement ); + $statement = str_ireplace( 'NOW()', 'CURRENT_TIMESTAMP', $statement ); + $statement = str_replace( 'LENGTH(', 'LEN(', $statement ); + $statement = str_replace( 'SUBSTR(', 'SUBSTRING(', $statement ); + $statement = str_ireplace( 'UNIX_TIMESTAMP()', 'DATEDIFF(second,{d \'1970-01-01\'},GETDATE())', $statement ); + return $statement; + } +} diff --git a/lib/private/db/connection.php b/lib/private/db/connection.php new file mode 100644 index 00000000000..2d3193a148a --- /dev/null +++ b/lib/private/db/connection.php @@ -0,0 +1,197 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\DB; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Configuration; +use Doctrine\DBAL\Cache\QueryCacheProfile; +use Doctrine\Common\EventManager; + +class Connection extends \Doctrine\DBAL\Connection implements \OCP\IDBConnection { + /** + * @var string $tablePrefix + */ + protected $tablePrefix; + + /** + * @var \OC\DB\Adapter $adapter + */ + protected $adapter; + + /** + * @var \Doctrine\DBAL\Driver\Statement[] $preparedQueries + */ + protected $preparedQueries = array(); + + protected $cachingQueryStatementEnabled = true; + + /** + * Initializes a new instance of the Connection class. + * + * @param array $params The connection parameters. + * @param \Doctrine\DBAL\Driver $driver + * @param \Doctrine\DBAL\Configuration $config + * @param \Doctrine\Common\EventManager $eventManager + * @throws \Exception + */ + public function __construct(array $params, Driver $driver, Configuration $config = null, + EventManager $eventManager = null) + { + if (!isset($params['adapter'])) { + throw new \Exception('adapter not set'); + } + if (!isset($params['tablePrefix'])) { + throw new \Exception('tablePrefix not set'); + } + parent::__construct($params, $driver, $config, $eventManager); + $this->adapter = new $params['adapter']($this); + $this->tablePrefix = $params['tablePrefix']; + } + + /** + * Prepares an SQL statement. + * + * @param string $statement The SQL statement to prepare. + * @param int $limit + * @param int $offset + * @return \Doctrine\DBAL\Driver\Statement The prepared statement. + */ + public function prepare( $statement, $limit=null, $offset=null ) { + if ($limit === -1) { + $limit = null; + } + if (!is_null($limit)) { + $platform = $this->getDatabasePlatform(); + $statement = $platform->modifyLimitQuery($statement, $limit, $offset); + } else { + if (isset($this->preparedQueries[$statement]) && $this->cachingQueryStatementEnabled) { + return $this->preparedQueries[$statement]; + } + $origStatement = $statement; + } + $statement = $this->replaceTablePrefix($statement); + $statement = $this->adapter->fixupStatement($statement); + + if(\OC_Config::getValue( 'log_query', false)) { + \OC_Log::write('core', 'DB prepare : '.$statement, \OC_Log::DEBUG); + } + $result = parent::prepare($statement); + if (is_null($limit) && $this->cachingQueryStatementEnabled) { + $this->preparedQueries[$origStatement] = $result; + } + return $result; + } + + /** + * Executes an, optionally parameterized, SQL query. + * + * If the query is parameterized, a prepared statement is used. + * If an SQLLogger is configured, the execution is logged. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters to bind to the query, if any. + * @param array $types The types the previous parameters are in. + * @param QueryCacheProfile $qcp + * @return \Doctrine\DBAL\Driver\Statement The executed statement. + * @internal PERF: Directly prepares a driver statement, not a wrapper. + */ + public function executeQuery($query, array $params = array(), $types = array(), QueryCacheProfile $qcp = null) + { + $query = $this->replaceTablePrefix($query); + $query = $this->adapter->fixupStatement($query); + return parent::executeQuery($query, $params, $types, $qcp); + } + + /** + * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters + * and returns the number of affected rows. + * + * This method supports PDO binding types as well as DBAL mapping types. + * + * @param string $query The SQL query. + * @param array $params The query parameters. + * @param array $types The parameter types. + * @return integer The number of affected rows. + * @internal PERF: Directly prepares a driver statement, not a wrapper. + */ + public function executeUpdate($query, array $params = array(), array $types = array()) + { + $query = $this->replaceTablePrefix($query); + $query = $this->adapter->fixupStatement($query); + return parent::executeUpdate($query, $params, $types); + } + + /** + * Returns the ID of the last inserted row, or the last value from a sequence object, + * depending on the underlying driver. + * + * Note: This method may not return a meaningful or consistent result across different drivers, + * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY + * columns or sequences. + * + * @param string $seqName Name of the sequence object from which the ID should be returned. + * @return string A string representation of the last inserted ID. + */ + public function lastInsertId($seqName = null) + { + if ($seqName) { + $seqName = $this->replaceTablePrefix($seqName); + } + return $this->adapter->lastInsertId($seqName); + } + + // internal use + public function realLastInsertId($seqName = null) + { + return parent::lastInsertId($seqName); + } + + /** + * @brief Insert a row if a matching row doesn't exists. + * @param string $table. The table to insert into in the form '*PREFIX*tableName' + * @param array $input. An array of fieldname/value pairs + * @return bool The return value from execute() + */ + public function insertIfNotExist($table, $input) { + return $this->adapter->insertIfNotExist($table, $input); + } + + /** + * returns the error code and message as a string for logging + * works with DoctrineException + * @return string + */ + public function getError() { + $msg = $this->errorCode() . ': '; + $errorInfo = $this->errorInfo(); + if (is_array($errorInfo)) { + $msg .= 'SQLSTATE = '.$errorInfo[0] . ', '; + $msg .= 'Driver Code = '.$errorInfo[1] . ', '; + $msg .= 'Driver Message = '.$errorInfo[2]; + } + return $msg; + } + + // internal use + /** + * @param string $statement + * @return string + */ + protected function replaceTablePrefix($statement) { + return str_replace( '*PREFIX*', $this->tablePrefix, $statement ); + } + + public function enableQueryStatementCaching() { + $this->cachingQueryStatementEnabled = true; + } + + public function disableQueryStatementCaching() { + $this->cachingQueryStatementEnabled = false; + $this->preparedQueries = array(); + } +} diff --git a/lib/private/db/mdb2schemamanager.php b/lib/private/db/mdb2schemamanager.php new file mode 100644 index 00000000000..8e76f46c78f --- /dev/null +++ b/lib/private/db/mdb2schemamanager.php @@ -0,0 +1,150 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\DB; + +class MDB2SchemaManager { + /** + * @var \OC\DB\Connection $conn + */ + protected $conn; + + /** + * @param \OC\DB\Connection $conn + */ + public function __construct($conn) { + $this->conn = $conn; + } + + /** + * @brief saves database scheme to xml file + * @param string $file name of file + * @param int|string $mode + * @return bool + * + * TODO: write more documentation + */ + public function getDbStructure( $file, $mode = MDB2_SCHEMA_DUMP_STRUCTURE) { + $sm = $this->conn->getSchemaManager(); + + return \OC_DB_MDB2SchemaWriter::saveSchemaToFile($file, $sm); + } + + /** + * @brief Creates tables from XML file + * @param string $file file to read structure from + * @return bool + * + * TODO: write more documentation + */ + public function createDbFromStructure( $file ) { + $schemaReader = new MDB2SchemaReader(\OC_Config::getObject(), $this->conn->getDatabasePlatform()); + $toSchema = $schemaReader->loadSchemaFromFile($file); + return $this->executeSchemaChange($toSchema); + } + + /** + * @brief update the database scheme + * @param string $file file to read structure from + * @return bool + */ + public function updateDbFromStructure($file) { + $sm = $this->conn->getSchemaManager(); + $fromSchema = $sm->createSchema(); + + $schemaReader = new MDB2SchemaReader(\OC_Config::getObject(), $this->conn->getDatabasePlatform()); + $toSchema = $schemaReader->loadSchemaFromFile($file); + + // remove tables we don't know about + foreach($fromSchema->getTables() as $table) { + if (!$toSchema->hasTable($table->getName())) { + $fromSchema->dropTable($table->getName()); + } + } + // remove sequences we don't know about + foreach($fromSchema->getSequences() as $table) { + if (!$toSchema->hasSequence($table->getName())) { + $fromSchema->dropSequence($table->getName()); + } + } + + $comparator = new \Doctrine\DBAL\Schema\Comparator(); + $schemaDiff = $comparator->compare($fromSchema, $toSchema); + + $platform = $this->conn->getDatabasePlatform(); + $tables = $schemaDiff->newTables + $schemaDiff->changedTables + $schemaDiff->removedTables; + foreach($tables as $tableDiff) { + $tableDiff->name = $platform->quoteIdentifier($tableDiff->name); + } + + return $this->executeSchemaChange($schemaDiff); + } + + /** + * @brief drop a table + * @param string $tableName the table to drop + */ + public function dropTable($tableName) { + $sm = $this->conn->getSchemaManager(); + $fromSchema = $sm->createSchema(); + $toSchema = clone $fromSchema; + $toSchema->dropTable($tableName); + $sql = $fromSchema->getMigrateToSql($toSchema, $this->conn->getDatabasePlatform()); + $this->conn->executeQuery($sql); + } + + /** + * remove all tables defined in a database structure xml file + * @param string $file the xml file describing the tables + */ + public function removeDBStructure($file) { + $schemaReader = new MDB2SchemaReader(\OC_Config::getObject(), $this->conn->getDatabasePlatform()); + $fromSchema = $schemaReader->loadSchemaFromFile($file); + $toSchema = clone $fromSchema; + foreach($toSchema->getTables() as $table) { + $toSchema->dropTable($table->getName()); + } + $comparator = new \Doctrine\DBAL\Schema\Comparator(); + $schemaDiff = $comparator->compare($fromSchema, $toSchema); + $this->executeSchemaChange($schemaDiff); + } + + /** + * @brief replaces the ownCloud tables with a new set + * @param $file string path to the MDB2 xml db export file + */ + public function replaceDB( $file ) { + $apps = \OC_App::getAllApps(); + $this->conn->beginTransaction(); + // Delete the old tables + $this->removeDBStructure( \OC::$SERVERROOT . '/db_structure.xml' ); + + foreach($apps as $app) { + $path = \OC_App::getAppPath($app).'/appinfo/database.xml'; + if(file_exists($path)) { + $this->removeDBStructure( $path ); + } + } + + // Create new tables + $this->conn->commit(); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + * @return bool + */ + private function executeSchemaChange($schema) { + $this->conn->beginTransaction(); + foreach($schema->toSql($this->conn->getDatabasePlatform()) as $sql) { + $this->conn->query($sql); + } + $this->conn->commit(); + return true; + } +} diff --git a/lib/private/db/mdb2schemareader.php b/lib/private/db/mdb2schemareader.php new file mode 100644 index 00000000000..b7128a2f176 --- /dev/null +++ b/lib/private/db/mdb2schemareader.php @@ -0,0 +1,305 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\DB; + +class MDB2SchemaReader { + /** + * @var string $DBNAME + */ + protected $DBNAME; + + /** + * @var string $DBTABLEPREFIX + */ + protected $DBTABLEPREFIX; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + protected $platform; + + /** + * @param \OC\Config $config + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct($config, $platform) { + $this->platform = $platform; + $this->DBNAME = $config->getValue('dbname', 'owncloud'); + $this->DBTABLEPREFIX = $config->getValue('dbtableprefix', 'oc_'); + } + + /** + * @param string $file + * @return \Doctrine\DBAL\Schema\Schema + * @throws \DomainException + */ + public function loadSchemaFromFile($file) { + $schema = new \Doctrine\DBAL\Schema\Schema(); + $xml = simplexml_load_file($file); + foreach ($xml->children() as $child) { + /** + * @var \SimpleXMLElement $child + */ + switch ($child->getName()) { + case 'name': + case 'create': + case 'overwrite': + case 'charset': + break; + case 'table': + $this->loadTable($schema, $child); + break; + default: + throw new \DomainException('Unknown element: ' . $child->getName()); + + } + } + return $schema; + } + + /** + * @param\Doctrine\DBAL\Schema\Schema $schema + * @param \SimpleXMLElement $xml + * @throws \DomainException + */ + private function loadTable($schema, $xml) { + $table = null; + foreach ($xml->children() as $child) { + /** + * @var \SimpleXMLElement $child + */ + switch ($child->getName()) { + case 'name': + $name = (string)$child; + $name = str_replace('*dbprefix*', $this->DBTABLEPREFIX, $name); + $name = $this->platform->quoteIdentifier($name); + $table = $schema->createTable($name); + break; + case 'create': + case 'overwrite': + case 'charset': + break; + case 'declaration': + if (is_null($table)) { + throw new \DomainException('Table declaration before table name'); + } + $this->loadDeclaration($table, $child); + break; + default: + throw new \DomainException('Unknown element: ' . $child->getName()); + + } + } + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \SimpleXMLElement $xml + * @throws \DomainException + */ + private function loadDeclaration($table, $xml) { + foreach ($xml->children() as $child) { + /** + * @var \SimpleXMLElement $child + */ + switch ($child->getName()) { + case 'field': + $this->loadField($table, $child); + break; + case 'index': + $this->loadIndex($table, $child); + break; + default: + throw new \DomainException('Unknown element: ' . $child->getName()); + + } + } + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \SimpleXMLElement $xml + * @throws \DomainException + */ + private function loadField($table, $xml) { + $options = array(); + foreach ($xml->children() as $child) { + /** + * @var \SimpleXMLElement $child + */ + switch ($child->getName()) { + case 'name': + $name = (string)$child; + $name = $this->platform->quoteIdentifier($name); + break; + case 'type': + $type = (string)$child; + switch ($type) { + case 'text': + $type = 'string'; + break; + case 'clob': + $type = 'text'; + break; + case 'timestamp': + $type = 'datetime'; + break; + } + break; + case 'length': + $length = (string)$child; + $options['length'] = $length; + break; + case 'unsigned': + $unsigned = $this->asBool($child); + $options['unsigned'] = $unsigned; + break; + case 'notnull': + $notnull = $this->asBool($child); + $options['notnull'] = $notnull; + break; + case 'autoincrement': + $autoincrement = $this->asBool($child); + $options['autoincrement'] = $autoincrement; + break; + case 'default': + $default = (string)$child; + $options['default'] = $default; + break; + case 'comments': + $comment = (string)$child; + $options['comment'] = $comment; + break; + case 'primary': + $primary = $this->asBool($child); + $options['primary'] = $primary; + break; + default: + throw new \DomainException('Unknown element: ' . $child->getName()); + + } + } + if (isset($name) && isset($type)) { + if (empty($options['default'])) { + if (empty($options['notnull']) || !$options['notnull']) { + unset($options['default']); + $options['notnull'] = false; + } else { + $options['default'] = ''; + } + if ($type == 'integer') { + $options['default'] = 0; + } elseif ($type == 'boolean') { + $options['default'] = false; + } + if (!empty($options['autoincrement']) && $options['autoincrement']) { + unset($options['default']); + } + } + if ($type === 'integer' && isset($options['default'])) { + $options['default'] = (int)$options['default']; + } + if ($type === 'integer' && isset($options['length'])) { + $length = $options['length']; + if ($length < 4) { + $type = 'smallint'; + } else if ($length > 4) { + $type = 'bigint'; + } + } + if ($type === 'boolean' && isset($options['default'])) { + $options['default'] = $this->asBool($options['default']); + } + if (!empty($options['autoincrement']) + && !empty($options['notnull']) + ) { + $options['primary'] = true; + } + $table->addColumn($name, $type, $options); + if (!empty($options['primary']) && $options['primary']) { + $table->setPrimaryKey(array($name)); + } + } + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \SimpleXMLElement $xml + * @throws \DomainException + */ + private function loadIndex($table, $xml) { + $name = null; + $fields = array(); + foreach ($xml->children() as $child) { + /** + * @var \SimpleXMLElement $child + */ + switch ($child->getName()) { + case 'name': + $name = (string)$child; + break; + case 'primary': + $primary = $this->asBool($child); + break; + case 'unique': + $unique = $this->asBool($child); + break; + case 'field': + foreach ($child->children() as $field) { + /** + * @var \SimpleXMLElement $field + */ + switch ($field->getName()) { + case 'name': + $field_name = (string)$field; + $field_name = $this->platform->quoteIdentifier($field_name); + $fields[] = $field_name; + break; + case 'sorting': + break; + default: + throw new \DomainException('Unknown element: ' . $field->getName()); + + } + } + break; + default: + throw new \DomainException('Unknown element: ' . $child->getName()); + + } + } + if (!empty($fields)) { + if (isset($primary) && $primary) { + $table->setPrimaryKey($fields, $name); + } else + if (isset($unique) && $unique) { + $table->addUniqueIndex($fields, $name); + } else { + $table->addIndex($fields, $name); + } + } else { + throw new \DomainException('Empty index definition: ' . $name . ' options:' . print_r($fields, true)); + } + } + + /** + * @param \SimpleXMLElement | string $xml + * @return bool + */ + private function asBool($xml) { + $result = (string)$xml; + if ($result == 'true') { + $result = true; + } elseif ($result == 'false') { + $result = false; + } + return (bool)$result; + } + +} diff --git a/lib/private/db/mdb2schemawriter.php b/lib/private/db/mdb2schemawriter.php new file mode 100644 index 00000000000..21b43cbfe80 --- /dev/null +++ b/lib/private/db/mdb2schemawriter.php @@ -0,0 +1,133 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_DB_MDB2SchemaWriter { + + /** + * @param $file + * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $sm + * @return bool + */ + static public function saveSchemaToFile($file, $sm) { + $xml = new SimpleXMLElement('<database/>'); + $xml->addChild('name', OC_Config::getValue( "dbname", "owncloud" )); + $xml->addChild('create', 'true'); + $xml->addChild('overwrite', 'false'); + $xml->addChild('charset', 'utf8'); + foreach ($sm->listTables() as $table) { + self::saveTable($table, $xml->addChild('table')); + } + file_put_contents($file, $xml->asXML()); + return true; + } + + private static function saveTable($table, $xml) { + $xml->addChild('name', $table->getName()); + $declaration = $xml->addChild('declaration'); + foreach($table->getColumns() as $column) { + self::saveColumn($column, $declaration->addChild('field')); + } + foreach($table->getIndexes() as $index) { + if ($index->getName() == 'PRIMARY') { + $autoincrement = false; + foreach($index->getColumns() as $column) { + if ($table->getColumn($column)->getAutoincrement()) { + $autoincrement = true; + } + } + if ($autoincrement) { + continue; + } + } + self::saveIndex($index, $declaration->addChild('index')); + } + } + + private static function saveColumn($column, $xml) { + $xml->addChild('name', $column->getName()); + switch($column->getType()) { + case 'SmallInt': + case 'Integer': + case 'BigInt': + $xml->addChild('type', 'integer'); + $default = $column->getDefault(); + if (is_null($default) && $column->getAutoincrement()) { + $default = '0'; + } + $xml->addChild('default', $default); + $xml->addChild('notnull', self::toBool($column->getNotnull())); + if ($column->getAutoincrement()) { + $xml->addChild('autoincrement', '1'); + } + if ($column->getUnsigned()) { + $xml->addChild('unsigned', 'true'); + } + $length = '4'; + if ($column->getType() == 'SmallInt') { + $length = '2'; + } + elseif ($column->getType() == 'BigInt') { + $length = '8'; + } + $xml->addChild('length', $length); + break; + case 'String': + $xml->addChild('type', 'text'); + $default = trim($column->getDefault()); + if ($default === '') { + $default = false; + } + $xml->addChild('default', $default); + $xml->addChild('notnull', self::toBool($column->getNotnull())); + $xml->addChild('length', $column->getLength()); + break; + case 'Text': + $xml->addChild('type', 'clob'); + $xml->addChild('notnull', self::toBool($column->getNotnull())); + break; + case 'Decimal': + $xml->addChild('type', 'decimal'); + $xml->addChild('default', $column->getDefault()); + $xml->addChild('notnull', self::toBool($column->getNotnull())); + $xml->addChild('length', '15'); + break; + case 'Boolean': + $xml->addChild('type', 'integer'); + $xml->addChild('default', $column->getDefault()); + $xml->addChild('notnull', self::toBool($column->getNotnull())); + $xml->addChild('length', '1'); + break; + case 'DateTime': + $xml->addChild('type', 'timestamp'); + $xml->addChild('default', $column->getDefault()); + $xml->addChild('notnull', self::toBool($column->getNotnull())); + break; + + } + } + + private static function saveIndex($index, $xml) { + $xml->addChild('name', $index->getName()); + if ($index->isPrimary()) { + $xml->addChild('primary', 'true'); + } + elseif ($index->isUnique()) { + $xml->addChild('unique', 'true'); + } + foreach($index->getColumns() as $column) { + $field = $xml->addChild('field'); + $field->addChild('name', $column); + $field->addChild('sorting', 'ascending'); + + } + } + + private static function toBool($bool) { + return $bool ? 'true' : 'false'; + } +} diff --git a/lib/private/db/oracleconnection.php b/lib/private/db/oracleconnection.php new file mode 100644 index 00000000000..e2fc4644f47 --- /dev/null +++ b/lib/private/db/oracleconnection.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\DB; + +class OracleConnection extends Connection { + /** + * Quote the keys of the array + */ + private function quoteKeys(array $data) { + $return = array(); + foreach($data as $key => $value) { + $return[$this->quoteIdentifier($key)] = $value; + } + return $return; + } + + /* + * {@inheritDoc} + */ + public function insert($tableName, array $data, array $types = array()) { + $tableName = $this->quoteIdentifier($tableName); + $data = $this->quoteKeys($data); + return parent::insert($tableName, $data, $types); + } + + /* + * {@inheritDoc} + */ + public function update($tableName, array $data, array $identifier, array $types = array()) { + $tableName = $this->quoteIdentifier($tableName); + $data = $this->quoteKeys($data); + $identifier = $this->quoteKeys($identifier); + return parent::update($tableName, $data, $identifier, $types); + } + + /* + * {@inheritDoc} + */ + public function delete($tableName, array $identifier) { + $tableName = $this->quoteIdentifier($tableName); + $identifier = $this->quoteKeys($identifier); + return parent::delete($tableName, $identifier); + } +} diff --git a/lib/private/db/statementwrapper.php b/lib/private/db/statementwrapper.php new file mode 100644 index 00000000000..b8da1afc0e5 --- /dev/null +++ b/lib/private/db/statementwrapper.php @@ -0,0 +1,191 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * small wrapper around \Doctrine\DBAL\Driver\Statement to make it behave, more like an MDB2 Statement + */ +class OC_DB_StatementWrapper { + /** + * @var \Doctrine\DBAL\Driver\Statement + */ + private $statement = null; + private $isManipulation = false; + private $lastArguments = array(); + + public function __construct($statement, $isManipulation) { + $this->statement = $statement; + $this->isManipulation = $isManipulation; + } + + /** + * pass all other function directly to the \Doctrine\DBAL\Driver\Statement + */ + public function __call($name,$arguments) { + return call_user_func_array(array($this->statement,$name), $arguments); + } + + /** + * provide numRows + */ + public function numRows() { + $type = OC_Config::getValue( "dbtype", "sqlite" ); + if ($type == 'oci') { + // OCI doesn't have a queryString, just do a rowCount for now + return $this->statement->rowCount(); + } + $regex = '/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/i'; + $queryString = $this->statement->getWrappedStatement()->queryString; + if (preg_match($regex, $queryString, $output) > 0) { + $query = OC_DB::prepare("SELECT COUNT(*) FROM {$output[1]}"); + return $query->execute($this->lastArguments)->fetchColumn(); + }else{ + return $this->statement->rowCount(); + } + } + + /** + * make execute return the result instead of a bool + */ + public function execute($input=array()) { + if(OC_Config::getValue( "log_query", false)) { + $params_str = str_replace("\n", " ", var_export($input, true)); + OC_Log::write('core', 'DB execute with arguments : '.$params_str, OC_Log::DEBUG); + } + $this->lastArguments = $input; + if (count($input) > 0) { + + if (!isset($type)) { + $type = OC_Config::getValue( "dbtype", "sqlite" ); + } + + if ($type == 'mssql') { + $input = $this->tryFixSubstringLastArgumentDataForMSSQL($input); + } + + $result = $this->statement->execute($input); + } else { + $result = $this->statement->execute(); + } + + if ($result === false) { + return false; + } + if ($this->isManipulation) { + return $this->statement->rowCount(); + } else { + return $this; + } + } + + private function tryFixSubstringLastArgumentDataForMSSQL($input) { + $query = $this->statement->getWrappedStatement()->queryString; + $pos = stripos ($query, 'SUBSTRING'); + + if ( $pos === false) { + return $input; + } + + try { + $newQuery = ''; + + $cArg = 0; + + $inSubstring = false; + + // Create new query + for ($i = 0; $i < strlen ($query); $i++) { + if ($inSubstring == false) { + // Defines when we should start inserting values + if (substr ($query, $i, 9) == 'SUBSTRING') { + $inSubstring = true; + } + } else { + // Defines when we should stop inserting values + if (substr ($query, $i, 1) == ')') { + $inSubstring = false; + } + } + + if (substr ($query, $i, 1) == '?') { + // We found a question mark + if ($inSubstring) { + $newQuery .= $input[$cArg]; + + // + // Remove from input array + // + array_splice ($input, $cArg, 1); + } else { + $newQuery .= substr ($query, $i, 1); + $cArg++; + } + } else { + $newQuery .= substr ($query, $i, 1); + } + } + + // The global data we need + $name = OC_Config::getValue( "dbname", "owncloud" ); + $host = OC_Config::getValue( "dbhost", "" ); + $user = OC_Config::getValue( "dbuser", "" ); + $pass = OC_Config::getValue( "dbpassword", "" ); + if (strpos($host, ':')) { + list($host, $port) = explode(':', $host, 2); + } else { + $port = false; + } + $opts = array(); + + if ($port) { + $dsn = 'sqlsrv:Server='.$host.','.$port.';Database='.$name; + } else { + $dsn = 'sqlsrv:Server='.$host.';Database='.$name; + } + + $PDO = new PDO($dsn, $user, $pass, $opts); + $PDO->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + $PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $this->statement = $PDO->prepare($newQuery); + + $this->lastArguments = $input; + + return $input; + } catch (PDOException $e){ + $entry = 'PDO DB Error: "'.$e->getMessage().'"<br />'; + $entry .= 'Offending command was: '.$this->statement->queryString .'<br />'; + $entry .= 'Input parameters: ' .print_r($input, true).'<br />'; + $entry .= 'Stack trace: ' .$e->getTraceAsString().'<br />'; + OC_Log::write('core', $entry, OC_Log::FATAL); + OC_User::setUserId(null); + + // send http status 503 + header('HTTP/1.1 503 Service Temporarily Unavailable'); + header('Status: 503 Service Temporarily Unavailable'); + OC_Template::printErrorPage('Failed to connect to database'); + die ($entry); + } + } + + /** + * provide an alias for fetch + */ + public function fetchRow() { + return $this->statement->fetch(); + } + + /** + * Provide a simple fetchOne. + * fetch single column from the next row + * @param int $colnum the column number to fetch + * @return string + */ + public function fetchOne($colnum = 0) { + return $this->statement->fetchColumn($colnum); + } +} diff --git a/lib/private/defaults.php b/lib/private/defaults.php new file mode 100644 index 00000000000..10813a3e8d8 --- /dev/null +++ b/lib/private/defaults.php @@ -0,0 +1,135 @@ +<?php + +/** + * Default strings and values which differ between the enterprise and the + * community edition. Use the get methods to always get the right strings. + */ + + +if (file_exists(OC::$SERVERROOT . '/themes/' . OC_Util::getTheme() . '/defaults.php')) { + require_once 'themes/' . OC_Util::getTheme() . '/defaults.php'; +} + +class OC_Defaults { + + private $theme; + + private $defaultEntity; + private $defaultName; + private $defaultTitle; + private $defaultBaseUrl; + private $defaultSyncClientUrl; + private $defaultDocBaseUrl; + private $defaultSlogan; + private $defaultLogoClaim; + + function __construct() { + $l = OC_L10N::get('core'); + + $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 = "http://owncloud.org"; + $this->defaultSyncClientUrl = " http://owncloud.org/sync-clients/"; + $this->defaultDocBaseUrl = "http://doc.owncloud.org"; + $this->defaultSlogan = $l->t("web services under your control"); + $this->defaultLogoClaim = ""; + + if (class_exists("OC_Theme")) { + $this->theme = new OC_Theme(); + } + } + + private function themeExist($method) { + if (OC_Util::getTheme() !== '' && method_exists('OC_Theme', $method)) { + return true; + } + return false; + } + + public function getBaseUrl() { + if ($this->themeExist('getBaseUrl')) { + return $this->theme->getBaseUrl(); + } else { + return $this->defaultBaseUrl; + } + } + + public function getSyncClientUrl() { + if ($this->themeExist('getSyncClientUrl')) { + return $this->theme->getSyncClientUrl(); + } else { + return $this->defaultSyncClientUrl; + } + } + + public function getDocBaseUrl() { + if ($this->themeExist('getDocBaseUrl')) { + return $this->theme->getDocBaseUrl(); + } else { + return $this->defaultDocBaseUrl; + } + } + + public function getTitle() { + if ($this->themeExist('getTitle')) { + return $this->theme->getTitle(); + } else { + return $this->defaultTitle; + } + } + + public function getName() { + if ($this->themeExist('getName')) { + return $this->theme->getName(); + } else { + return $this->defaultName; + } + } + + public function getEntity() { + if ($this->themeExist('getEntity')) { + return $this->theme->getEntity(); + } else { + return $this->defaultEntity; + } + } + + public function getSlogan() { + if ($this->themeExist('getSlogan')) { + return $this->theme->getSlogan(); + } else { + return $this->defaultSlogan; + } + } + + public function getLogoClaim() { + if ($this->themeExist('getLogoClaim')) { + return $this->theme->getLogoClaim(); + } else { + return $this->defaultLogoClaim; + } + } + + public function getShortFooter() { + if ($this->themeExist('getShortFooter')) { + $footer = $this->theme->getShortFooter(); + } else { + $footer = "<a href=\"". $this->getBaseUrl() . "\" target=\"_blank\">" .$this->getEntity() . "</a>". + ' – ' . $this->getSlogan(); + } + + return $footer; + } + + public function getLongFooter() { + if ($this->themeExist('getLongFooter')) { + $footer = $this->theme->getLongFooter(); + } else { + $footer = $this->getShortFooter(); + } + + return $footer; + } + +} diff --git a/lib/private/eventsource.php b/lib/private/eventsource.php new file mode 100644 index 00000000000..a83084d9251 --- /dev/null +++ b/lib/private/eventsource.php @@ -0,0 +1,85 @@ +<?php + +/** +* ownCloud +* +* @author Robin Appelman +* @copyright 2012 Robin Appelman icewind1991@gmail.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +/** + * 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{ + private $fallback; + private $fallBackId=0; + + public function __construct() { + OC_Util::obEnd(); + header('Cache-Control: no-cache'); + $this->fallback=isset($_GET['fallback']) and $_GET['fallback']=='true'; + if($this->fallback) { + $this->fallBackId=$_GET['fallback_id']; + header("Content-Type: text/html"); + echo str_repeat('<span></span>'.PHP_EOL, 10); //dummy data to keep IE happy + }else{ + header("Content-Type: text/event-stream"); + } + if( !OC_Util::isCallRegistered()) { + $this->send('error', 'Possible CSRF attack. Connection will be closed.'); + exit(); + } + flush(); + + } + + /** + * send a message to the client + * @param string $type + * @param mixed $data + * + * if only one parameter is given, a typeless message will be send with that parameter as data + */ + public function send($type, $data=null) { + if(is_null($data)) { + $data=$type; + $type=null; + } + if($this->fallback) { + $response='<script type="text/javascript">window.parent.OC.EventSource.fallBackCallBack(' + .$this->fallBackId.',"'.$type.'",'.json_encode($data).')</script>'.PHP_EOL; + echo $response; + }else{ + if($type) { + echo 'event: '.$type.PHP_EOL; + } + echo 'data: '.json_encode($data).PHP_EOL; + } + echo PHP_EOL; + flush(); + } + + /** + * close the connection of the even 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 new file mode 100644 index 00000000000..313a6ee87d2 --- /dev/null +++ b/lib/private/filechunking.php @@ -0,0 +1,149 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + + +class OC_FileChunking { + protected $info; + protected $cache; + + static public function decodeName($name) { + preg_match('/(?P<name>.*)-chunking-(?P<transferid>\d+)-(?P<chunkcount>\d+)-(?P<index>\d+)/', $name, $matches); + return $matches; + } + + 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; + } + + public function store($index, $data) { + $cache = $this->getCache(); + $name = $this->getPrefix().$index; + $cache->set($name, $data); + } + + public function isComplete() { + $prefix = $this->getPrefix(); + $parts = 0; + $cache = $this->getCache(); + for($i=0; $i < $this->info['chunkcount']; $i++) { + if ($cache->hasKey($prefix.$i)) { + $parts ++; + } + } + return $parts == $this->info['chunkcount']; + } + + 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); + $cache->remove($prefix.$i); + $count += fwrite($f, $chunk); + } + return $count; + } + + public function signature_split($orgfile, $input) { + $info = unpack('n', fread($input, 2)); + $blocksize = $info[1]; + $this->info['transferid'] = mt_rand(); + $count = 0; + $needed = array(); + $cache = $this->getCache(); + $prefix = $this->getPrefix(); + while (!feof($orgfile)) { + $new_md5 = fread($input, 16); + if (feof($input)) { + break; + } + $data = fread($orgfile, $blocksize); + $org_md5 = md5($data, true); + if ($org_md5 == $new_md5) { + $cache->set($prefix.$count, $data); + } else { + $needed[] = $count; + } + $count++; + } + return array( + 'transferid' => $this->info['transferid'], + 'needed' => $needed, + 'count' => $count, + ); + } + + public function file_assemble($path) { + $absolutePath = \OC\Files\Filesystem::normalizePath(\OC\Files\Filesystem::getView()->getAbsolutePath($path)); + $data = ''; + // use file_put_contents as method because that best matches what this function does + if (OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data) + && \OC\Files\Filesystem::isValidPath($path)) { + $path = \OC\Files\Filesystem::getView()->getRelativePath($absolutePath); + $exists = \OC\Files\Filesystem::file_exists($path); + $run = true; + if(!$exists) { + OC_Hook::emit( + \OC\Files\Filesystem::CLASSNAME, + \OC\Files\Filesystem::signal_create, + array( + \OC\Files\Filesystem::signal_param_path => $path, + \OC\Files\Filesystem::signal_param_run => &$run + ) + ); + } + OC_Hook::emit( + \OC\Files\Filesystem::CLASSNAME, + \OC\Files\Filesystem::signal_write, + array( + \OC\Files\Filesystem::signal_param_path => $path, + \OC\Files\Filesystem::signal_param_run => &$run + ) + ); + if(!$run) { + return false; + } + $target = \OC\Files\Filesystem::fopen($path, 'w'); + if($target) { + $count = $this->assemble($target); + fclose($target); + if(!$exists) { + OC_Hook::emit( + \OC\Files\Filesystem::CLASSNAME, + \OC\Files\Filesystem::signal_post_create, + array( \OC\Files\Filesystem::signal_param_path => $path) + ); + } + OC_Hook::emit( + \OC\Files\Filesystem::CLASSNAME, + \OC\Files\Filesystem::signal_post_write, + array( \OC\Files\Filesystem::signal_param_path => $path) + ); + OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count); + return $count > 0; + }else{ + return false; + } + } + } +} diff --git a/lib/private/fileproxy.php b/lib/private/fileproxy.php new file mode 100644 index 00000000000..52ec79b4bdb --- /dev/null +++ b/lib/private/fileproxy.php @@ -0,0 +1,115 @@ +<?php + +/** +* ownCloud +* +* @author Robin Appelman +* @copyright 2011 Robin Appelman icewind1991@gmail.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +/** + * Class for manipulating filesystem requests + * + * Manipulation happens by using 2 kind of proxy operations, pre and post proxies + * that manipulate the filesystem call and the result of the call respectively + * + * A pre-proxy recieves the filepath as arugments (or 2 filespaths in case of + * operations like copy or move) and return a boolean + * If a pre-proxy returns false the file operation will be canceled + * All filesystem operations have a pre-proxy + * + * A post-proxy recieves 2 arguments, the filepath and the result of the operation. + * The return value of the post-proxy will be used as the new result of the operation + * The operations that have a post-proxy are: + * file_get_contents, is_file, is_dir, file_exists, stat, is_readable, + * is_writable, filemtime, filectime, file_get_contents, + * getMimeType, hash, fopen, free_space and search + */ + +class OC_FileProxy{ + private static $proxies=array(); + public static $enabled=true; + + /** + * fallback function when a proxy operation is not implemented + * @param string $function the name of the proxy operation + * @param mixed + * + * this implements a dummy proxy for all operations + */ + public function __call($function, $arguments) { + if(substr($function, 0, 3)=='pre') { + return true; + }else{ + return $arguments[1]; + } + } + + /** + * register a proxy to be used + * @param OC_FileProxy $proxy + */ + public static function register($proxy) { + self::$proxies[]=$proxy; + } + + public static function getProxies($operation) { + $proxies=array(); + foreach(self::$proxies as $proxy) { + if(method_exists($proxy, $operation)) { + $proxies[]=$proxy; + } + } + return $proxies; + } + + public static function runPreProxies($operation,&$filepath,&$filepath2=null) { + if(!self::$enabled) { + return true; + } + $operation='pre'.$operation; + $proxies=self::getProxies($operation); + foreach($proxies as $proxy) { + if(!is_null($filepath2)) { + if($proxy->$operation($filepath, $filepath2)===false) { + return false; + } + }else{ + if($proxy->$operation($filepath)===false) { + return false; + } + } + } + return true; + } + + public static function runPostProxies($operation, $path, $result) { + if(!self::$enabled) { + return $result; + } + $operation='post'.$operation; + $proxies=self::getProxies($operation); + foreach($proxies as $proxy) { + $result=$proxy->$operation($path, $result); + } + return $result; + } + + public static function clearProxies() { + self::$proxies=array(); + } +} diff --git a/lib/private/fileproxy/fileoperations.php b/lib/private/fileproxy/fileoperations.php new file mode 100644 index 00000000000..b2ff2e7e5e9 --- /dev/null +++ b/lib/private/fileproxy/fileoperations.php @@ -0,0 +1,37 @@ +<?php +/** + * ownCloud + * + * @author Bjoern Schiessle + * @copyright 2012 Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * check if standard file operations + */ + +class OC_FileProxy_FileOperations extends OC_FileProxy{ + static $rootView; + + public function premkdir($path) { + if(!self::$rootView) { + self::$rootView = new \OC\Files\View(''); + } + return !self::$rootView->file_exists($path); + } + +} diff --git a/lib/private/files.php b/lib/private/files.php new file mode 100644 index 00000000000..c705d2adb1a --- /dev/null +++ b/lib/private/files.php @@ -0,0 +1,321 @@ +<?php + +/** + * ownCloud + * + * @author Frank Karlitschek + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * Class for fileserver access + * + */ +class OC_Files { + static $tmpFiles = array(); + + static public function getFileInfo($path){ + return \OC\Files\Filesystem::getFileInfo($path); + } + + static public function getDirectoryContent($path){ + return \OC\Files\Filesystem::getDirectoryContent($path); + } + + /** + * return the content of a file or return a zip file containing multiple files + * + * @param string $dir + * @param string $file ; separated list of files to download + * @param boolean $only_header ; boolean to only send header of the request + */ + public static function get($dir, $files, $only_header = false) { + $xsendfile = false; + if (isset($_SERVER['MOD_X_SENDFILE_ENABLED']) || + isset($_SERVER['MOD_X_SENDFILE2_ENABLED']) || + isset($_SERVER['MOD_X_ACCEL_REDIRECT_ENABLED'])) { + $xsendfile = true; + } + + if (is_array($files) && count($files) == 1) { + $files = $files[0]; + } + + if (is_array($files)) { + self::validateZipDownload($dir, $files); + $executionTime = intval(ini_get('max_execution_time')); + set_time_limit(0); + $zip = new ZipArchive(); + $filename = OC_Helper::tmpFile('.zip'); + if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==true) { + $l = OC_L10N::get('lib'); + throw new Exception($l->t('cannot open "%s"', array($filename))); + } + foreach ($files as $file) { + $file = $dir . '/' . $file; + if (\OC\Files\Filesystem::is_file($file)) { + $tmpFile = \OC\Files\Filesystem::toTmpFile($file); + self::$tmpFiles[] = $tmpFile; + $zip->addFile($tmpFile, basename($file)); + } elseif (\OC\Files\Filesystem::is_dir($file)) { + self::zipAddDir($file, $zip); + } + } + $zip->close(); + if ($xsendfile) { + $filename = OC_Helper::moveToNoClean($filename); + } + $basename = basename($dir); + if ($basename) { + $name = $basename . '.zip'; + } else { + $name = 'owncloud.zip'; + } + + set_time_limit($executionTime); + } elseif (\OC\Files\Filesystem::is_dir($dir . '/' . $files)) { + self::validateZipDownload($dir, $files); + $executionTime = intval(ini_get('max_execution_time')); + set_time_limit(0); + $zip = new ZipArchive(); + $filename = OC_Helper::tmpFile('.zip'); + if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==true) { + $l = OC_L10N::get('lib'); + throw new Exception($l->t('cannot open "%s"', array($filename))); + } + $file = $dir . '/' . $files; + self::zipAddDir($file, $zip); + $zip->close(); + if ($xsendfile) { + $filename = OC_Helper::moveToNoClean($filename); + } + $name = $files . '.zip'; + set_time_limit($executionTime); + } else { + $zip = false; + $filename = $dir . '/' . $files; + $name = $files; + } + OC_Util::obEnd(); + if ($zip or \OC\Files\Filesystem::isReadable($filename)) { + if ( preg_match( "/MSIE/", $_SERVER["HTTP_USER_AGENT"] ) ) { + header( 'Content-Disposition: attachment; filename="' . rawurlencode($name) . '"' ); + } else { + header( 'Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode($name) + . '; filename="' . rawurlencode($name) . '"' ); + } + header('Content-Transfer-Encoding: binary'); + OC_Response::disableCaching(); + if ($zip) { + ini_set('zlib.output_compression', 'off'); + header('Content-Type: application/zip'); + header('Content-Length: ' . filesize($filename)); + self::addSendfileHeader($filename); + }else{ + $filesize = \OC\Files\Filesystem::filesize($filename); + header('Content-Type: '.\OC\Files\Filesystem::getMimeType($filename)); + if ($filesize > -1) { + header("Content-Length: ".$filesize); + } + list($storage) = \OC\Files\Filesystem::resolvePath($filename); + if ($storage instanceof \OC\Files\Storage\Local) { + self::addSendfileHeader(\OC\Files\Filesystem::getLocalFile($filename)); + } + } + } elseif ($zip or !\OC\Files\Filesystem::file_exists($filename)) { + header("HTTP/1.0 404 Not Found"); + $tmpl = new OC_Template('', '404', 'guest'); + $tmpl->assign('file', $name); + $tmpl->printPage(); + } else { + header("HTTP/1.0 403 Forbidden"); + die('403 Forbidden'); + } + if($only_header) { + return ; + } + if ($zip) { + $handle = fopen($filename, 'r'); + if ($handle) { + $chunkSize = 8 * 1024; // 1 MB chunks + while (!feof($handle)) { + echo fread($handle, $chunkSize); + flush(); + } + } + if (!$xsendfile) { + unlink($filename); + } + }else{ + \OC\Files\Filesystem::readfile($filename); + } + foreach (self::$tmpFiles as $tmpFile) { + if (file_exists($tmpFile) and is_file($tmpFile)) { + unlink($tmpFile); + } + } + } + + private static function addSendfileHeader($filename) { + if (isset($_SERVER['MOD_X_SENDFILE_ENABLED'])) { + header("X-Sendfile: " . $filename); + } + if (isset($_SERVER['MOD_X_SENDFILE2_ENABLED'])) { + if (isset($_SERVER['HTTP_RANGE']) && + preg_match("/^bytes=([0-9]+)-([0-9]*)$/", $_SERVER['HTTP_RANGE'], $range)) { + $filelength = filesize($filename); + if ($range[2] == "") { + $range[2] = $filelength - 1; + } + header("Content-Range: bytes $range[1]-$range[2]/" . $filelength); + header("HTTP/1.1 206 Partial content"); + header("X-Sendfile2: " . str_replace(",", "%2c", rawurlencode($filename)) . " $range[1]-$range[2]"); + } else { + header("X-Sendfile: " . $filename); + } + } + + if (isset($_SERVER['MOD_X_ACCEL_REDIRECT_ENABLED'])) { + header("X-Accel-Redirect: " . $filename); + } + } + + public static function zipAddDir($dir, $zip, $internalDir='') { + $dirname=basename($dir); + $zip->addEmptyDir($internalDir.$dirname); + $internalDir.=$dirname.='/'; + $files=OC_Files::getDirectoryContent($dir); + foreach($files as $file) { + $filename=$file['name']; + $file=$dir.'/'.$filename; + if(\OC\Files\Filesystem::is_file($file)) { + $tmpFile=\OC\Files\Filesystem::toTmpFile($file); + OC_Files::$tmpFiles[]=$tmpFile; + $zip->addFile($tmpFile, $internalDir.$filename); + }elseif(\OC\Files\Filesystem::is_dir($file)) { + self::zipAddDir($file, $zip, $internalDir); + } + } + } + + /** + * checks if the selected files are within the size constraint. If not, outputs an error page. + * + * @param dir $dir + * @param files $files + */ + static function validateZipDownload($dir, $files) { + if (!OC_Config::getValue('allowZipDownload', true)) { + $l = OC_L10N::get('lib'); + header("HTTP/1.0 409 Conflict"); + OC_Template::printErrorPage( + $l->t('ZIP download is turned off.'), + $l->t('Files need to be downloaded one by one.') + . '<br/><a href="javascript:history.back()">' . $l->t('Back to Files') . '</a>' + ); + exit; + } + + $zipLimit = OC_Config::getValue('maxZipInputSize', OC_Helper::computerFileSize('800 MB')); + if ($zipLimit > 0) { + $totalsize = 0; + if(!is_array($files)) { + $files = array($files); + } + foreach ($files as $file) { + $path = $dir . '/' . $file; + if(\OC\Files\Filesystem::is_dir($path)) { + foreach (\OC\Files\Filesystem::getDirectoryContent($path) as $i) { + $totalsize += $i['size']; + } + } else { + $totalsize += \OC\Files\Filesystem::filesize($path); + } + } + if ($totalsize > $zipLimit) { + $l = OC_L10N::get('lib'); + header("HTTP/1.0 409 Conflict"); + OC_Template::printErrorPage( + $l->t('Selected files too large to generate zip file.'), + $l->t('Download the files in smaller chunks, seperately or kindly ask your administrator.') + .'<br/><a href="javascript:history.back()">' + . $l->t('Back to Files') . '</a>' + ); + exit; + } + } + } + + /** + * set the maximum upload size limit for apache hosts using .htaccess + * + * @param int size filesisze in bytes + * @return false on failure, size on success + */ + static function setUploadLimit($size) { + //don't allow user to break his config -- upper boundary + if ($size > PHP_INT_MAX) { + //max size is always 1 byte lower than computerFileSize returns + if ($size > PHP_INT_MAX + 1) + return false; + $size -= 1; + } else { + $size = OC_Helper::humanFileSize($size); + $size = substr($size, 0, -1); //strip the B + $size = str_replace(' ', '', $size); //remove the space between the size and the postfix + } + + //don't allow user to break his config -- broken or malicious size input + if (intval($size) == 0) { + return false; + } + + $htaccess = @file_get_contents(OC::$SERVERROOT . '/.htaccess'); //supress errors in case we don't have permissions for + if (!$htaccess) { + return false; + } + + $phpValueKeys = array( + 'upload_max_filesize', + 'post_max_size' + ); + + foreach ($phpValueKeys as $key) { + $pattern = '/php_value ' . $key . ' (\S)*/'; + $setting = 'php_value ' . $key . ' ' . $size; + $hasReplaced = 0; + $content = preg_replace($pattern, $setting, $htaccess, 1, $hasReplaced); + if ($content !== null) { + $htaccess = $content; + } + if ($hasReplaced == 0) { + $htaccess .= "\n" . $setting; + } + } + + //check for write permissions + if (is_writable(OC::$SERVERROOT . '/.htaccess')) { + file_put_contents(OC::$SERVERROOT . '/.htaccess', $htaccess); + return OC_Helper::computerFileSize($size); + } else { + OC_Log::write('files', + 'Can\'t write upload limit to ' . OC::$SERVERROOT . '/.htaccess. Please check the file permissions', + OC_Log::WARN); + } + return false; + } +} diff --git a/lib/private/files/cache/backgroundwatcher.php b/lib/private/files/cache/backgroundwatcher.php new file mode 100644 index 00000000000..923804f48d0 --- /dev/null +++ b/lib/private/files/cache/backgroundwatcher.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +use \OC\Files\Mount; +use \OC\Files\Filesystem; + +class BackgroundWatcher { + static $folderMimetype = null; + + static private function getFolderMimetype() { + if (!is_null(self::$folderMimetype)) { + return self::$folderMimetype; + } + $sql = 'SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = ?'; + $result = \OC_DB::executeAudited($sql, array('httpd/unix-directory')); + $row = $result->fetchRow(); + return $row['id']; + } + + static private function checkUpdate($id) { + $cacheItem = Cache::getById($id); + if (is_null($cacheItem)) { + return; + } + list($storageId, $internalPath) = $cacheItem; + $mounts = Filesystem::getMountByStorageId($storageId); + + if (count($mounts) === 0) { + //if the storage we need isn't mounted on default, try to find a user that has access to the storage + $permissionsCache = new Permissions($storageId); + $users = $permissionsCache->getUsers($id); + if (count($users) === 0) { + return; + } + Filesystem::initMountPoints($users[0]); + $mounts = Filesystem::getMountByStorageId($storageId); + if (count($mounts) === 0) { + return; + } + } + $storage = $mounts[0]->getStorage(); + $watcher = new Watcher($storage); + $watcher->checkUpdate($internalPath); + } + + /** + * get the next fileid in the cache + * + * @param int $previous + * @param bool $folder + * @return int + */ + static private function getNextFileId($previous, $folder) { + if ($folder) { + $stmt = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `fileid` > ? AND `mimetype` = ? ORDER BY `fileid` ASC', 1); + } else { + $stmt = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `fileid` > ? AND `mimetype` != ? ORDER BY `fileid` ASC', 1); + } + $result = \OC_DB::executeAudited($stmt, array($previous,self::getFolderMimetype())); + if ($row = $result->fetchRow()) { + return $row['fileid']; + } else { + return 0; + } + } + + static public function checkNext() { + // check both 1 file and 1 folder, this way new files are detected quicker because there are less folders than files usually + $previousFile = \OC_Appconfig::getValue('files', 'backgroundwatcher_previous_file', 0); + $previousFolder = \OC_Appconfig::getValue('files', 'backgroundwatcher_previous_folder', 0); + $nextFile = self::getNextFileId($previousFile, false); + $nextFolder = self::getNextFileId($previousFolder, true); + \OC_Appconfig::setValue('files', 'backgroundwatcher_previous_file', $nextFile); + \OC_Appconfig::setValue('files', 'backgroundwatcher_previous_folder', $nextFolder); + if ($nextFile > 0) { + self::checkUpdate($nextFile); + } + if ($nextFolder > 0) { + self::checkUpdate($nextFolder); + } + } + + static public function checkAll() { + $previous = 0; + $next = 1; + while ($next != 0) { + $next = self::getNextFileId($previous, true); + self::checkUpdate($next); + } + $previous = 0; + $next = 1; + while ($next != 0) { + $next = self::getNextFileId($previous, false); + self::checkUpdate($next); + } + } +} diff --git a/lib/private/files/cache/cache.php b/lib/private/files/cache/cache.php new file mode 100644 index 00000000000..e69733727af --- /dev/null +++ b/lib/private/files/cache/cache.php @@ -0,0 +1,582 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +/** + * Metadata cache for the filesystem + * + * don't use this class directly if you need to get metadata, use \OC\Files\Filesystem::getFileInfo instead + */ +class Cache { + const NOT_FOUND = 0; + const PARTIAL = 1; //only partial data available, file not cached in the database + const SHALLOW = 2; //folder in cache, but not all child files are completely scanned + const COMPLETE = 3; + + /** + * @var array partial data for the cache + */ + private $partial = array(); + + /** + * @var string + */ + private $storageId; + + /** + * @var Storage $storageCache + */ + private $storageCache; + + private $mimetypeIds = array(); + private $mimetypes = array(); + + /** + * @param \OC\Files\Storage\Storage|string $storage + */ + public function __construct($storage) { + if ($storage instanceof \OC\Files\Storage\Storage) { + $this->storageId = $storage->getId(); + } else { + $this->storageId = $storage; + } + if (strlen($this->storageId) > 64) { + $this->storageId = md5($this->storageId); + } + + $this->storageCache = new Storage($storage); + } + + public function getNumericStorageId() { + return $this->storageCache->getNumericId(); + } + + /** + * normalize mimetypes + * + * @param string $mime + * @return int + */ + public function getMimetypeId($mime) { + if (!isset($this->mimetypeIds[$mime])) { + $result = \OC_DB::executeAudited('SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = ?', array($mime)); + if ($row = $result->fetchRow()) { + $this->mimetypeIds[$mime] = $row['id']; + } else { + $result = \OC_DB::executeAudited('INSERT INTO `*PREFIX*mimetypes`(`mimetype`) VALUES(?)', array($mime)); + $this->mimetypeIds[$mime] = \OC_DB::insertid('*PREFIX*mimetypes'); + } + $this->mimetypes[$this->mimetypeIds[$mime]] = $mime; + } + return $this->mimetypeIds[$mime]; + } + + public function getMimetype($id) { + if (!isset($this->mimetypes[$id])) { + $sql = 'SELECT `mimetype` FROM `*PREFIX*mimetypes` WHERE `id` = ?'; + $result = \OC_DB::executeAudited($sql, array($id)); + if ($row = $result->fetchRow()) { + $this->mimetypes[$id] = $row['mimetype']; + } else { + return null; + } + } + return $this->mimetypes[$id]; + } + + /** + * get the stored metadata of a file or folder + * + * @param string/int $file + * @return array | false + */ + public function get($file) { + if (is_string($file) or $file == '') { + // normalize file + $file = $this->normalize($file); + + $where = 'WHERE `storage` = ? AND `path_hash` = ?'; + $params = array($this->getNumericStorageId(), md5($file)); + } else { //file id + $where = 'WHERE `fileid` = ?'; + $params = array($file); + } + $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, + `storage_mtime`, `encrypted`, `unencrypted_size`, `etag` + FROM `*PREFIX*filecache` ' . $where; + $result = \OC_DB::executeAudited($sql, $params); + $data = $result->fetchRow(); + + //FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO + //PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false + if ($data === null) { + $data = false; + } + + //merge partial data + if (!$data and is_string($file)) { + if (isset($this->partial[$file])) { + $data = $this->partial[$file]; + } + } else { + //fix types + $data['fileid'] = (int)$data['fileid']; + $data['size'] = (int)$data['size']; + $data['mtime'] = (int)$data['mtime']; + $data['encrypted'] = (bool)$data['encrypted']; + $data['unencrypted_size'] = (int)$data['unencrypted_size']; + $data['storage'] = $this->storageId; + $data['mimetype'] = $this->getMimetype($data['mimetype']); + $data['mimepart'] = $this->getMimetype($data['mimepart']); + if ($data['storage_mtime'] == 0) { + $data['storage_mtime'] = $data['mtime']; + } + } + + return $data; + } + + /** + * get the metadata of all files stored in $folder + * + * @param string $folder + * @return array + */ + public function getFolderContents($folder) { + $fileId = $this->getId($folder); + if ($fileId > -1) { + $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, + `storage_mtime`, `encrypted`, `unencrypted_size`, `etag` + FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC'; + $result = \OC_DB::executeAudited($sql,array($fileId)); + $files = $result->fetchAll(); + foreach ($files as &$file) { + $file['mimetype'] = $this->getMimetype($file['mimetype']); + $file['mimepart'] = $this->getMimetype($file['mimepart']); + if ($file['storage_mtime'] == 0) { + $file['storage_mtime'] = $file['mtime']; + } + } + return $files; + } else { + return array(); + } + } + + /** + * store meta data for a file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + */ + public function put($file, array $data) { + if (($id = $this->getId($file)) > -1) { + $this->update($id, $data); + return $id; + } else { + // normalize file + $file = $this->normalize($file); + + if (isset($this->partial[$file])) { //add any saved partial data + $data = array_merge($this->partial[$file], $data); + unset($this->partial[$file]); + } + + $requiredFields = array('size', 'mtime', 'mimetype'); + foreach ($requiredFields as $field) { + if (!isset($data[$field])) { //data not complete save as partial and return + $this->partial[$file] = $data; + return -1; + } + } + + $data['path'] = $file; + $data['parent'] = $this->getParentId($file); + $data['name'] = \OC_Util::basename($file); + + list($queryParts, $params) = $this->buildParts($data); + $queryParts[] = '`storage`'; + $params[] = $this->getNumericStorageId(); + $valuesPlaceholder = array_fill(0, count($queryParts), '?'); + + $sql = 'INSERT INTO `*PREFIX*filecache` (' . implode(', ', $queryParts) . ')' + . ' VALUES (' . implode(', ', $valuesPlaceholder) . ')'; + \OC_DB::executeAudited($sql, $params); + + return (int)\OC_DB::insertid('*PREFIX*filecache'); + } + } + + /** + * update the metadata in the cache + * + * @param int $id + * @param array $data + */ + public function update($id, array $data) { + + if(isset($data['path'])) { + // normalize path + $data['path'] = $this->normalize($data['path']); + } + + if(isset($data['name'])) { + // normalize path + $data['name'] = $this->normalize($data['name']); + } + + list($queryParts, $params) = $this->buildParts($data); + $params[] = $id; + + $sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? WHERE `fileid` = ?'; + \OC_DB::executeAudited($sql, $params); + } + + /** + * extract query parts and params array from data array + * + * @param array $data + * @return array + */ + function buildParts(array $data) { + $fields = array('path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted', 'unencrypted_size', 'etag'); + $params = array(); + $queryParts = array(); + foreach ($data as $name => $value) { + if (array_search($name, $fields) !== false) { + if ($name === 'path') { + $params[] = md5($value); + $queryParts[] = '`path_hash`'; + } elseif ($name === 'mimetype') { + $params[] = $this->getMimetypeId(substr($value, 0, strpos($value, '/'))); + $queryParts[] = '`mimepart`'; + $value = $this->getMimetypeId($value); + } elseif ($name === 'storage_mtime') { + if (!isset($data['mtime'])) { + $params[] = $value; + $queryParts[] = '`mtime`'; + } + } elseif ($name === 'encrypted') { + // Boolean to integer conversion + $value = $value ? 1 : 0; + } + $params[] = $value; + $queryParts[] = '`' . $name . '`'; + } + } + return array($queryParts, $params); + } + + /** + * get the file id for a file + * + * @param string $file + * @return int + */ + public function getId($file) { + // normalize file + $file = $this->normalize($file); + + $pathHash = md5($file); + + $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'; + $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId(), $pathHash)); + if ($row = $result->fetchRow()) { + return $row['fileid']; + } else { + return -1; + } + } + + /** + * get the id of the parent folder of a file + * + * @param string $file + * @return int + */ + public function getParentId($file) { + if ($file === '') { + return -1; + } else { + $parent = dirname($file); + if ($parent === '.') { + $parent = ''; + } + return $this->getId($parent); + } + } + + /** + * check if a file is available in the cache + * + * @param string $file + * @return bool + */ + public function inCache($file) { + return $this->getId($file) != -1; + } + + /** + * remove a file or folder from the cache + * + * @param string $file + */ + public function remove($file) { + $entry = $this->get($file); + if ($entry['mimetype'] === 'httpd/unix-directory') { + $children = $this->getFolderContents($file); + foreach ($children as $child) { + $this->remove($child['path']); + } + } + + $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?'; + \OC_DB::executeAudited($sql, array($entry['fileid'])); + + $permissionsCache = new Permissions($this->storageId); + $permissionsCache->remove($entry['fileid']); + } + + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + */ + public function move($source, $target) { + // normalize source and target + $source = $this->normalize($source); + $target = $this->normalize($target); + + $sourceData = $this->get($source); + $sourceId = $sourceData['fileid']; + $newParentId = $this->getParentId($target); + + if ($sourceData['mimetype'] === 'httpd/unix-directory') { + //find all child entries + $sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?'; + $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId(), $source . '/%')); + $childEntries = $result->fetchAll(); + $sourceLength = strlen($source); + $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ? WHERE `fileid` = ?'); + + foreach ($childEntries as $child) { + $targetPath = $target . substr($child['path'], $sourceLength); + \OC_DB::executeAudited($query, array($targetPath, md5($targetPath), $child['fileid'])); + } + } + + $sql = 'UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?'; + \OC_DB::executeAudited($sql, array($target, md5($target), basename($target), $newParentId, $sourceId)); + } + + /** + * remove all entries for files that are stored on the storage from the cache + */ + public function clear() { + $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?'; + \OC_DB::executeAudited($sql, array($this->getNumericStorageId())); + + $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?'; + \OC_DB::executeAudited($sql, array($this->storageId)); + } + + /** + * @param string $file + * + * @return int, Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE + */ + public function getStatus($file) { + // normalize file + $file = $this->normalize($file); + + $pathHash = md5($file); + $sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'; + $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId(), $pathHash)); + if ($row = $result->fetchRow()) { + if ((int)$row['size'] === -1) { + return self::SHALLOW; + } else { + return self::COMPLETE; + } + } else { + if (isset($this->partial[$file])) { + return self::PARTIAL; + } else { + return self::NOT_FOUND; + } + } + } + + /** + * search for files matching $pattern + * + * @param string $pattern + * @return array of file data + */ + public function search($pattern) { + + // normalize pattern + $pattern = $this->normalize($pattern); + + $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `unencrypted_size`, `etag` + FROM `*PREFIX*filecache` WHERE `name` LIKE ? AND `storage` = ?'; + $result = \OC_DB::executeAudited($sql, array($pattern, $this->getNumericStorageId())); + $files = array(); + while ($row = $result->fetchRow()) { + $row['mimetype'] = $this->getMimetype($row['mimetype']); + $row['mimepart'] = $this->getMimetype($row['mimepart']); + $files[] = $row; + } + return $files; + } + + /** + * search for files by mimetype + * + * @param string $mimetype + * @return array + */ + public function searchByMime($mimetype) { + if (strpos($mimetype, '/')) { + $where = '`mimetype` = ?'; + } else { + $where = '`mimepart` = ?'; + } + $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `unencrypted_size`, `etag` + FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?'; + $mimetype = $this->getMimetypeId($mimetype); + $result = \OC_DB::executeAudited($sql, array($mimetype, $this->getNumericStorageId())); + $files = array(); + while ($row = $result->fetchRow()) { + $row['mimetype'] = $this->getMimetype($row['mimetype']); + $row['mimepart'] = $this->getMimetype($row['mimepart']); + $files[] = $row; + } + return $files; + } + + /** + * update the folder size and the size of all parent folders + * + * @param $path + */ + public function correctFolderSize($path) { + $this->calculateFolderSize($path); + if ($path !== '') { + $parent = dirname($path); + if ($parent === '.' or $parent === '/') { + $parent = ''; + } + $this->correctFolderSize($parent); + } + } + + /** + * get the size of a folder and set it in the cache + * + * @param string $path + * @return int + */ + public function calculateFolderSize($path) { + $totalSize = 0; + $entry = $this->get($path); + if ($entry && $entry['mimetype'] === 'httpd/unix-directory') { + $id = $entry['fileid']; + $sql = 'SELECT SUM(`size`), MIN(`size`) FROM `*PREFIX*filecache` '. + 'WHERE `parent` = ? AND `storage` = ?'; + $result = \OC_DB::executeAudited($sql, array($id, $this->getNumericStorageId())); + if ($row = $result->fetchRow()) { + list($sum, $min) = array_values($row); + $sum = (int)$sum; + $min = (int)$min; + if ($min === -1) { + $totalSize = $min; + } else { + $totalSize = $sum; + } + if ($entry['size'] !== $totalSize) { + $this->update($id, array('size' => $totalSize)); + } + + } + } + return $totalSize; + } + + /** + * get all file ids on the files on the storage + * + * @return int[] + */ + public function getAll() { + $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?'; + $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId())); + $ids = array(); + while ($row = $result->fetchRow()) { + $ids[] = $row['fileid']; + } + return $ids; + } + + /** + * find a folder in the cache which has not been fully scanned + * + * If multiply incomplete folders are in the cache, the one with the highest id will be returned, + * use the one with the highest id gives the best result with the background scanner, since that is most + * likely the folder where we stopped scanning previously + * + * @return string|bool the path of the folder or false when no folder matched + */ + public function getIncomplete() { + $query = \OC_DB::prepare('SELECT `path` FROM `*PREFIX*filecache`' + . ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC',1); + $result = \OC_DB::executeAudited($query, array($this->getNumericStorageId())); + if ($row = $result->fetchRow()) { + return $row['path']; + } else { + return false; + } + } + + /** + * get the storage id of the storage for a file and the internal path of the file + * + * @param int $id + * @return array, first element holding the storage id, second the path + */ + static public function getById($id) { + $sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?'; + $result = \OC_DB::executeAudited($sql, array($id)); + if ($row = $result->fetchRow()) { + $numericId = $row['storage']; + $path = $row['path']; + } else { + return null; + } + + if ($id = Storage::getStorageId($numericId)) { + return array($id, $path); + } else { + return null; + } + } + + /** + * normalize the given path + * @param $path + * @return string + */ + public function normalize($path) { + + return \OC_Util::normalizeUnicode($path); + } +} diff --git a/lib/private/files/cache/legacy.php b/lib/private/files/cache/legacy.php new file mode 100644 index 00000000000..8eed1f67a5d --- /dev/null +++ b/lib/private/files/cache/legacy.php @@ -0,0 +1,136 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +/** + * Provide read only support for the old filecache + */ +class Legacy { + private $user; + + private $cacheHasItems = null; + + public function __construct($user) { + $this->user = $user; + } + + /** + * get the numbers of items in the legacy cache + * + * @return int + */ + function getCount() { + $sql = 'SELECT COUNT(`id`) AS `count` FROM `*PREFIX*fscache` WHERE `user` = ?'; + $result = \OC_DB::executeAudited($sql, array($this->user)); + if ($row = $result->fetchRow()) { + return $row['count']; + } else { + return 0; + } + } + + /** + * check if a legacy cache is present and holds items + * + * @return bool + */ + function hasItems() { + if (!is_null($this->cacheHasItems)) { + return $this->cacheHasItems; + } + try { + $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*fscache` WHERE `user` = ?',1); + } catch (\Exception $e) { + $this->cacheHasItems = false; + return false; + } + try { + $result = $query->execute(array($this->user)); + } catch (\Exception $e) { + $this->cacheHasItems = false; + return false; + } + + if ($result === false || property_exists($result, 'error_message_prefix')) { + $this->cacheHasItems = false; + return false; + } + + $this->cacheHasItems = (bool)$result->fetchRow(); + return $this->cacheHasItems; + } + + /** + * get an item from the legacy cache + * + * @param string|int $path + * @return array + */ + function get($path) { + if (is_numeric($path)) { + $sql = 'SELECT * FROM `*PREFIX*fscache` WHERE `id` = ?'; + } else { + $sql = 'SELECT * FROM `*PREFIX*fscache` WHERE `path` = ?'; + } + $result = \OC_DB::executeAudited($sql, array($path)); + $data = $result->fetchRow(); + $data['etag'] = $this->getEtag($data['path'], $data['user']); + return $data; + } + + /** + * Get the ETag for the given path + * + * @param type $path + * @return string + */ + function getEtag($path, $user = null) { + static $query = null; + + $pathDetails = explode('/', $path, 4); + if((!$user) && !isset($pathDetails[1])) { + //no user!? Too odd, return empty string. + return ''; + } else if(!$user) { + //guess user from path, if no user passed. + $user = $pathDetails[1]; + } + + if(!isset($pathDetails[3]) || is_null($pathDetails[3])) { + $relativePath = ''; + } else { + $relativePath = $pathDetails[3]; + } + + if(is_null($query)){ + $query = \OC_DB::prepare('SELECT `propertyvalue` FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = \'{DAV:}getetag\''); + } + $result = \OC_DB::executeAudited($query,array($user, '/' . $relativePath)); + if ($row = $result->fetchRow()) { + return trim($row['propertyvalue'], '"'); + } else { + return ''; + } + } + + /** + * get all child items of an item from the legacy cache + * + * @param int $id + * @return array + */ + function getChildren($id) { + $result = \OC_DB::executeAudited('SELECT * FROM `*PREFIX*fscache` WHERE `parent` = ?', array($id)); + $data = $result->fetchAll(); + foreach ($data as $i => $item) { + $data[$i]['etag'] = $this->getEtag($item['path'], $item['user']); + } + return $data; + } +} diff --git a/lib/private/files/cache/permissions.php b/lib/private/files/cache/permissions.php new file mode 100644 index 00000000000..2e2bdb20b78 --- /dev/null +++ b/lib/private/files/cache/permissions.php @@ -0,0 +1,143 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +class Permissions { + /** + * @var string $storageId + */ + private $storageId; + + /** + * @param \OC\Files\Storage\Storage|string $storage + */ + public function __construct($storage) { + if ($storage instanceof \OC\Files\Storage\Storage) { + $this->storageId = $storage->getId(); + } else { + $this->storageId = $storage; + } + } + + /** + * get the permissions for a single file + * + * @param int $fileId + * @param string $user + * @return int (-1 if file no permissions set) + */ + public function get($fileId, $user) { + $sql = 'SELECT `permissions` FROM `*PREFIX*permissions` WHERE `user` = ? AND `fileid` = ?'; + $result = \OC_DB::executeAudited($sql, array($user, $fileId)); + if ($row = $result->fetchRow()) { + return $row['permissions']; + } else { + return -1; + } + } + + /** + * set the permissions of a file + * + * @param int $fileId + * @param string $user + * @param int $permissions + */ + public function set($fileId, $user, $permissions) { + if (self::get($fileId, $user) !== -1) { + $sql = 'UPDATE `*PREFIX*permissions` SET `permissions` = ? WHERE `user` = ? AND `fileid` = ?'; + } else { + $sql = 'INSERT INTO `*PREFIX*permissions`(`permissions`, `user`, `fileid`) VALUES(?, ?,? )'; + } + \OC_DB::executeAudited($sql, array($permissions, $user, $fileId)); + } + + /** + * get the permissions of multiply files + * + * @param int[] $fileIds + * @param string $user + * @return int[] + */ + public function getMultiple($fileIds, $user) { + if (count($fileIds) === 0) { + return array(); + } + $params = $fileIds; + $params[] = $user; + $inPart = implode(', ', array_fill(0, count($fileIds), '?')); + + $sql = 'SELECT `fileid`, `permissions` FROM `*PREFIX*permissions`' + . ' WHERE `fileid` IN (' . $inPart . ') AND `user` = ?'; + $result = \OC_DB::executeAudited($sql, $params); + $filePermissions = array(); + while ($row = $result->fetchRow()) { + $filePermissions[$row['fileid']] = $row['permissions']; + } + return $filePermissions; + } + + /** + * get the permissions for all files in a folder + * + * @param int $parentId + * @param string $user + * @return int[] + */ + public function getDirectoryPermissions($parentId, $user) { + $sql = 'SELECT `*PREFIX*permissions`.`fileid`, `permissions` + FROM `*PREFIX*permissions` + INNER JOIN `*PREFIX*filecache` ON `*PREFIX*permissions`.`fileid` = `*PREFIX*filecache`.`fileid` + WHERE `*PREFIX*filecache`.`parent` = ? AND `*PREFIX*permissions`.`user` = ?'; + + $result = \OC_DB::executeAudited($sql, array($parentId, $user)); + $filePermissions = array(); + while ($row = $result->fetchRow()) { + $filePermissions[$row['fileid']] = $row['permissions']; + } + return $filePermissions; + } + + /** + * remove the permissions for a file + * + * @param int $fileId + * @param string $user + */ + public function remove($fileId, $user = null) { + if (is_null($user)) { + \OC_DB::executeAudited('DELETE FROM `*PREFIX*permissions` WHERE `fileid` = ?', array($fileId)); + } else { + $sql = 'DELETE FROM `*PREFIX*permissions` WHERE `fileid` = ? AND `user` = ?'; + \OC_DB::executeAudited($sql, array($fileId, $user)); + } + } + + public function removeMultiple($fileIds, $user) { + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*permissions` WHERE `fileid` = ? AND `user` = ?'); + foreach ($fileIds as $fileId) { + \OC_DB::executeAudited($query, array($fileId, $user)); + } + } + + /** + * get the list of users which have permissions stored for a file + * + * @param int $fileId + */ + public function getUsers($fileId) { + $sql = 'SELECT `user` FROM `*PREFIX*permissions` WHERE `fileid` = ?'; + $result = \OC_DB::executeAudited($sql, array($fileId)); + $users = array(); + while ($row = $result->fetchRow()) { + $users[] = $row['user']; + } + return $users; + } +} diff --git a/lib/private/files/cache/scanner.php b/lib/private/files/cache/scanner.php new file mode 100644 index 00000000000..96f84609cf2 --- /dev/null +++ b/lib/private/files/cache/scanner.php @@ -0,0 +1,258 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +use OC\Files\Filesystem; +use OC\Hooks\BasicEmitter; + +/** + * Class Scanner + * + * Hooks available in scope \OC\Files\Cache\Scanner: + * - scanFile(string $path, string $storageId) + * - scanFolder(string $path, string $storageId) + * + * @package OC\Files\Cache + */ +class Scanner extends BasicEmitter { + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage; + + /** + * @var string $storageId + */ + private $storageId; + + /** + * @var \OC\Files\Cache\Cache $cache + */ + private $cache; + + /** + * @var \OC\Files\Cache\Permissions $permissionsCache + */ + private $permissionsCache; + + const SCAN_RECURSIVE = true; + const SCAN_SHALLOW = false; + + const REUSE_ETAG = 1; + const REUSE_SIZE = 2; + + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->storageId = $this->storage->getId(); + $this->cache = $storage->getCache(); + $this->permissionsCache = $storage->getPermissionsCache(); + } + + /** + * get all the metadata of a file or folder + * * + * + * @param string $path + * @return array with metadata of the file + */ + public function getData($path) { + $data = array(); + if (!$this->storage->isReadable($path)) return null; //cant read, nothing we can do + $data['mimetype'] = $this->storage->getMimeType($path); + $data['mtime'] = $this->storage->filemtime($path); + if ($data['mimetype'] == 'httpd/unix-directory') { + $data['size'] = -1; //unknown + } else { + $data['size'] = $this->storage->filesize($path); + } + $data['etag'] = $this->storage->getETag($path); + $data['storage_mtime'] = $data['mtime']; + return $data; + } + + /** + * scan a single file and store it in the cache + * + * @param string $file + * @param int $reuseExisting + * @param bool $parentExistsInCache + * @return array with metadata of the scanned file + */ + public function scanFile($file, $reuseExisting = 0, $parentExistsInCache = false) { + if (!self::isPartialFile($file) + and !Filesystem::isFileBlacklisted($file) + ) { + $this->emit('\OC\Files\Cache\Scanner', 'scanFile', array($file, $this->storageId)); + \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId)); + $data = $this->getData($file); + if ($data) { + if ($file and !$parentExistsInCache) { + $parent = dirname($file); + if ($parent === '.' or $parent === '/') { + $parent = ''; + } + if (!$this->cache->inCache($parent)) { + $this->scanFile($parent); + } + } + $newData = $data; + $cacheData = $this->cache->get($file); + if ($cacheData) { + $this->permissionsCache->remove($cacheData['fileid']); + if ($reuseExisting) { + // prevent empty etag + $etag = $cacheData['etag']; + $propagateETagChange = false; + if (empty($etag)) { + $etag = $data['etag']; + $propagateETagChange = true; + } + // only reuse data if the file hasn't explicitly changed + if (isset($data['mtime']) && isset($cacheData['mtime']) && $data['mtime'] === $cacheData['mtime']) { + if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) { + $data['size'] = $cacheData['size']; + } + if ($reuseExisting & self::REUSE_ETAG) { + $data['etag'] = $etag; + if ($propagateETagChange) { + $parent = $file; + while ($parent !== '') { + $parent = dirname($parent); + if ($parent === '.') { + $parent = ''; + } + $parentCacheData = $this->cache->get($parent); + $this->cache->update($parentCacheData['fileid'], array( + 'etag' => $this->storage->getETag($parent), + )); + } + } + } + } + // Only update metadata that has changed + $newData = array_diff($data, $cacheData); + } + } + if (!empty($newData)) { + $this->cache->put($file, $newData); + } + } else { + $this->cache->remove($file); + } + return $data; + } + return null; + } + + /** + * scan a folder and all it's children + * + * @param string $path + * @param bool $recursive + * @param int $reuse + * @return int the size of the scanned folder or -1 if the size is unknown at this stage + */ + public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1) { + if ($reuse === -1) { + $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : 0; + } + $this->scanFile($path, $reuse); + return $this->scanChildren($path, $recursive, $reuse); + } + + /** + * scan all the files and folders in a folder + * + * @param string $path + * @param bool $recursive + * @param int $reuse + * @return int the size of the scanned folder or -1 if the size is unknown at this stage + */ + public function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1) { + if ($reuse === -1) { + $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : 0; + } + $this->emit('\OC\Files\Cache\Scanner', 'scanFolder', array($path, $this->storageId)); + $size = 0; + $childQueue = array(); + $existingChildren = array(); + if ($this->cache->inCache($path)) { + $children = $this->cache->getFolderContents($path); + foreach ($children as $child) { + $existingChildren[] = $child['name']; + } + } + $newChildren = array(); + if ($this->storage->is_dir($path) && ($dh = $this->storage->opendir($path))) { + \OC_DB::beginTransaction(); + if (is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + $child = ($path) ? $path . '/' . $file : $file; + if (!Filesystem::isIgnoredDir($file)) { + $newChildren[] = $file; + $data = $this->scanFile($child, $reuse, true); + if ($data) { + if ($data['size'] === -1) { + if ($recursive === self::SCAN_RECURSIVE) { + $childQueue[] = $child; + } else { + $size = -1; + } + } else if ($size !== -1) { + $size += $data['size']; + } + } + } + } + } + $removedChildren = \array_diff($existingChildren, $newChildren); + foreach ($removedChildren as $childName) { + $child = ($path) ? $path . '/' . $childName : $childName; + $this->cache->remove($child); + } + \OC_DB::commit(); + foreach ($childQueue as $child) { + $childSize = $this->scanChildren($child, self::SCAN_RECURSIVE, $reuse); + if ($childSize === -1) { + $size = -1; + } else { + $size += $childSize; + } + } + $this->cache->put($path, array('size' => $size)); + } + return $size; + } + + /** + * @brief check if the file should be ignored when scanning + * NOTE: files with a '.part' extension are ignored as well! + * prevents unfinished put requests to be scanned + * @param String $file + * @return boolean + */ + public static function isPartialFile($file) { + if (pathinfo($file, PATHINFO_EXTENSION) === 'part') { + return true; + } + return false; + } + + /** + * walk over any folders that are not fully scanned yet and scan them + */ + public function backgroundScan() { + $lastPath = null; + while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) { + $this->scan($path); + $this->cache->correctFolderSize($path); + $lastPath = $path; + } + } +} diff --git a/lib/private/files/cache/storage.php b/lib/private/files/cache/storage.php new file mode 100644 index 00000000000..8a9e47ca36d --- /dev/null +++ b/lib/private/files/cache/storage.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +/** + * Class Storage + * + * cache storage specific data + * + * @package OC\Files\Cache + */ +class Storage { + private $storageId; + private $numericId; + + /** + * @param \OC\Files\Storage\Storage|string $storage + */ + public function __construct($storage) { + if ($storage instanceof \OC\Files\Storage\Storage) { + $this->storageId = $storage->getId(); + } else { + $this->storageId = $storage; + } + if (strlen($this->storageId) > 64) { + $this->storageId = md5($this->storageId); + } + + $sql = 'SELECT `numeric_id` FROM `*PREFIX*storages` WHERE `id` = ?'; + $result = \OC_DB::executeAudited($sql, array($this->storageId)); + if ($row = $result->fetchRow()) { + $this->numericId = $row['numeric_id']; + } else { + $sql = 'INSERT INTO `*PREFIX*storages` (`id`) VALUES(?)'; + \OC_DB::executeAudited($sql, array($this->storageId)); + $this->numericId = \OC_DB::insertid('*PREFIX*storages'); + } + } + + public function getNumericId() { + return $this->numericId; + } + + public static function getStorageId($numericId) { + + $sql = 'SELECT `id` FROM `*PREFIX*storages` WHERE `numeric_id` = ?'; + $result = \OC_DB::executeAudited($sql, array($numericId)); + if ($row = $result->fetchRow()) { + return $row['id']; + } else { + return null; + } + } +} diff --git a/lib/private/files/cache/updater.php b/lib/private/files/cache/updater.php new file mode 100644 index 00000000000..1f30173a8f8 --- /dev/null +++ b/lib/private/files/cache/updater.php @@ -0,0 +1,161 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; +use OCP\Util; + +/** + * listen to filesystem hooks and change the cache accordingly + */ +class Updater { + + /** + * resolve a path to a storage and internal path + * + * @param string $path the relative path + * @return array consisting of the storage and the internal path + */ + static public function resolvePath($path) { + $view = \OC\Files\Filesystem::getView(); + return $view->resolvePath($path); + } + + /** + * perform a write update + * + * @param string $path the relative path of the file + */ + static public function writeUpdate($path) { + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = self::resolvePath($path); + if ($storage) { + $cache = $storage->getCache($internalPath); + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Scanner::SCAN_SHALLOW); + $cache->correctFolderSize($internalPath); + self::correctFolder($path, $storage->filemtime($internalPath)); + } + } + + /** + * perform a delete update + * + * @param string $path the relative path of the file + */ + static public function deleteUpdate($path) { + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = self::resolvePath($path); + if ($storage) { + $cache = $storage->getCache($internalPath); + $cache->remove($internalPath); + $cache->correctFolderSize($internalPath); + self::correctFolder($path, time()); + } + } + + /** + * preform a rename update + * + * @param string $from the relative path of the source file + * @param string $to the relative path of the target file + */ + static public function renameUpdate($from, $to) { + /** + * @var \OC\Files\Storage\Storage $storageFrom + * @var \OC\Files\Storage\Storage $storageTo + * @var string $internalFrom + * @var string $internalTo + */ + list($storageFrom, $internalFrom) = self::resolvePath($from); + list($storageTo, $internalTo) = self::resolvePath($to); + if ($storageFrom && $storageTo) { + if ($storageFrom === $storageTo) { + $cache = $storageFrom->getCache($internalFrom); + $cache->move($internalFrom, $internalTo); + $cache->correctFolderSize($internalFrom); + $cache->correctFolderSize($internalTo); + self::correctFolder($from, time()); + self::correctFolder($to, time()); + } else { + self::deleteUpdate($from); + self::writeUpdate($to); + } + } + } + + /** + * Update the mtime and ETag of all parent folders + * + * @param string $path + * @param string $time + */ + static public function correctFolder($path, $time) { + if ($path !== '' && $path !== '/') { + $parent = dirname($path); + if ($parent === '.' || $parent === '\\') { + $parent = ''; + } + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = self::resolvePath($parent); + if ($storage) { + $cache = $storage->getCache(); + $id = $cache->getId($internalPath); + if ($id !== -1) { + $cache->update($id, array('mtime' => $time, 'etag' => $storage->getETag($internalPath))); + self::correctFolder($parent, $time); + } else { + Util::writeLog('core', 'Path not in cache: '.$internalPath, Util::ERROR); + } + } + } + } + + /** + * @param array $params + */ + static public function writeHook($params) { + self::writeUpdate($params['path']); + } + + /** + * @param array $params + */ + static public function touchHook($params) { + $path = $params['path']; + list($storage, $internalPath) = self::resolvePath($path); + $cache = $storage->getCache(); + $id = $cache->getId($internalPath); + if ($id !== -1) { + $cache->update($id, array('etag' => $storage->getETag($internalPath))); + } + self::writeUpdate($path); + } + + /** + * @param array $params + */ + static public function renameHook($params) { + self::renameUpdate($params['oldpath'], $params['newpath']); + } + + /** + * @param array $params + */ + static public function deleteHook($params) { + self::deleteUpdate($params['path']); + } +} diff --git a/lib/private/files/cache/upgrade.php b/lib/private/files/cache/upgrade.php new file mode 100644 index 00000000000..cfb9a117311 --- /dev/null +++ b/lib/private/files/cache/upgrade.php @@ -0,0 +1,227 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +class Upgrade { + /** + * @var Legacy $legacy + */ + private $legacy; + + private $numericIds = array(); + + private $mimeTypeIds = array(); + + /** + * @param Legacy $legacy + */ + public function __construct($legacy) { + $this->legacy = $legacy; + } + + /** + * Preform a upgrade a path and it's childs + * + * @param string $path + * @param bool $mode + */ + function upgradePath($path, $mode = Scanner::SCAN_RECURSIVE) { + if (!$this->legacy->hasItems()) { + return; + } + \OC_Hook::emit('\OC\Files\Cache\Upgrade', 'migrate_path', $path); + if ($row = $this->legacy->get($path)) { + $data = $this->getNewData($row); + if ($data) { + $this->insert($data); + $this->upgradeChilds($data['id'], $mode); + } + } + } + + /** + * upgrade all child elements of an item + * + * @param int $id + * @param bool $mode + */ + function upgradeChilds($id, $mode = Scanner::SCAN_RECURSIVE) { + $children = $this->legacy->getChildren($id); + foreach ($children as $child) { + $childData = $this->getNewData($child); + \OC_Hook::emit('\OC\Files\Cache\Upgrade', 'migrate_path', $child['path']); + if ($childData) { + $this->insert($childData); + if ($mode == Scanner::SCAN_RECURSIVE) { + $this->upgradeChilds($child['id']); + } + } + } + } + + /** + * insert data into the new cache + * + * @param array $data the data for the new cache + */ + function insert($data) { + static $insertQuery = null; + if(is_null($insertQuery)) { + $insertQuery = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache` + ( `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag` ) + VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); + } + if (!$this->inCache($data['storage'], $data['path_hash'], $data['id'])) { + \OC_DB::executeAudited($insertQuery, array($data['id'], $data['storage'], + $data['path'], $data['path_hash'], $data['parent'], $data['name'], + $data['mimetype'], $data['mimepart'], $data['size'], $data['mtime'], $data['encrypted'], $data['etag'])); + } + } + + /** + * check if an item is already in the new cache + * + * @param string $storage + * @param string $pathHash + * @param string $id + * @return bool + */ + function inCache($storage, $pathHash, $id) { + static $query = null; + if(is_null($query)) { + $query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE (`storage` = ? AND `path_hash` = ?) OR `fileid` = ?'); + } + $result = \OC_DB::executeAudited($query, array($storage, $pathHash, $id)); + return (bool)$result->fetchRow(); + } + + /** + * get the new data array from the old one + * + * @param array $data the data from the old cache + * Example data array + * Array + * ( + * [id] => 418 + * [path] => /tina/files/picture.jpg //relative to datadir + * [path_hash] => 66d4547e372888deed80b24fec9b192b + * [parent] => 234 + * [name] => picture.jpg + * [user] => tina + * [size] => 1265283 + * [ctime] => 1363909709 + * [mtime] => 1363909709 + * [mimetype] => image/jpeg + * [mimepart] => image + * [encrypted] => 0 + * [versioned] => 0 + * [writable] => 1 + * ) + * + * @return array + */ + function getNewData($data) { + //Make sure there is a path, otherwise we can do nothing. + if(!isset($data['path'])) { + return false; + } + $newData = $data; + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath; + */ + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($data['path']); + if ($storage) { + $newData['etag'] = $data['etag']; + $newData['path_hash'] = md5($internalPath); + $newData['path'] = $internalPath; + $newData['storage'] = $this->getNumericId($storage); + $newData['parent'] = ($internalPath === '') ? -1 : $data['parent']; + $newData['permissions'] = ($data['writable']) ? \OCP\PERMISSION_ALL : \OCP\PERMISSION_READ; + $newData['storage_object'] = $storage; + $newData['mimetype'] = $this->getMimetypeId($newData['mimetype'], $storage); + $newData['mimepart'] = $this->getMimetypeId($newData['mimepart'], $storage); + return $newData; + } else { + \OC_Log::write('core', 'Unable to migrate data from old cache for '.$data['path'].' because the storage was not found', \OC_Log::ERROR); + return false; + } + } + + /** + * get the numeric storage id + * + * @param \OC\Files\Storage\Storage $storage + * @return int + */ + function getNumericId($storage) { + $storageId = $storage->getId(); + if (!isset($this->numericIds[$storageId])) { + $cache = $storage->getCache(); + $this->numericIds[$storageId] = $cache->getNumericStorageId(); + } + return $this->numericIds[$storageId]; + } + + /** + * get the numeric id for a mimetype + * + * @param string $mimetype + * @param \OC\Files\Storage\Storage $storage + * @return int + */ + function getMimetypeId($mimetype, $storage) { + if (!isset($this->mimeTypeIds[$mimetype])) { + $cache = new Cache($storage); + $this->mimeTypeIds[$mimetype] = $cache->getMimetypeId($mimetype); + } + return $this->mimeTypeIds[$mimetype]; + } + + /** + * check if a cache upgrade is required for $user + * + * @param string $user + * @return bool + */ + static function needUpgrade($user) { + $cacheVersion = (int)\OCP\Config::getUserValue($user, 'files', 'cache_version', 4); + return $cacheVersion < 5; + } + + /** + * mark the filecache as upgrade + * + * @param string $user + */ + static function upgradeDone($user) { + \OCP\Config::setUserValue($user, 'files', 'cache_version', 5); + } + + /** + * Does a "silent" upgrade, i.e. without an Event-Source as triggered + * on User-Login via Ajax. This method is called within the regular + * ownCloud upgrade. + * + * @param string $user a User ID + */ + public static function doSilentUpgrade($user) { + if(!self::needUpgrade($user)) { + return; + } + $legacy = new \OC\Files\Cache\Legacy($user); + if ($legacy->hasItems()) { + \OC_DB::beginTransaction(); + $upgrade = new \OC\Files\Cache\Upgrade($legacy); + $upgrade->upgradePath('/' . $user . '/files'); + \OC_DB::commit(); + } + \OC\Files\Cache\Upgrade::upgradeDone($user); + } +} diff --git a/lib/private/files/cache/watcher.php b/lib/private/files/cache/watcher.php new file mode 100644 index 00000000000..8bfd4602f3a --- /dev/null +++ b/lib/private/files/cache/watcher.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +/** + * check the storage backends for updates and change the cache accordingly + */ +class Watcher { + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage; + + /** + * @var Cache $cache + */ + private $cache; + + /** + * @var Scanner $scanner; + */ + private $scanner; + + /** + * @param \OC\Files\Storage\Storage $storage + */ + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->cache = $storage->getCache(); + $this->scanner = $storage->getScanner(); + } + + /** + * check $path for updates + * + * @param string $path + */ + public function checkUpdate($path) { + $cachedEntry = $this->cache->get($path); + if ($this->storage->hasUpdated($path, $cachedEntry['storage_mtime'])) { + if ($this->storage->is_dir($path)) { + $this->scanner->scan($path, Scanner::SCAN_SHALLOW); + } else { + $this->scanner->scanFile($path); + } + if ($cachedEntry['mimetype'] === 'httpd/unix-directory') { + $this->cleanFolder($path); + } + $this->cache->correctFolderSize($path); + } + } + + /** + * remove deleted files in $path from the cache + * + * @param string $path + */ + public function cleanFolder($path) { + $cachedContent = $this->cache->getFolderContents($path); + foreach ($cachedContent as $entry) { + if (!$this->storage->file_exists($entry['path'])) { + $this->cache->remove($entry['path']); + } + } + } +} diff --git a/lib/private/files/filesystem.php b/lib/private/files/filesystem.php new file mode 100644 index 00000000000..10ec5c41d11 --- /dev/null +++ b/lib/private/files/filesystem.php @@ -0,0 +1,770 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * Class for abstraction of filesystem functions + * This class won't call any filesystem functions for itself but will pass them to the correct OC_Filestorage object + * this class should also handle all the file permission related stuff + * + * Hooks provided: + * read(path) + * write(path, &run) + * post_write(path) + * create(path, &run) (when a file is created, both create and write will be emitted in that order) + * post_create(path) + * delete(path, &run) + * post_delete(path) + * rename(oldpath,newpath, &run) + * post_rename(oldpath,newpath) + * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emitted in that order) + * post_rename(oldpath,newpath) + * post_initMountPoints(user, user_dir) + * + * the &run parameter can be set to false to prevent the operation from occurring + */ + +namespace OC\Files; + +use OC\Files\Storage\Loader; +const SPACE_NOT_COMPUTED = -1; +const SPACE_UNKNOWN = -2; +const SPACE_UNLIMITED = -3; + +class Filesystem { + /** + * @var Mount\Manager $mounts + */ + private static $mounts; + + public static $loaded = false; + /** + * @var \OC\Files\View $defaultInstance + */ + static private $defaultInstance; + + + /** + * classname which used for hooks handling + * used as signalclass in OC_Hooks::emit() + */ + const CLASSNAME = 'OC_Filesystem'; + + /** + * signalname emitted before file renaming + * + * @param string $oldpath + * @param string $newpath + */ + const signal_rename = 'rename'; + + /** + * signal emitted after file renaming + * + * @param string $oldpath + * @param string $newpath + */ + const signal_post_rename = 'post_rename'; + + /** + * signal emitted before file/dir creation + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_create = 'create'; + + /** + * signal emitted after file/dir creation + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_post_create = 'post_create'; + + /** + * signal emits before file/dir copy + * + * @param string $oldpath + * @param string $newpath + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_copy = 'copy'; + + /** + * signal emits after file/dir copy + * + * @param string $oldpath + * @param string $newpath + */ + const signal_post_copy = 'post_copy'; + + /** + * signal emits before file/dir save + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_write = 'write'; + + /** + * signal emits after file/dir save + * + * @param string $path + */ + const signal_post_write = 'post_write'; + + /** + * signal emits when reading file/dir + * + * @param string $path + */ + const signal_read = 'read'; + + /** + * signal emits when removing file/dir + * + * @param string $path + */ + const signal_delete = 'delete'; + + /** + * parameters definitions for signals + */ + const signal_param_path = 'path'; + const signal_param_oldpath = 'oldpath'; + const signal_param_newpath = 'newpath'; + + /** + * run - changing this flag to false in hook handler will cancel event + */ + const signal_param_run = 'run'; + + /** + * @var \OC\Files\Storage\Loader $loader + */ + private static $loader; + + /** + * @param callable $wrapper + */ + public static function addStorageWrapper($wrapper) { + self::getLoader()->addStorageWrapper($wrapper); + + $mounts = self::getMountManager()->getAll(); + foreach ($mounts as $mount) { + $mount->wrapStorage($wrapper); + } + } + + public static function getLoader() { + if (!self::$loader) { + self::$loader = new Loader(); + } + return self::$loader; + } + + public static function getMountManager() { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + return self::$mounts; + } + + /** + * get the mountpoint of the storage object for a path + * ( note: because a storage is not always mounted inside the fakeroot, the + * returned mountpoint is relative to the absolute root of the filesystem + * and doesn't take the chroot into account ) + * + * @param string $path + * @return string + */ + static public function getMountPoint($path) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $mount = self::$mounts->find($path); + if ($mount) { + return $mount->getMountPoint(); + } else { + return ''; + } + } + + /** + * get a list of all mount points in a directory + * + * @param string $path + * @return string[] + */ + static public function getMountPoints($path) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $result = array(); + $mounts = self::$mounts->findIn($path); + foreach ($mounts as $mount) { + $result[] = $mount->getMountPoint(); + } + return $result; + } + + /** + * get the storage mounted at $mountPoint + * + * @param string $mountPoint + * @return \OC\Files\Storage\Storage + */ + public static function getStorage($mountPoint) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $mount = self::$mounts->find($mountPoint); + return $mount->getStorage(); + } + + /** + * @param $id + * @return Mount\Mount[] + */ + public static function getMountByStorageId($id) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + return self::$mounts->findByStorageId($id); + } + + /** + * @param $id + * @return Mount\Mount[] + */ + public static function getMountByNumericId($id) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + return self::$mounts->findByNumericId($id); + } + + /** + * resolve a path to a storage and internal path + * + * @param string $path + * @return array consisting of the storage and the internal path + */ + static public function resolvePath($path) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $mount = self::$mounts->find($path); + if ($mount) { + return array($mount->getStorage(), $mount->getInternalPath($path)); + } else { + return array(null, null); + } + } + + static public function init($user, $root) { + if (self::$defaultInstance) { + return false; + } + self::getLoader(); + self::$defaultInstance = new View($root); + + if (!self::$mounts) { + self::$mounts = new Mount\Manager(); + } + + //load custom mount config + self::initMountPoints($user); + + self::$loaded = true; + + return true; + } + + static public function initMounts() { + if (!self::$mounts) { + self::$mounts = new Mount\Manager(); + } + } + + /** + * Initialize system and personal mount points for a user + * + * @param string $user + */ + public static function initMountPoints($user = '') { + if ($user == '') { + $user = \OC_User::getUser(); + } + $parser = new \OC\ArrayParser(); + + $root = \OC_User::getHome($user); + self::mount('\OC\Files\Storage\Local', array('datadir' => $root), $user); + $datadir = \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data"); + + //move config file to it's new position + if (is_file(\OC::$SERVERROOT . '/config/mount.json')) { + rename(\OC::$SERVERROOT . '/config/mount.json', $datadir . '/mount.json'); + } + // Load system mount points + if (is_file(\OC::$SERVERROOT . '/config/mount.php') or is_file($datadir . '/mount.json')) { + if (is_file($datadir . '/mount.json')) { + $mountConfig = json_decode(file_get_contents($datadir . '/mount.json'), true); + } elseif (is_file(\OC::$SERVERROOT . '/config/mount.php')) { + $mountConfig = $parser->parsePHP(file_get_contents(\OC::$SERVERROOT . '/config/mount.php')); + } + if (isset($mountConfig['global'])) { + foreach ($mountConfig['global'] as $mountPoint => $options) { + self::mount($options['class'], $options['options'], $mountPoint); + } + } + if (isset($mountConfig['group'])) { + foreach ($mountConfig['group'] as $group => $mounts) { + if (\OC_Group::inGroup($user, $group)) { + foreach ($mounts as $mountPoint => $options) { + $mountPoint = self::setUserVars($user, $mountPoint); + foreach ($options as &$option) { + $option = self::setUserVars($user, $option); + } + self::mount($options['class'], $options['options'], $mountPoint); + } + } + } + } + if (isset($mountConfig['user'])) { + foreach ($mountConfig['user'] as $mountUser => $mounts) { + if ($mountUser === 'all' or strtolower($mountUser) === strtolower($user)) { + foreach ($mounts as $mountPoint => $options) { + $mountPoint = self::setUserVars($user, $mountPoint); + foreach ($options as &$option) { + $option = self::setUserVars($user, $option); + } + self::mount($options['class'], $options['options'], $mountPoint); + } + } + } + } + } + // Load personal mount points + if (is_file($root . '/mount.php') or is_file($root . '/mount.json')) { + if (is_file($root . '/mount.json')) { + $mountConfig = json_decode(file_get_contents($root . '/mount.json'), true); + } elseif (is_file($root . '/mount.php')) { + $mountConfig = $parser->parsePHP(file_get_contents($root . '/mount.php')); + } + if (isset($mountConfig['user'][$user])) { + foreach ($mountConfig['user'][$user] as $mountPoint => $options) { + self::mount($options['class'], $options['options'], $mountPoint); + } + } + } + + // Chance to mount for other storages + \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', array('user' => $user, 'user_dir' => $root)); + } + + /** + * fill in the correct values for $user + * + * @param string $user + * @param string $input + * @return string + */ + private static function setUserVars($user, $input) { + return str_replace('$user', $user, $input); + } + + /** + * get the default filesystem view + * + * @return View + */ + static public function getView() { + return self::$defaultInstance; + } + + /** + * tear down the filesystem, removing all storage providers + */ + static public function tearDown() { + self::clearMounts(); + self::$defaultInstance = null; + } + + /** + * @brief get the relative path of the root data directory for the current user + * @return string + * + * Returns path like /admin/files + */ + static public function getRoot() { + return self::$defaultInstance->getRoot(); + } + + /** + * clear all mounts and storage backends + */ + public static function clearMounts() { + if (self::$mounts) { + self::$mounts->clear(); + } + } + + /** + * mount an \OC\Files\Storage\Storage in our virtual filesystem + * + * @param \OC\Files\Storage\Storage|string $class + * @param array $arguments + * @param string $mountpoint + */ + static public function mount($class, $arguments, $mountpoint) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $mount = new Mount\Mount($class, $mountpoint, $arguments, self::getLoader()); + self::$mounts->addMount($mount); + } + + /** + * return the path to a local version of the file + * we need this because we can't know if a file is stored local or not from + * outside the filestorage and for some purposes a local file is needed + * + * @param string $path + * @return string + */ + static public function getLocalFile($path) { + return self::$defaultInstance->getLocalFile($path); + } + + /** + * @param string $path + * @return string + */ + static public function getLocalFolder($path) { + return self::$defaultInstance->getLocalFolder($path); + } + + /** + * return path to file which reflects one visible in browser + * + * @param string $path + * @return string + */ + static public function getLocalPath($path) { + $datadir = \OC_User::getHome(\OC_User::getUser()) . '/files'; + $newpath = $path; + if (strncmp($newpath, $datadir, strlen($datadir)) == 0) { + $newpath = substr($path, strlen($datadir)); + } + return $newpath; + } + + /** + * check if the requested path is valid + * + * @param string $path + * @return bool + */ + static public function isValidPath($path) { + $path = self::normalizePath($path); + if (!$path || $path[0] !== '/') { + $path = '/' . $path; + } + if (strstr($path, '/../') || strrchr($path, '/') === '/..') { + return false; + } + return true; + } + + /** + * checks if a file is blacklisted for storage in the filesystem + * Listens to write and rename hooks + * + * @param array $data from hook + */ + static public function isBlacklisted($data) { + if (isset($data['path'])) { + $path = $data['path']; + } else if (isset($data['newpath'])) { + $path = $data['newpath']; + } + if (isset($path)) { + if (self::isFileBlacklisted($path)) { + $data['run'] = false; + } + } + } + + /** + * @param string $filename + * @return bool + */ + static public function isFileBlacklisted($filename) { + $blacklist = \OC_Config::getValue('blacklisted_files', array('.htaccess')); + $filename = strtolower(basename($filename)); + return (in_array($filename, $blacklist)); + } + + /** + * @brief check if the directory should be ignored when scanning + * NOTE: the special directories . and .. would cause never ending recursion + * @param String $dir + * @return boolean + */ + static public function isIgnoredDir($dir) { + if ($dir === '.' || $dir === '..') { + return true; + } + return false; + } + + /** + * following functions are equivalent to their php builtin equivalents for arguments/return values. + */ + static public function mkdir($path) { + return self::$defaultInstance->mkdir($path); + } + + static public function rmdir($path) { + return self::$defaultInstance->rmdir($path); + } + + static public function opendir($path) { + return self::$defaultInstance->opendir($path); + } + + static public function readdir($path) { + return self::$defaultInstance->readdir($path); + } + + static public function is_dir($path) { + return self::$defaultInstance->is_dir($path); + } + + static public function is_file($path) { + return self::$defaultInstance->is_file($path); + } + + static public function stat($path) { + return self::$defaultInstance->stat($path); + } + + static public function filetype($path) { + return self::$defaultInstance->filetype($path); + } + + static public function filesize($path) { + return self::$defaultInstance->filesize($path); + } + + static public function readfile($path) { + return self::$defaultInstance->readfile($path); + } + + static public function isCreatable($path) { + return self::$defaultInstance->isCreatable($path); + } + + static public function isReadable($path) { + return self::$defaultInstance->isReadable($path); + } + + static public function isUpdatable($path) { + return self::$defaultInstance->isUpdatable($path); + } + + static public function isDeletable($path) { + return self::$defaultInstance->isDeletable($path); + } + + static public function isSharable($path) { + return self::$defaultInstance->isSharable($path); + } + + static public function file_exists($path) { + return self::$defaultInstance->file_exists($path); + } + + static public function filemtime($path) { + return self::$defaultInstance->filemtime($path); + } + + static public function touch($path, $mtime = null) { + return self::$defaultInstance->touch($path, $mtime); + } + + static public function file_get_contents($path) { + return self::$defaultInstance->file_get_contents($path); + } + + static public function file_put_contents($path, $data) { + return self::$defaultInstance->file_put_contents($path, $data); + } + + static public function unlink($path) { + return self::$defaultInstance->unlink($path); + } + + static public function rename($path1, $path2) { + return self::$defaultInstance->rename($path1, $path2); + } + + static public function copy($path1, $path2) { + return self::$defaultInstance->copy($path1, $path2); + } + + static public function fopen($path, $mode) { + return self::$defaultInstance->fopen($path, $mode); + } + + static public function toTmpFile($path) { + return self::$defaultInstance->toTmpFile($path); + } + + static public function fromTmpFile($tmpFile, $path) { + return self::$defaultInstance->fromTmpFile($tmpFile, $path); + } + + static public function getMimeType($path) { + return self::$defaultInstance->getMimeType($path); + } + + static public function hash($type, $path, $raw = false) { + return self::$defaultInstance->hash($type, $path, $raw); + } + + static public function free_space($path = '/') { + return self::$defaultInstance->free_space($path); + } + + static public function search($query) { + return self::$defaultInstance->search($query); + } + + static public function searchByMime($query) { + return self::$defaultInstance->searchByMime($query); + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + */ + static public function hasUpdated($path, $time) { + return self::$defaultInstance->hasUpdated($path, $time); + } + + /** + * @brief Fix common problems with a file path + * @param string $path + * @param bool $stripTrailingSlash + * @return string + */ + public static function normalizePath($path, $stripTrailingSlash = true) { + if ($path == '') { + return '/'; + } + //no windows style slashes + $path = str_replace('\\', '/', $path); + //add leading slash + if ($path[0] !== '/') { + $path = '/' . $path; + } + //remove duplicate slashes + while (strpos($path, '//') !== false) { + $path = str_replace('//', '/', $path); + } + //remove trailing slash + if ($stripTrailingSlash and strlen($path) > 1 and substr($path, -1, 1) === '/') { + $path = substr($path, 0, -1); + } + //normalize unicode if possible + $path = \OC_Util::normalizeUnicode($path); + + return $path; + } + + /** + * get the filesystem info + * + * @param string $path + * @return array + * + * returns an associative array with the following keys: + * - size + * - mtime + * - mimetype + * - encrypted + * - versioned + */ + public static function getFileInfo($path) { + return self::$defaultInstance->getFileInfo($path); + } + + /** + * change file metadata + * + * @param string $path + * @param array $data + * @return int + * + * returns the fileid of the updated file + */ + public static function putFileInfo($path, $data) { + return self::$defaultInstance->putFileInfo($path, $data); + } + + /** + * get the content of a directory + * + * @param string $directory path under datadirectory + * @param string $mimetype_filter limit returned content to this mimetype or mimepart + * @return array + */ + public static function getDirectoryContent($directory, $mimetype_filter = '') { + return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter); + } + + /** + * Get the path of a file by id + * + * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file + * + * @param int $id + * @return string + */ + public static function getPath($id) { + return self::$defaultInstance->getPath($id); + } + + /** + * Get the owner for a file or folder + * + * @param string $path + * @return string + */ + public static function getOwner($path) { + return self::$defaultInstance->getOwner($path); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + static public function getETag($path) { + return self::$defaultInstance->getETag($path); + } +} + +\OC_Util::setupFS(); diff --git a/lib/private/files/mapper.php b/lib/private/files/mapper.php new file mode 100644 index 00000000000..47abd4e52fe --- /dev/null +++ b/lib/private/files/mapper.php @@ -0,0 +1,239 @@ +<?php + +namespace OC\Files; + +/** + * class Mapper is responsible to translate logical paths to physical paths and reverse + */ +class Mapper +{ + private $unchangedPhysicalRoot; + + public function __construct($rootDir) { + $this->unchangedPhysicalRoot = $rootDir; + } + + /** + * @param string $logicPath + * @param bool $create indicates if the generated physical name shall be stored in the database or not + * @return string the physical path + */ + public function logicToPhysical($logicPath, $create) { + $physicalPath = $this->resolveLogicPath($logicPath); + if ($physicalPath !== null) { + return $physicalPath; + } + + return $this->create($logicPath, $create); + } + + /** + * @param string $physicalPath + * @return string + */ + public function physicalToLogic($physicalPath) { + $logicPath = $this->resolvePhysicalPath($physicalPath); + if ($logicPath !== null) { + return $logicPath; + } + + $this->insert($physicalPath, $physicalPath); + return $physicalPath; + } + + /** + * @param string $path + * @param bool $isLogicPath indicates if $path is logical or physical + * @param $recursive + * @return void + */ + public function removePath($path, $isLogicPath, $recursive) { + if ($recursive) { + $path=$path.'%'; + } + + if ($isLogicPath) { + \OC_DB::executeAudited('DELETE FROM `*PREFIX*file_map` WHERE `logic_path` LIKE ?', array($path)); + } else { + \OC_DB::executeAudited('DELETE FROM `*PREFIX*file_map` WHERE `physic_path` LIKE ?', array($path)); + } + } + + /** + * @param $path1 + * @param $path2 + * @throws \Exception + */ + public function copy($path1, $path2) + { + $path1 = $this->stripLast($path1); + $path2 = $this->stripLast($path2); + $physicPath1 = $this->logicToPhysical($path1, true); + $physicPath2 = $this->logicToPhysical($path2, true); + + $sql = 'SELECT * FROM `*PREFIX*file_map` WHERE `logic_path` LIKE ?'; + $result = \OC_DB::executeAudited($sql, array($path1.'%')); + $updateQuery = \OC_DB::prepare('UPDATE `*PREFIX*file_map`' + .' SET `logic_path` = ?' + .' , `logic_path_hash` = ?' + .' , `physic_path` = ?' + .' , `physic_path_hash` = ?' + .' WHERE `logic_path` = ?'); + while( $row = $result->fetchRow()) { + $currentLogic = $row['logic_path']; + $currentPhysic = $row['physic_path']; + $newLogic = $path2.$this->stripRootFolder($currentLogic, $path1); + $newPhysic = $physicPath2.$this->stripRootFolder($currentPhysic, $physicPath1); + if ($path1 !== $currentLogic) { + try { + \OC_DB::executeAudited($updateQuery, array($newLogic, md5($newLogic), $newPhysic, md5($newPhysic), + $currentLogic)); + } catch (\Exception $e) { + error_log('Mapper::Copy failed '.$currentLogic.' -> '.$newLogic.'\n'.$e); + throw $e; + } + } + } + } + + /** + * @param $path + * @param $root + * @return bool|string + */ + public function stripRootFolder($path, $root) { + if (strpos($path, $root) !== 0) { + // throw exception ??? + return false; + } + if (strlen($path) > strlen($root)) { + return substr($path, strlen($root)); + } + + return ''; + } + + private function stripLast($path) { + if (substr($path, -1) == '/') { + $path = substr_replace($path, '', -1); + } + return $path; + } + + private function resolveLogicPath($logicPath) { + $logicPath = $this->stripLast($logicPath); + $sql = 'SELECT * FROM `*PREFIX*file_map` WHERE `logic_path_hash` = ?'; + $result = \OC_DB::executeAudited($sql, array(md5($logicPath))); + $result = $result->fetchRow(); + if ($result === false) { + return null; + } + + return $result['physic_path']; + } + + private function resolvePhysicalPath($physicalPath) { + $physicalPath = $this->stripLast($physicalPath); + $sql = \OC_DB::prepare('SELECT * FROM `*PREFIX*file_map` WHERE `physic_path_hash` = ?'); + $result = \OC_DB::executeAudited($sql, array(md5($physicalPath))); + $result = $result->fetchRow(); + + return $result['logic_path']; + } + + private function create($logicPath, $store) { + $logicPath = $this->stripLast($logicPath); + $index = 0; + + // create the slugified path + $physicalPath = $this->slugifyPath($logicPath); + + // detect duplicates + while ($this->resolvePhysicalPath($physicalPath) !== null) { + $physicalPath = $this->slugifyPath($logicPath, $index++); + } + + // insert the new path mapping if requested + if ($store) { + $this->insert($logicPath, $physicalPath); + } + + return $physicalPath; + } + + private function insert($logicPath, $physicalPath) { + $sql = 'INSERT INTO `*PREFIX*file_map` (`logic_path`, `physic_path`, `logic_path_hash`, `physic_path_hash`) + VALUES (?, ?, ?, ?)'; + \OC_DB::executeAudited($sql, array($logicPath, $physicalPath, md5($logicPath), md5($physicalPath))); + } + + public function slugifyPath($path, $index=null) { + $path = $this->stripRootFolder($path, $this->unchangedPhysicalRoot); + + $pathElements = explode('/', $path); + $sluggedElements = array(); + + $last= end($pathElements); + + foreach ($pathElements as $pathElement) { + // remove empty elements + if (empty($pathElement)) { + continue; + } + + $sluggedElements[] = self::slugify($pathElement); + } + + // apply index to file name + if ($index !== null) { + $last= array_pop($sluggedElements); + + // if filename contains periods - add index number before last period + if (preg_match('~\.[^\.]+$~i',$last,$extension)){ + array_push($sluggedElements, substr($last,0,-(strlen($extension[0]))).'-'.$index.$extension[0]); + } else { + // if filename doesn't contain periods add index ofter the last char + array_push($sluggedElements, $last.'-'.$index); + } + + } + + $sluggedPath = $this->unchangedPhysicalRoot.implode('/', $sluggedElements); + return $this->stripLast($sluggedPath); + } + + /** + * Modifies a string to remove all non ASCII characters and spaces. + * + * @param string $text + * @return string + */ + private function slugify($text) + { + // replace non letter or digits or dots by - + $text = preg_replace('~[^\\pL\d\.]+~u', '-', $text); + + // trim + $text = trim($text, '-'); + + // transliterate + if (function_exists('iconv')) { + $text = iconv('utf-8', 'us-ascii//TRANSLIT//IGNORE', $text); + } + + // lowercase + $text = strtolower($text); + + // remove unwanted characters + $text = preg_replace('~[^-\w\.]+~', '', $text); + + // trim ending dots (for security reasons and win compatibility) + $text = preg_replace('~\.+$~', '', $text); + + if (empty($text)) { + return uniqid(); + } + + return $text; + } +} diff --git a/lib/private/files/mount/manager.php b/lib/private/files/mount/manager.php new file mode 100644 index 00000000000..4c432dcf724 --- /dev/null +++ b/lib/private/files/mount/manager.php @@ -0,0 +1,127 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Mount; + +use \OC\Files\Filesystem; + +class Manager { + /** + * @var Mount[] + */ + private $mounts = array(); + + /** + * @param Mount $mount + */ + public function addMount($mount) { + $this->mounts[$mount->getMountPoint()] = $mount; + } + + /** + * Find the mount for $path + * + * @param $path + * @return Mount + */ + public function find($path) { + \OC_Util::setupFS(); + $path = $this->formatPath($path); + if (isset($this->mounts[$path])) { + return $this->mounts[$path]; + } + + \OC_Hook::emit('OC_Filesystem', 'get_mountpoint', array('path' => $path)); + $foundMountPoint = ''; + $mountPoints = array_keys($this->mounts); + foreach ($mountPoints as $mountpoint) { + if (strpos($path, $mountpoint) === 0 and strlen($mountpoint) > strlen($foundMountPoint)) { + $foundMountPoint = $mountpoint; + } + } + if (isset($this->mounts[$foundMountPoint])) { + return $this->mounts[$foundMountPoint]; + } else { + return null; + } + } + + /** + * Find all mounts in $path + * + * @param $path + * @return Mount[] + */ + public function findIn($path) { + \OC_Util::setupFS(); + $path = $this->formatPath($path); + $result = array(); + $pathLength = strlen($path); + $mountPoints = array_keys($this->mounts); + foreach ($mountPoints as $mountPoint) { + if (substr($mountPoint, 0, $pathLength) === $path and strlen($mountPoint) > $pathLength) { + $result[] = $this->mounts[$mountPoint]; + } + } + return $result; + } + + public function clear() { + $this->mounts = array(); + } + + /** + * Find mounts by storage id + * + * @param string $id + * @return Mount[] + */ + public function findByStorageId($id) { + \OC_Util::setupFS(); + if (strlen($id) > 64) { + $id = md5($id); + } + $result = array(); + foreach ($this->mounts as $mount) { + if ($mount->getStorageId() === $id) { + $result[] = $mount; + } + } + return $result; + } + + /** + * @return Mount[] + */ + public function getAll() { + return $this->mounts; + } + + /** + * Find mounts by numeric storage id + * + * @param string $id + * @return Mount + */ + public function findByNumericId($id) { + $storageId = \OC\Files\Cache\Storage::getStorageId($id); + return $this->findByStorageId($storageId); + } + + /** + * @param string $path + * @return string + */ + private function formatPath($path) { + $path = Filesystem::normalizePath($path); + if (strlen($path) > 1) { + $path .= '/'; + } + return $path; + } +} diff --git a/lib/private/files/mount/mount.php b/lib/private/files/mount/mount.php new file mode 100644 index 00000000000..0ce2f5975c7 --- /dev/null +++ b/lib/private/files/mount/mount.php @@ -0,0 +1,148 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Mount; + +use \OC\Files\Filesystem; +use OC\Files\Storage\Loader; +use OC\Files\Storage\Storage; + +class Mount { + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage = null; + private $class; + private $storageId; + private $arguments = array(); + private $mountPoint; + + /** + * @var \OC\Files\Storage\Loader $loader + */ + private $loader; + + /** + * @param string | \OC\Files\Storage\Storage $storage + * @param string $mountpoint + * @param array $arguments (optional)\ + * @param \OC\Files\Storage\Loader $loader + */ + public function __construct($storage, $mountpoint, $arguments = null, $loader = null) { + if (is_null($arguments)) { + $arguments = array(); + } + if (is_null($loader)) { + $this->loader = new Loader(); + } else { + $this->loader = $loader; + } + + $mountpoint = $this->formatPath($mountpoint); + if ($storage instanceof Storage) { + $this->class = get_class($storage); + $this->storage = $this->loader->wrap($mountpoint, $storage); + } else { + // Update old classes to new namespace + if (strpos($storage, 'OC_Filestorage_') !== false) { + $storage = '\OC\Files\Storage\\' . substr($storage, 15); + } + $this->class = $storage; + $this->arguments = $arguments; + } + $this->mountPoint = $mountpoint; + } + + /** + * @return string + */ + public function getMountPoint() { + return $this->mountPoint; + } + + /** + * create the storage that is mounted + * + * @return \OC\Files\Storage\Storage + */ + private function createStorage() { + if (class_exists($this->class)) { + try { + return $this->loader->load($this->mountPoint, $this->class, $this->arguments); + } catch (\Exception $exception) { + \OC_Log::write('core', $exception->getMessage(), \OC_Log::ERROR); + return null; + } + } else { + \OC_Log::write('core', 'storage backend ' . $this->class . ' not found', \OC_Log::ERROR); + return null; + } + } + + /** + * @return \OC\Files\Storage\Storage + */ + public function getStorage() { + if (is_null($this->storage)) { + $this->storage = $this->createStorage(); + } + return $this->storage; + } + + /** + * @return string + */ + public function getStorageId() { + if (!$this->storageId) { + if (is_null($this->storage)) { + $storage = $this->createStorage(); //FIXME: start using exceptions + if (is_null($storage)) { + return null; + } + $this->storage = $storage; + } + $this->storageId = $this->storage->getId(); + if (strlen($this->storageId) > 64) { + $this->storageId = md5($this->storageId); + } + } + return $this->storageId; + } + + /** + * @param string $path + * @return string + */ + public function getInternalPath($path) { + if ($this->mountPoint === $path or $this->mountPoint . '/' === $path) { + $internalPath = ''; + } else { + $internalPath = substr($path, strlen($this->mountPoint)); + } + return $internalPath; + } + + /** + * @param string $path + * @return string + */ + private function formatPath($path) { + $path = Filesystem::normalizePath($path); + if (strlen($path) > 1) { + $path .= '/'; + } + return $path; + } + + /** + * @param callable $wrapper + */ + public function wrapStorage($wrapper) { + $this->storage = $wrapper($this->mountPoint, $this->storage); + } +} diff --git a/lib/private/files/node/file.php b/lib/private/files/node/file.php new file mode 100644 index 00000000000..75d5e0166b6 --- /dev/null +++ b/lib/private/files/node/file.php @@ -0,0 +1,155 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Node; + +use OCP\Files\NotPermittedException; + +class File extends Node implements \OCP\Files\File { + /** + * @return string + * @throws \OCP\Files\NotPermittedException + */ + public function getContent() { + if ($this->checkPermissions(\OCP\PERMISSION_READ)) { + /** + * @var \OC\Files\Storage\Storage $storage; + */ + return $this->view->file_get_contents($this->path); + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $data + * @throws \OCP\Files\NotPermittedException + */ + public function putContent($data) { + if ($this->checkPermissions(\OCP\PERMISSION_UPDATE)) { + $this->sendHooks(array('preWrite')); + $this->view->file_put_contents($this->path, $data); + $this->sendHooks(array('postWrite')); + } else { + throw new NotPermittedException(); + } + } + + /** + * @return string + */ + public function getMimeType() { + return $this->view->getMimeType($this->path); + } + + /** + * @param string $mode + * @return resource + * @throws \OCP\Files\NotPermittedException + */ + public function fopen($mode) { + $preHooks = array(); + $postHooks = array(); + $requiredPermissions = \OCP\PERMISSION_READ; + switch ($mode) { + case 'r+': + case 'rb+': + case 'w+': + case 'wb+': + case 'x+': + case 'xb+': + case 'a+': + case 'ab+': + case 'w': + case 'wb': + case 'x': + case 'xb': + case 'a': + case 'ab': + $preHooks[] = 'preWrite'; + $postHooks[] = 'postWrite'; + $requiredPermissions |= \OCP\PERMISSION_UPDATE; + break; + } + + if ($this->checkPermissions($requiredPermissions)) { + $this->sendHooks($preHooks); + $result = $this->view->fopen($this->path, $mode); + $this->sendHooks($postHooks); + return $result; + } else { + throw new NotPermittedException(); + } + } + + public function delete() { + if ($this->checkPermissions(\OCP\PERMISSION_DELETE)) { + $this->sendHooks(array('preDelete')); + $this->view->unlink($this->path); + $nonExisting = new NonExistingFile($this->root, $this->view, $this->path); + $this->root->emit('\OC\Files', 'postDelete', array($nonExisting)); + $this->exists = false; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function copy($targetPath) { + $targetPath = $this->normalizePath($targetPath); + $parent = $this->root->get(dirname($targetPath)); + if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { + $nonExisting = new NonExistingFile($this->root, $this->view, $targetPath); + $this->root->emit('\OC\Files', 'preCopy', array($this, $nonExisting)); + $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); + $this->view->copy($this->path, $targetPath); + $targetNode = $this->root->get($targetPath); + $this->root->emit('\OC\Files', 'postCopy', array($this, $targetNode)); + $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); + return $targetNode; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function move($targetPath) { + $targetPath = $this->normalizePath($targetPath); + $parent = $this->root->get(dirname($targetPath)); + if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { + $nonExisting = new NonExistingFile($this->root, $this->view, $targetPath); + $this->root->emit('\OC\Files', 'preRename', array($this, $nonExisting)); + $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); + $this->view->rename($this->path, $targetPath); + $targetNode = $this->root->get($targetPath); + $this->root->emit('\OC\Files', 'postRename', array($this, $targetNode)); + $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); + $this->path = $targetPath; + return $targetNode; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $type + * @param bool $raw + * @return string + */ + public function hash($type, $raw = false) { + return $this->view->hash($type, $this->path, $raw); + } +} diff --git a/lib/private/files/node/folder.php b/lib/private/files/node/folder.php new file mode 100644 index 00000000000..923f53821b2 --- /dev/null +++ b/lib/private/files/node/folder.php @@ -0,0 +1,382 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Node; + +use OC\Files\Cache\Cache; +use OC\Files\Cache\Scanner; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; + +class Folder extends Node implements \OCP\Files\Folder { + /** + * @param string $path path relative to the folder + * @return string + * @throws \OCP\Files\NotPermittedException + */ + public function getFullPath($path) { + if (!$this->isValidPath($path)) { + throw new NotPermittedException(); + } + return $this->path . $this->normalizePath($path); + } + + /** + * @param string $path + * @throws \OCP\Files\NotFoundException + * @return string + */ + public function getRelativePath($path) { + if ($this->path === '' or $this->path === '/') { + return $this->normalizePath($path); + } + if (strpos($path, $this->path) !== 0) { + throw new NotFoundException(); + } else { + $path = substr($path, strlen($this->path)); + if (strlen($path) === 0) { + return '/'; + } else { + return $this->normalizePath($path); + } + } + } + + /** + * check if a node is a (grand-)child of the folder + * + * @param \OC\Files\Node\Node $node + * @return bool + */ + public function isSubNode($node) { + return strpos($node->getPath(), $this->path . '/') === 0; + } + + /** + * get the content of this directory + * + * @throws \OCP\Files\NotFoundException + * @return Node[] + */ + public function getDirectoryListing() { + $result = array(); + + /** + * @var \OC\Files\Storage\Storage $storage + */ + list($storage, $internalPath) = $this->view->resolvePath($this->path); + if ($storage) { + $cache = $storage->getCache($internalPath); + $permissionsCache = $storage->getPermissionsCache($internalPath); + + //trigger cache update check + $this->view->getFileInfo($this->path); + + $files = $cache->getFolderContents($internalPath); + $permissions = $permissionsCache->getDirectoryPermissions($this->getId(), $this->root->getUser()->getUID()); + } else { + $files = array(); + } + + //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders + $mounts = $this->root->getMountsIn($this->path); + $dirLength = strlen($this->path); + foreach ($mounts as $mount) { + $subStorage = $mount->getStorage(); + if ($subStorage) { + $subCache = $subStorage->getCache(''); + + if ($subCache->getStatus('') === Cache::NOT_FOUND) { + $subScanner = $subStorage->getScanner(''); + $subScanner->scanFile(''); + } + + $rootEntry = $subCache->get(''); + if ($rootEntry) { + $relativePath = trim(substr($mount->getMountPoint(), $dirLength), '/'); + if ($pos = strpos($relativePath, '/')) { + //mountpoint inside subfolder add size to the correct folder + $entryName = substr($relativePath, 0, $pos); + foreach ($files as &$entry) { + if ($entry['name'] === $entryName) { + if ($rootEntry['size'] >= 0) { + $entry['size'] += $rootEntry['size']; + } else { + $entry['size'] = -1; + } + } + } + } else { //mountpoint in this folder, add an entry for it + $rootEntry['name'] = $relativePath; + $rootEntry['storageObject'] = $subStorage; + + //remove any existing entry with the same name + foreach ($files as $i => $file) { + if ($file['name'] === $rootEntry['name']) { + $files[$i] = null; + break; + } + } + $files[] = $rootEntry; + } + } + } + } + + foreach ($files as $file) { + if ($file) { + if (isset($permissions[$file['fileid']])) { + $file['permissions'] = $permissions[$file['fileid']]; + } + $node = $this->createNode($this->path . '/' . $file['name'], $file); + $result[] = $node; + } + } + + return $result; + } + + /** + * @param string $path + * @param array $info + * @return File|Folder + */ + protected function createNode($path, $info = array()) { + if (!isset($info['mimetype'])) { + $isDir = $this->view->is_dir($path); + } else { + $isDir = $info['mimetype'] === 'httpd/unix-directory'; + } + if ($isDir) { + return new Folder($this->root, $this->view, $path); + } else { + return new File($this->root, $this->view, $path); + } + } + + /** + * Get the node at $path + * + * @param string $path + * @return \OC\Files\Node\Node + * @throws \OCP\Files\NotFoundException + */ + public function get($path) { + return $this->root->get($this->getFullPath($path)); + } + + /** + * @param string $path + * @return bool + */ + public function nodeExists($path) { + try { + $this->get($path); + return true; + } catch (NotFoundException $e) { + return false; + } + } + + /** + * @param string $path + * @return \OC\Files\Node\Folder + * @throws \OCP\Files\NotPermittedException + */ + public function newFolder($path) { + if ($this->checkPermissions(\OCP\PERMISSION_CREATE)) { + $fullPath = $this->getFullPath($path); + $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath); + $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); + $this->root->emit('\OC\Files', 'preCreate', array($nonExisting)); + $this->view->mkdir($fullPath); + $node = new Folder($this->root, $this->view, $fullPath); + $this->root->emit('\OC\Files', 'postWrite', array($node)); + $this->root->emit('\OC\Files', 'postCreate', array($node)); + return $node; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $path + * @return \OC\Files\Node\File + * @throws \OCP\Files\NotPermittedException + */ + public function newFile($path) { + if ($this->checkPermissions(\OCP\PERMISSION_CREATE)) { + $fullPath = $this->getFullPath($path); + $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath); + $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); + $this->root->emit('\OC\Files', 'preCreate', array($nonExisting)); + $this->view->touch($fullPath); + $node = new File($this->root, $this->view, $fullPath); + $this->root->emit('\OC\Files', 'postWrite', array($node)); + $this->root->emit('\OC\Files', 'postCreate', array($node)); + return $node; + } else { + throw new NotPermittedException(); + } + } + + /** + * search for files with the name matching $query + * + * @param string $query + * @return \OC\Files\Node\Node[] + */ + public function search($query) { + return $this->searchCommon('%' . $query . '%', 'search'); + } + + /** + * search for files by mimetype + * + * @param string $mimetype + * @return Node[] + */ + public function searchByMime($mimetype) { + return $this->searchCommon($mimetype, 'searchByMime'); + } + + /** + * @param string $query + * @param string $method + * @return \OC\Files\Node\Node[] + */ + private function searchCommon($query, $method) { + $files = array(); + $rootLength = strlen($this->path); + /** + * @var \OC\Files\Storage\Storage $storage + */ + list($storage, $internalPath) = $this->view->resolvePath($this->path); + $internalRootLength = strlen($internalPath); + + $cache = $storage->getCache(''); + + $results = $cache->$method($query); + foreach ($results as $result) { + if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) { + $result['internalPath'] = $result['path']; + $result['path'] = substr($result['path'], $internalRootLength); + $result['storage'] = $storage; + $files[] = $result; + } + } + + $mounts = $this->root->getMountsIn($this->path); + foreach ($mounts as $mount) { + $storage = $mount->getStorage(); + if ($storage) { + $cache = $storage->getCache(''); + + $relativeMountPoint = substr($mount->getMountPoint(), $rootLength); + $results = $cache->$method($query); + foreach ($results as $result) { + $result['internalPath'] = $result['path']; + $result['path'] = $relativeMountPoint . $result['path']; + $result['storage'] = $storage; + $files[] = $result; + } + } + } + + $result = array(); + foreach ($files as $file) { + $result[] = $this->createNode($this->normalizePath($this->path . '/' . $file['path']), $file); + } + + return $result; + } + + /** + * @param $id + * @return \OC\Files\Node\Node[] + */ + public function getById($id) { + $nodes = $this->root->getById($id); + $result = array(); + foreach ($nodes as $node) { + $pathPart = substr($node->getPath(), 0, strlen($this->getPath()) + 1); + if ($this->path === '/' or $pathPart === $this->getPath() . '/') { + $result[] = $node; + } + } + return $result; + } + + public function getFreeSpace() { + return $this->view->free_space($this->path); + } + + /** + * @return bool + */ + public function isCreatable() { + return $this->checkPermissions(\OCP\PERMISSION_CREATE); + } + + public function delete() { + if ($this->checkPermissions(\OCP\PERMISSION_DELETE)) { + $this->sendHooks(array('preDelete')); + $this->view->rmdir($this->path); + $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path); + $this->root->emit('\OC\Files', 'postDelete', array($nonExisting)); + $this->exists = false; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function copy($targetPath) { + $targetPath = $this->normalizePath($targetPath); + $parent = $this->root->get(dirname($targetPath)); + if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { + $nonExisting = new NonExistingFolder($this->root, $this->view, $targetPath); + $this->root->emit('\OC\Files', 'preCopy', array($this, $nonExisting)); + $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); + $this->view->copy($this->path, $targetPath); + $targetNode = $this->root->get($targetPath); + $this->root->emit('\OC\Files', 'postCopy', array($this, $targetNode)); + $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); + return $targetNode; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function move($targetPath) { + $targetPath = $this->normalizePath($targetPath); + $parent = $this->root->get(dirname($targetPath)); + if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { + $nonExisting = new NonExistingFolder($this->root, $this->view, $targetPath); + $this->root->emit('\OC\Files', 'preRename', array($this, $nonExisting)); + $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); + $this->view->rename($this->path, $targetPath); + $targetNode = $this->root->get($targetPath); + $this->root->emit('\OC\Files', 'postRename', array($this, $targetNode)); + $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); + $this->path = $targetPath; + return $targetNode; + } else { + throw new NotPermittedException(); + } + } +} diff --git a/lib/private/files/node/node.php b/lib/private/files/node/node.php new file mode 100644 index 00000000000..063e2424a64 --- /dev/null +++ b/lib/private/files/node/node.php @@ -0,0 +1,245 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Node; + +use OC\Files\Cache\Cache; +use OC\Files\Cache\Scanner; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; + +class Node implements \OCP\Files\Node { + /** + * @var \OC\Files\View $view + */ + protected $view; + + /** + * @var \OC\Files\Node\Root $root + */ + protected $root; + + /** + * @var string $path + */ + protected $path; + + /** + * @param \OC\Files\View $view + * @param \OC\Files\Node\Root Root $root + * @param string $path + */ + public function __construct($root, $view, $path) { + $this->view = $view; + $this->root = $root; + $this->path = $path; + } + + /** + * @param string[] $hooks + */ + protected function sendHooks($hooks) { + foreach ($hooks as $hook) { + $this->root->emit('\OC\Files', $hook, array($this)); + } + } + + /** + * @param int $permissions + * @return bool + */ + protected function checkPermissions($permissions) { + return ($this->getPermissions() & $permissions) === $permissions; + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function move($targetPath) { + return; + } + + public function delete() { + return; + } + + /** + * @param string $targetPath + * @return \OC\Files\Node\Node + */ + public function copy($targetPath) { + return; + } + + /** + * @param int $mtime + * @throws \OCP\Files\NotPermittedException + */ + public function touch($mtime = null) { + if ($this->checkPermissions(\OCP\PERMISSION_UPDATE)) { + $this->sendHooks(array('preTouch')); + $this->view->touch($this->path, $mtime); + $this->sendHooks(array('postTouch')); + } else { + throw new NotPermittedException(); + } + } + + /** + * @return \OC\Files\Storage\Storage + * @throws \OCP\Files\NotFoundException + */ + public function getStorage() { + list($storage,) = $this->view->resolvePath($this->path); + return $storage; + } + + /** + * @return string + */ + public function getPath() { + return $this->path; + } + + /** + * @return string + */ + public function getInternalPath() { + list(, $internalPath) = $this->view->resolvePath($this->path); + return $internalPath; + } + + /** + * @return int + */ + public function getId() { + $info = $this->view->getFileInfo($this->path); + return $info['fileid']; + } + + /** + * @return array + */ + public function stat() { + return $this->view->stat($this->path); + } + + /** + * @return int + */ + public function getMTime() { + return $this->view->filemtime($this->path); + } + + /** + * @return int + */ + public function getSize() { + return $this->view->filesize($this->path); + } + + /** + * @return string + */ + public function getEtag() { + $info = $this->view->getFileInfo($this->path); + return $info['etag']; + } + + /** + * @return int + */ + public function getPermissions() { + $info = $this->view->getFileInfo($this->path); + return $info['permissions']; + } + + /** + * @return bool + */ + public function isReadable() { + return $this->checkPermissions(\OCP\PERMISSION_READ); + } + + /** + * @return bool + */ + public function isUpdateable() { + return $this->checkPermissions(\OCP\PERMISSION_UPDATE); + } + + /** + * @return bool + */ + public function isDeletable() { + return $this->checkPermissions(\OCP\PERMISSION_DELETE); + } + + /** + * @return bool + */ + public function isShareable() { + return $this->checkPermissions(\OCP\PERMISSION_SHARE); + } + + /** + * @return Node + */ + public function getParent() { + return $this->root->get(dirname($this->path)); + } + + /** + * @return string + */ + public function getName() { + return basename($this->path); + } + + /** + * @param string $path + * @return string + */ + protected function normalizePath($path) { + if ($path === '' or $path === '/') { + return '/'; + } + //no windows style slashes + $path = str_replace('\\', '/', $path); + //add leading slash + if ($path[0] !== '/') { + $path = '/' . $path; + } + //remove duplicate slashes + while (strpos($path, '//') !== false) { + $path = str_replace('//', '/', $path); + } + //remove trailing slash + $path = rtrim($path, '/'); + + return $path; + } + + /** + * check if the requested path is valid + * + * @param string $path + * @return bool + */ + public function isValidPath($path) { + if (!$path || $path[0] !== '/') { + $path = '/' . $path; + } + if (strstr($path, '/../') || strrchr($path, '/') === '/..') { + return false; + } + return true; + } +} diff --git a/lib/private/files/node/nonexistingfile.php b/lib/private/files/node/nonexistingfile.php new file mode 100644 index 00000000000..d45076f7fee --- /dev/null +++ b/lib/private/files/node/nonexistingfile.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Node; + +use OCP\Files\NotFoundException; + +class NonExistingFile extends File { + /** + * @param string $newPath + * @throws \OCP\Files\NotFoundException + */ + public function rename($newPath) { + throw new NotFoundException(); + } + + public function delete() { + throw new NotFoundException(); + } + + public function copy($newPath) { + throw new NotFoundException(); + } + + public function touch($mtime = null) { + throw new NotFoundException(); + } + + public function getId() { + throw new NotFoundException(); + } + + public function stat() { + throw new NotFoundException(); + } + + public function getMTime() { + throw new NotFoundException(); + } + + public function getSize() { + throw new NotFoundException(); + } + + public function getEtag() { + throw new NotFoundException(); + } + + public function getPermissions() { + throw new NotFoundException(); + } + + public function isReadable() { + throw new NotFoundException(); + } + + public function isUpdateable() { + throw new NotFoundException(); + } + + public function isDeletable() { + throw new NotFoundException(); + } + + public function isShareable() { + throw new NotFoundException(); + } + + public function getContent() { + throw new NotFoundException(); + } + + public function putContent($data) { + throw new NotFoundException(); + } + + public function getMimeType() { + throw new NotFoundException(); + } + + public function fopen($mode) { + throw new NotFoundException(); + } +} diff --git a/lib/private/files/node/nonexistingfolder.php b/lib/private/files/node/nonexistingfolder.php new file mode 100644 index 00000000000..0346cbf1e21 --- /dev/null +++ b/lib/private/files/node/nonexistingfolder.php @@ -0,0 +1,113 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Node; + +use OCP\Files\NotFoundException; + +class NonExistingFolder extends Folder { + /** + * @param string $newPath + * @throws \OCP\Files\NotFoundException + */ + public function rename($newPath) { + throw new NotFoundException(); + } + + public function delete() { + throw new NotFoundException(); + } + + public function copy($newPath) { + throw new NotFoundException(); + } + + public function touch($mtime = null) { + throw new NotFoundException(); + } + + public function getId() { + throw new NotFoundException(); + } + + public function stat() { + throw new NotFoundException(); + } + + public function getMTime() { + throw new NotFoundException(); + } + + public function getSize() { + throw new NotFoundException(); + } + + public function getEtag() { + throw new NotFoundException(); + } + + public function getPermissions() { + throw new NotFoundException(); + } + + public function isReadable() { + throw new NotFoundException(); + } + + public function isUpdateable() { + throw new NotFoundException(); + } + + public function isDeletable() { + throw new NotFoundException(); + } + + public function isShareable() { + throw new NotFoundException(); + } + + public function get($path) { + throw new NotFoundException(); + } + + public function getDirectoryListing() { + throw new NotFoundException(); + } + + public function nodeExists($path) { + return false; + } + + public function newFolder($path) { + throw new NotFoundException(); + } + + public function newFile($path) { + throw new NotFoundException(); + } + + public function search($pattern) { + throw new NotFoundException(); + } + + public function searchByMime($mime) { + throw new NotFoundException(); + } + + public function getById($id) { + throw new NotFoundException(); + } + + public function getFreeSpace() { + throw new NotFoundException(); + } + + public function isCreatable() { + throw new NotFoundException(); + } +} diff --git a/lib/private/files/node/root.php b/lib/private/files/node/root.php new file mode 100644 index 00000000000..e3d58476e9c --- /dev/null +++ b/lib/private/files/node/root.php @@ -0,0 +1,337 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Node; + +use OC\Files\Cache\Cache; +use OC\Files\Cache\Scanner; +use OC\Files\Mount\Manager; +use OC\Files\Mount\Mount; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OC\Hooks\Emitter; +use OC\Hooks\PublicEmitter; + +/** + * Class Root + * + * Hooks available in scope \OC\Files + * - preWrite(\OCP\Files\Node $node) + * - postWrite(\OCP\Files\Node $node) + * - preCreate(\OCP\Files\Node $node) + * - postCreate(\OCP\Files\Node $node) + * - preDelete(\OCP\Files\Node $node) + * - postDelete(\OCP\Files\Node $node) + * - preTouch(\OC\FilesP\Node $node, int $mtime) + * - postTouch(\OCP\Files\Node $node) + * - preCopy(\OCP\Files\Node $source, \OCP\Files\Node $target) + * - postCopy(\OCP\Files\Node $source, \OCP\Files\Node $target) + * - preRename(\OCP\Files\Node $source, \OCP\Files\Node $target) + * - postRename(\OCP\Files\Node $source, \OCP\Files\Node $target) + * + * @package OC\Files\Node + */ +class Root extends Folder implements Emitter { + + /** + * @var \OC\Files\Mount\Manager $mountManager + */ + private $mountManager; + + /** + * @var \OC\Hooks\PublicEmitter + */ + private $emitter; + + /** + * @var \OC\User\User $user + */ + private $user; + + /** + * @param \OC\Files\Mount\Manager $manager + * @param \OC\Files\View $view + * @param \OC\User\User $user + */ + public function __construct($manager, $view, $user) { + parent::__construct($this, $view, ''); + $this->mountManager = $manager; + $this->user = $user; + $this->emitter = new PublicEmitter(); + } + + /** + * Get the user for which the filesystem is setup + * + * @return \OC\User\User + */ + public function getUser() { + return $this->user; + } + + /** + * @param string $scope + * @param string $method + * @param callable $callback + */ + public function listen($scope, $method, $callback) { + $this->emitter->listen($scope, $method, $callback); + } + + /** + * @param string $scope optional + * @param string $method optional + * @param callable $callback optional + */ + public function removeListener($scope = null, $method = null, $callback = null) { + $this->emitter->removeListener($scope, $method, $callback); + } + + /** + * @param string $scope + * @param string $method + * @param array $arguments + */ + public function emit($scope, $method, $arguments = array()) { + $this->emitter->emit($scope, $method, $arguments); + } + + /** + * @param \OC\Files\Storage\Storage $storage + * @param string $mountPoint + * @param array $arguments + */ + public function mount($storage, $mountPoint, $arguments = array()) { + $mount = new Mount($storage, $mountPoint, $arguments); + $this->mountManager->addMount($mount); + } + + /** + * @param string $mountPoint + * @return \OC\Files\Mount\Mount + */ + public function getMount($mountPoint) { + return $this->mountManager->find($mountPoint); + } + + /** + * @param string $mountPoint + * @return \OC\Files\Mount\Mount[] + */ + public function getMountsIn($mountPoint) { + return $this->mountManager->findIn($mountPoint); + } + + /** + * @param string $storageId + * @return \OC\Files\Mount\Mount[] + */ + public function getMountByStorageId($storageId) { + return $this->mountManager->findByStorageId($storageId); + } + + /** + * @param int $numericId + * @return Mount[] + */ + public function getMountByNumericStorageId($numericId) { + return $this->mountManager->findByNumericId($numericId); + } + + /** + * @param \OC\Files\Mount\Mount $mount + */ + public function unMount($mount) { + $this->mountManager->remove($mount); + } + + /** + * @param string $path + * @throws \OCP\Files\NotFoundException + * @throws \OCP\Files\NotPermittedException + * @return Node + */ + public function get($path) { + $path = $this->normalizePath($path); + if ($this->isValidPath($path)) { + $fullPath = $this->getFullPath($path); + if ($this->view->file_exists($fullPath)) { + return $this->createNode($fullPath); + } else { + throw new NotFoundException(); + } + } else { + throw new NotPermittedException(); + } + } + + /** + * search file by id + * + * An array is returned because in the case where a single storage is mounted in different places the same file + * can exist in different places + * + * @param int $id + * @throws \OCP\Files\NotFoundException + * @return Node[] + */ + public function getById($id) { + $result = Cache::getById($id); + if (is_null($result)) { + throw new NotFoundException(); + } else { + list($storageId, $internalPath) = $result; + $nodes = array(); + $mounts = $this->mountManager->findByStorageId($storageId); + foreach ($mounts as $mount) { + $nodes[] = $this->get($mount->getMountPoint() . $internalPath); + } + return $nodes; + } + + } + + //most operations cant be done on the root + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function rename($targetPath) { + throw new NotPermittedException(); + } + + public function delete() { + throw new NotPermittedException(); + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function copy($targetPath) { + throw new NotPermittedException(); + } + + /** + * @param int $mtime + * @throws \OCP\Files\NotPermittedException + */ + public function touch($mtime = null) { + throw new NotPermittedException(); + } + + /** + * @return \OC\Files\Storage\Storage + * @throws \OCP\Files\NotFoundException + */ + public function getStorage() { + throw new NotFoundException(); + } + + /** + * @return string + */ + public function getPath() { + return '/'; + } + + /** + * @return string + */ + public function getInternalPath() { + return ''; + } + + /** + * @return int + */ + public function getId() { + return null; + } + + /** + * @return array + */ + public function stat() { + return null; + } + + /** + * @return int + */ + public function getMTime() { + return null; + } + + /** + * @return int + */ + public function getSize() { + return null; + } + + /** + * @return string + */ + public function getEtag() { + return null; + } + + /** + * @return int + */ + public function getPermissions() { + return \OCP\PERMISSION_CREATE; + } + + /** + * @return bool + */ + public function isReadable() { + return false; + } + + /** + * @return bool + */ + public function isUpdateable() { + return false; + } + + /** + * @return bool + */ + public function isDeletable() { + return false; + } + + /** + * @return bool + */ + public function isShareable() { + return false; + } + + /** + * @return Node + * @throws \OCP\Files\NotFoundException + */ + public function getParent() { + throw new NotFoundException(); + } + + /** + * @return string + */ + public function getName() { + return ''; + } +} diff --git a/lib/private/files/storage/common.php b/lib/private/files/storage/common.php new file mode 100644 index 00000000000..a5b79f0e967 --- /dev/null +++ b/lib/private/files/storage/common.php @@ -0,0 +1,374 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +/** + * Storage backend class for providing common filesystem operation methods + * which are not storage-backend specific. + * + * \OC\Files\Storage\Common is never used directly; it is extended by all other + * storage backends, where its methods may be overridden, and additional + * (backend-specific) methods are defined. + * + * Some \OC\Files\Storage\Common methods call functions which are first defined + * in classes which extend it, e.g. $this->stat() . + */ + +abstract class Common implements \OC\Files\Storage\Storage { + private $cache; + private $scanner; + private $permissioncache; + private $watcher; + private $storageCache; + + public function __construct($parameters) { + } + + public function is_dir($path) { + return $this->filetype($path) == 'dir'; + } + + public function is_file($path) { + return $this->filetype($path) == 'file'; + } + + public function filesize($path) { + if ($this->is_dir($path)) { + return 0; //by definition + } else { + $stat = $this->stat($path); + if (isset($stat['size'])) { + return $stat['size']; + } else { + return 0; + } + } + } + + public function isCreatable($path) { + if ($this->is_dir($path) && $this->isUpdatable($path)) { + return true; + } + return false; + } + + public function isDeletable($path) { + return $this->isUpdatable($path); + } + + public function isSharable($path) { + return $this->isReadable($path); + } + + public function getPermissions($path) { + $permissions = 0; + if ($this->isCreatable($path)) { + $permissions |= \OCP\PERMISSION_CREATE; + } + if ($this->isReadable($path)) { + $permissions |= \OCP\PERMISSION_READ; + } + if ($this->isUpdatable($path)) { + $permissions |= \OCP\PERMISSION_UPDATE; + } + if ($this->isDeletable($path)) { + $permissions |= \OCP\PERMISSION_DELETE; + } + if ($this->isSharable($path)) { + $permissions |= \OCP\PERMISSION_SHARE; + } + return $permissions; + } + + public function filemtime($path) { + $stat = $this->stat($path); + if (isset($stat['mtime'])) { + return $stat['mtime']; + } else { + return 0; + } + } + + public function file_get_contents($path) { + $handle = $this->fopen($path, "r"); + if (!$handle) { + return false; + } + $size = $this->filesize($path); + if ($size == 0) { + return ''; + } + return fread($handle, $size); + } + + public function file_put_contents($path, $data) { + $handle = $this->fopen($path, "w"); + return fwrite($handle, $data); + } + + public function rename($path1, $path2) { + if ($this->copy($path1, $path2)) { + return $this->unlink($path1); + } else { + return false; + } + } + + public function copy($path1, $path2) { + $source = $this->fopen($path1, 'r'); + $target = $this->fopen($path2, 'w'); + list($count, $result) = \OC_Helper::streamCopy($source, $target); + return $result; + } + + /** + * @brief Deletes all files and folders recursively within a directory + * @param string $directory The directory whose contents will be deleted + * @param bool $empty Flag indicating whether directory will be emptied + * @returns bool + * + * @note By default the directory specified by $directory will be + * deleted together with its contents. To avoid this set $empty to true + */ + public function deleteAll($directory, $empty = false) { + $directory = trim($directory, '/'); + if (!$this->is_dir($directory) || !$this->isReadable($directory)) { + return false; + } else { + $directoryHandle = $this->opendir($directory); + if(is_resource($directoryHandle)) { + while (($contents = readdir($directoryHandle)) !== false) { + if (!\OC\Files\Filesystem::isIgnoredDir($contents)) { + $path = $directory . '/' . $contents; + if ($this->is_dir($path)) { + $this->deleteAll($path); + } else { + $this->unlink($path); + } + } + } + } + if ($empty === false) { + if (!$this->rmdir($directory)) { + return false; + } + } + return true; + } + + } + + public function getMimeType($path) { + if (!$this->file_exists($path)) { + return false; + } + if ($this->is_dir($path)) { + return 'httpd/unix-directory'; + } + $source = $this->fopen($path, 'r'); + if (!$source) { + return false; + } + $head = fread($source, 8192); //8kb should suffice to determine a mimetype + if ($pos = strrpos($path, '.')) { + $extension = substr($path, $pos); + } else { + $extension = ''; + } + $tmpFile = \OC_Helper::tmpFile($extension); + file_put_contents($tmpFile, $head); + $mime = \OC_Helper::getMimeType($tmpFile); + unlink($tmpFile); + return $mime; + } + + public function hash($type, $path, $raw = false) { + $tmpFile = $this->getLocalFile($path); + $hash = hash($type, $tmpFile, $raw); + unlink($tmpFile); + return $hash; + } + + public function search($query) { + return $this->searchInDir($query); + } + + public function getLocalFile($path) { + return $this->toTmpFile($path); + } + + private function toTmpFile($path) { //no longer in the storage api, still useful here + $source = $this->fopen($path, 'r'); + if (!$source) { + return false; + } + if ($pos = strrpos($path, '.')) { + $extension = substr($path, $pos); + } else { + $extension = ''; + } + $tmpFile = \OC_Helper::tmpFile($extension); + $target = fopen($tmpFile, 'w'); + \OC_Helper::streamCopy($source, $target); + return $tmpFile; + } + + public function getLocalFolder($path) { + $baseDir = \OC_Helper::tmpFolder(); + $this->addLocalFolder($path, $baseDir); + return $baseDir; + } + + private function addLocalFolder($path, $target) { + $dh = $this->opendir($path); + if(is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if ($file !== '.' and $file !== '..') { + if ($this->is_dir($path . '/' . $file)) { + mkdir($target . '/' . $file); + $this->addLocalFolder($path . '/' . $file, $target . '/' . $file); + } else { + $tmp = $this->toTmpFile($path . '/' . $file); + rename($tmp, $target . '/' . $file); + } + } + } + } + } + + protected function searchInDir($query, $dir = '') { + $files = array(); + $dh = $this->opendir($dir); + if (is_resource($dh)) { + while (($item = readdir($dh)) !== false) { + if ($item == '.' || $item == '..') continue; + if (strstr(strtolower($item), strtolower($query)) !== false) { + $files[] = $dir . '/' . $item; + } + if ($this->is_dir($dir . '/' . $item)) { + $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item)); + } + } + } + return $files; + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + return $this->filemtime($path) > $time; + } + + public function getCache($path = '') { + if (!isset($this->cache)) { + $this->cache = new \OC\Files\Cache\Cache($this); + } + return $this->cache; + } + + public function getScanner($path = '') { + if (!isset($this->scanner)) { + $this->scanner = new \OC\Files\Cache\Scanner($this); + } + return $this->scanner; + } + + public function getPermissionsCache($path = '') { + if (!isset($this->permissioncache)) { + $this->permissioncache = new \OC\Files\Cache\Permissions($this); + } + return $this->permissioncache; + } + + public function getWatcher($path = '') { + if (!isset($this->watcher)) { + $this->watcher = new \OC\Files\Cache\Watcher($this); + } + return $this->watcher; + } + + public function getStorageCache(){ + if (!isset($this->storageCache)) { + $this->storageCache = new \OC\Files\Cache\Storage($this); + } + return $this->storageCache; + } + + /** + * get the owner of a path + * + * @param string $path The path to get the owner + * @return string uid or false + */ + public function getOwner($path) { + return \OC_User::getUser(); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + $ETagFunction = \OC_Connector_Sabre_Node::$ETagFunction; + if ($ETagFunction) { + $hash = call_user_func($ETagFunction, $path); + return $hash; + } else { + return uniqid(); + } + } + + /** + * clean a path, i.e. remove all redundant '.' and '..' + * making sure that it can't point to higher than '/' + * + * @param $path The path to clean + * @return string cleaned path + */ + public function cleanPath($path) { + if (strlen($path) == 0 or $path[0] != '/') { + $path = '/' . $path; + } + + $output = array(); + foreach (explode('/', $path) as $chunk) { + if ($chunk == '..') { + array_pop($output); + } else if ($chunk == '.') { + } else { + $output[] = $chunk; + } + } + return implode('/', $output); + } + + public function test() { + if ($this->stat('')) { + return true; + } + return false; + } + + /** + * get the free space in the storage + * + * @param $path + * @return int + */ + public function free_space($path) { + return \OC\Files\SPACE_UNKNOWN; + } +} diff --git a/lib/private/files/storage/commontest.php b/lib/private/files/storage/commontest.php new file mode 100644 index 00000000000..c3f1eb31955 --- /dev/null +++ b/lib/private/files/storage/commontest.php @@ -0,0 +1,80 @@ +<?php + +/** +* ownCloud +* +* @author Robin Appelman +* @copyright 2012 Robin Appelman icewind@owncloud.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +/** + * test implementation for \OC\Files\Storage\Common with \OC\Files\Storage\Local + */ + +namespace OC\Files\Storage; + +class CommonTest extends \OC\Files\Storage\Common{ + /** + * underlying local storage used for missing functions + * @var \OC\Files\Storage\Local + */ + private $storage; + + public function __construct($params) { + $this->storage=new \OC\Files\Storage\Local($params); + } + + public function getId(){ + return 'test::'.$this->storage->getId(); + } + public function mkdir($path) { + return $this->storage->mkdir($path); + } + public function rmdir($path) { + return $this->storage->rmdir($path); + } + public function opendir($path) { + return $this->storage->opendir($path); + } + public function stat($path) { + return $this->storage->stat($path); + } + public function filetype($path) { + return $this->storage->filetype($path); + } + public function isReadable($path) { + return $this->storage->isReadable($path); + } + public function isUpdatable($path) { + return $this->storage->isUpdatable($path); + } + public function file_exists($path) { + return $this->storage->file_exists($path); + } + public function unlink($path) { + return $this->storage->unlink($path); + } + public function fopen($path, $mode) { + return $this->storage->fopen($path, $mode); + } + public function free_space($path) { + return $this->storage->free_space($path); + } + public function touch($path, $mtime=null) { + return $this->storage->touch($path, $mtime); + } +} diff --git a/lib/private/files/storage/loader.php b/lib/private/files/storage/loader.php new file mode 100644 index 00000000000..2572ef443bc --- /dev/null +++ b/lib/private/files/storage/loader.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +class Loader { + /** + * @var callable[] $storageWrappers + */ + private $storageWrappers = array(); + + /** + * allow modifier storage behaviour by adding wrappers around storages + * + * $callback should be a function of type (string $mountPoint, Storage $storage) => Storage + * + * @param callable $callback + */ + public function addStorageWrapper($callback) { + $this->storageWrappers[] = $callback; + } + + public function load($mountPoint, $class, $arguments) { + return $this->wrap($mountPoint, new $class($arguments)); + } + + public function wrap($mountPoint, $storage) { + foreach ($this->storageWrappers as $wrapper) { + $storage = $wrapper($mountPoint, $storage); + } + return $storage; + } +} diff --git a/lib/private/files/storage/local.php b/lib/private/files/storage/local.php new file mode 100644 index 00000000000..5209fabc30a --- /dev/null +++ b/lib/private/files/storage/local.php @@ -0,0 +1,310 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +if (\OC_Util::runningOnWindows()) { + class Local extends MappedLocal { + + } +} else { + + /** + * for local filestore, we only have to map the paths + */ + class Local extends \OC\Files\Storage\Common { + protected $datadir; + + public function __construct($arguments) { + $this->datadir = $arguments['datadir']; + if (substr($this->datadir, -1) !== '/') { + $this->datadir .= '/'; + } + } + + public function __destruct() { + } + + public function getId() { + return 'local::' . $this->datadir; + } + + public function mkdir($path) { + return @mkdir($this->datadir . $path); + } + + public function rmdir($path) { + try { + $it = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($this->datadir . $path), + \RecursiveIteratorIterator::CHILD_FIRST + ); + foreach ($it as $file) { + /** + * @var \SplFileInfo $file + */ + if (in_array($file->getBasename(), array('.', '..'))) { + continue; + } elseif ($file->isDir()) { + rmdir($file->getPathname()); + } elseif ($file->isFile() || $file->isLink()) { + unlink($file->getPathname()); + } + } + return rmdir($this->datadir . $path); + } catch (\UnexpectedValueException $e) { + return false; + } + } + + public function opendir($path) { + return opendir($this->datadir . $path); + } + + public function is_dir($path) { + if (substr($path, -1) == '/') { + $path = substr($path, 0, -1); + } + return is_dir($this->datadir . $path); + } + + public function is_file($path) { + return is_file($this->datadir . $path); + } + + public function stat($path) { + $fullPath = $this->datadir . $path; + $statResult = stat($fullPath); + + if ($statResult['size'] < 0) { + $size = self::getFileSizeFromOS($fullPath); + $statResult['size'] = $size; + $statResult[7] = $size; + } + return $statResult; + } + + public function filetype($path) { + $filetype = filetype($this->datadir . $path); + if ($filetype == 'link') { + $filetype = filetype(realpath($this->datadir . $path)); + } + return $filetype; + } + + public function filesize($path) { + if ($this->is_dir($path)) { + return 0; + } else { + $fullPath = $this->datadir . $path; + $fileSize = filesize($fullPath); + if ($fileSize < 0) { + return self::getFileSizeFromOS($fullPath); + } + + return $fileSize; + } + } + + public function isReadable($path) { + return is_readable($this->datadir . $path); + } + + public function isUpdatable($path) { + return is_writable($this->datadir . $path); + } + + public function file_exists($path) { + return file_exists($this->datadir . $path); + } + + public function filemtime($path) { + return filemtime($this->datadir . $path); + } + + public function touch($path, $mtime = null) { + // sets the modification time of the file to the given value. + // If mtime is nil the current time is set. + // note that the access time of the file always changes to the current time. + if ($this->file_exists($path) and !$this->isUpdatable($path)) { + return false; + } + if (!is_null($mtime)) { + $result = touch($this->datadir . $path, $mtime); + } else { + $result = touch($this->datadir . $path); + } + if ($result) { + clearstatcache(true, $this->datadir . $path); + } + + return $result; + } + + public function file_get_contents($path) { + return file_get_contents($this->datadir . $path); + } + + public function file_put_contents($path, $data) { //trigger_error("$path = ".var_export($path, 1)); + return file_put_contents($this->datadir . $path, $data); + } + + public function unlink($path) { + return $this->delTree($path); + } + + public function rename($path1, $path2) { + if (!$this->isUpdatable($path1)) { + \OC_Log::write('core', 'unable to rename, file is not writable : ' . $path1, \OC_Log::ERROR); + return false; + } + if (!$this->file_exists($path1)) { + \OC_Log::write('core', 'unable to rename, file does not exists : ' . $path1, \OC_Log::ERROR); + return false; + } + + if ($return = rename($this->datadir . $path1, $this->datadir . $path2)) { + } + return $return; + } + + public function copy($path1, $path2) { + if ($this->is_dir($path2)) { + if (!$this->file_exists($path2)) { + $this->mkdir($path2); + } + $source = substr($path1, strrpos($path1, '/') + 1); + $path2 .= $source; + } + return copy($this->datadir . $path1, $this->datadir . $path2); + } + + public function fopen($path, $mode) { + if ($return = fopen($this->datadir . $path, $mode)) { + switch ($mode) { + case 'r': + break; + case 'r+': + case 'w+': + case 'x+': + case 'a+': + break; + case 'w': + case 'x': + case 'a': + break; + } + } + return $return; + } + + public function getMimeType($path) { + if ($this->isReadable($path)) { + return \OC_Helper::getMimeType($this->datadir . $path); + } else { + return false; + } + } + + private function delTree($dir) { + $dirRelative = $dir; + $dir = $this->datadir . $dir; + if (!file_exists($dir)) return true; + if (!is_dir($dir) || is_link($dir)) return unlink($dir); + foreach (scandir($dir) as $item) { + if ($item == '.' || $item == '..') continue; + if (is_file($dir . '/' . $item)) { + if (unlink($dir . '/' . $item)) { + } + } elseif (is_dir($dir . '/' . $item)) { + if (!$this->delTree($dirRelative . "/" . $item)) { + return false; + }; + } + } + if ($return = rmdir($dir)) { + } + return $return; + } + + private static function getFileSizeFromOS($fullPath) { + $name = strtolower(php_uname('s')); + // Windows OS: we use COM to access the filesystem + if (strpos($name, 'win') !== false) { + if (class_exists('COM')) { + $fsobj = new \COM("Scripting.FileSystemObject"); + $f = $fsobj->GetFile($fullPath); + return $f->Size; + } + } else if (strpos($name, 'bsd') !== false) { + if (\OC_Helper::is_function_enabled('exec')) { + return (float)exec('stat -f %z ' . escapeshellarg($fullPath)); + } + } else if (strpos($name, 'linux') !== false) { + if (\OC_Helper::is_function_enabled('exec')) { + return (float)exec('stat -c %s ' . escapeshellarg($fullPath)); + } + } else { + \OC_Log::write('core', + 'Unable to determine file size of "' . $fullPath . '". Unknown OS: ' . $name, + \OC_Log::ERROR); + } + + return 0; + } + + public function hash($path, $type, $raw = false) { + return hash_file($type, $this->datadir . $path, $raw); + } + + public function free_space($path) { + $space = @disk_free_space($this->datadir . $path); + if ($space === false) { + return \OC\Files\SPACE_UNKNOWN; + } + return $space; + } + + public function search($query) { + return $this->searchInDir($query); + } + + public function getLocalFile($path) { + return $this->datadir . $path; + } + + public function getLocalFolder($path) { + return $this->datadir . $path; + } + + protected function searchInDir($query, $dir = '') { + $files = array(); + foreach (scandir($this->datadir . $dir) as $item) { + if ($item == '.' || $item == '..') continue; + if (strstr(strtolower($item), strtolower($query)) !== false) { + $files[] = $dir . '/' . $item; + } + if (is_dir($this->datadir . $dir . '/' . $item)) { + $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item)); + } + } + return $files; + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + return $this->filemtime($path) > $time; + } + } +} diff --git a/lib/private/files/storage/mappedlocal.php b/lib/private/files/storage/mappedlocal.php new file mode 100644 index 00000000000..ba5ac4191c5 --- /dev/null +++ b/lib/private/files/storage/mappedlocal.php @@ -0,0 +1,365 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace OC\Files\Storage; + +/** + * for local filestore, we only have to map the paths + */ +class MappedLocal extends \OC\Files\Storage\Common{ + protected $datadir; + private $mapper; + + public function __construct($arguments) { + $this->datadir=$arguments['datadir']; + if(substr($this->datadir, -1)!=='/') { + $this->datadir.='/'; + } + + $this->mapper= new \OC\Files\Mapper($this->datadir); + } + public function __destruct() { + if (defined('PHPUNIT_RUN')) { + $this->mapper->removePath($this->datadir, true, true); + } + } + public function getId(){ + return 'local::'.$this->datadir; + } + public function mkdir($path) { + return @mkdir($this->buildPath($path)); + } + public function rmdir($path) { + try { + $it = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($this->buildPath($path)), + \RecursiveIteratorIterator::CHILD_FIRST + ); + foreach ($it as $file) { + /** + * @var \SplFileInfo $file + */ + if (in_array($file->getBasename(), array('.', '..'))) { + continue; + } elseif ($file->isDir()) { + rmdir($file->getPathname()); + } elseif ($file->isFile() || $file->isLink()) { + unlink($file->getPathname()); + } + } + if ($result = @rmdir($this->buildPath($path))) { + $this->cleanMapper($path); + } + return $result; + } catch (\UnexpectedValueException $e) { + return false; + } + } + public function opendir($path) { + $files = array('.', '..'); + $physicalPath= $this->buildPath($path); + + $logicalPath = $this->mapper->physicalToLogic($physicalPath); + $dh = opendir($physicalPath); + if(is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if ($file === '.' or $file === '..') { + continue; + } + + $logicalFilePath = $this->mapper->physicalToLogic($physicalPath.'/'.$file); + + $file= $this->mapper->stripRootFolder($logicalFilePath, $logicalPath); + $file = $this->stripLeading($file); + $files[]= $file; + } + } + + \OC\Files\Stream\Dir::register('local-win32'.$path, $files); + return opendir('fakedir://local-win32'.$path); + } + public function is_dir($path) { + if(substr($path, -1)=='/') { + $path=substr($path, 0, -1); + } + return is_dir($this->buildPath($path)); + } + public function is_file($path) { + return is_file($this->buildPath($path)); + } + public function stat($path) { + $fullPath = $this->buildPath($path); + $statResult = stat($fullPath); + + if ($statResult['size'] < 0) { + $size = self::getFileSizeFromOS($fullPath); + $statResult['size'] = $size; + $statResult[7] = $size; + } + return $statResult; + } + public function filetype($path) { + $filetype=filetype($this->buildPath($path)); + if($filetype=='link') { + $filetype=filetype(realpath($this->buildPath($path))); + } + return $filetype; + } + public function filesize($path) { + if($this->is_dir($path)) { + return 0; + }else{ + $fullPath = $this->buildPath($path); + $fileSize = filesize($fullPath); + if ($fileSize < 0) { + return self::getFileSizeFromOS($fullPath); + } + + return $fileSize; + } + } + public function isReadable($path) { + return is_readable($this->buildPath($path)); + } + public function isUpdatable($path) { + return is_writable($this->buildPath($path)); + } + public function file_exists($path) { + return file_exists($this->buildPath($path)); + } + public function filemtime($path) { + return filemtime($this->buildPath($path)); + } + public function touch($path, $mtime=null) { + // sets the modification time of the file to the given value. + // If mtime is nil the current time is set. + // note that the access time of the file always changes to the current time. + if(!is_null($mtime)) { + $result=touch( $this->buildPath($path), $mtime ); + }else{ + $result=touch( $this->buildPath($path)); + } + if( $result ) { + clearstatcache( true, $this->buildPath($path) ); + } + + return $result; + } + public function file_get_contents($path) { + return file_get_contents($this->buildPath($path)); + } + public function file_put_contents($path, $data) { + return file_put_contents($this->buildPath($path), $data); + } + public function unlink($path) { + return $this->delTree($path); + } + public function rename($path1, $path2) { + if (!$this->isUpdatable($path1)) { + \OC_Log::write('core', 'unable to rename, file is not writable : '.$path1, \OC_Log::ERROR); + return false; + } + if(! $this->file_exists($path1)) { + \OC_Log::write('core', 'unable to rename, file does not exists : '.$path1, \OC_Log::ERROR); + return false; + } + + $physicPath1 = $this->buildPath($path1); + $physicPath2 = $this->buildPath($path2); + if($return=rename($physicPath1, $physicPath2)) { + // mapper needs to create copies or all children + $this->copyMapping($path1, $path2); + $this->cleanMapper($physicPath1, false, true); + } + return $return; + } + public function copy($path1, $path2) { + if($this->is_dir($path2)) { + if(!$this->file_exists($path2)) { + $this->mkdir($path2); + } + $source=substr($path1, strrpos($path1, '/')+1); + $path2.=$source; + } + if($return=copy($this->buildPath($path1), $this->buildPath($path2))) { + // mapper needs to create copies or all children + $this->copyMapping($path1, $path2); + } + return $return; + } + public function fopen($path, $mode) { + if($return=fopen($this->buildPath($path), $mode)) { + switch($mode) { + case 'r': + break; + case 'r+': + case 'w+': + case 'x+': + case 'a+': + break; + case 'w': + case 'x': + case 'a': + break; + } + } + return $return; + } + + public function getMimeType($path) { + if($this->isReadable($path)) { + return \OC_Helper::getMimeType($this->buildPath($path)); + }else{ + return false; + } + } + + private function delTree($dir, $isLogicPath=true) { + $dirRelative=$dir; + if ($isLogicPath) { + $dir=$this->buildPath($dir); + } + if (!file_exists($dir)) { + return true; + } + if (!is_dir($dir) || is_link($dir)) { + if($return=unlink($dir)) { + $this->cleanMapper($dir, false); + return $return; + } + } + foreach (scandir($dir) as $item) { + if ($item == '.' || $item == '..') { + continue; + } + if(is_file($dir.'/'.$item)) { + if(unlink($dir.'/'.$item)) { + $this->cleanMapper($dir.'/'.$item, false); + } + }elseif(is_dir($dir.'/'.$item)) { + if (!$this->delTree($dir. "/" . $item, false)) { + return false; + }; + } + } + if($return=rmdir($dir)) { + $this->cleanMapper($dir, false); + } + return $return; + } + + private static function getFileSizeFromOS($fullPath) { + $name = strtolower(php_uname('s')); + // Windows OS: we use COM to access the filesystem + if (strpos($name, 'win') !== false) { + if (class_exists('COM')) { + $fsobj = new \COM("Scripting.FileSystemObject"); + $f = $fsobj->GetFile($fullPath); + return $f->Size; + } + } else if (strpos($name, 'bsd') !== false) { + if (\OC_Helper::is_function_enabled('exec')) { + return (float)exec('stat -f %z ' . escapeshellarg($fullPath)); + } + } else if (strpos($name, 'linux') !== false) { + if (\OC_Helper::is_function_enabled('exec')) { + return (float)exec('stat -c %s ' . escapeshellarg($fullPath)); + } + } else { + \OC_Log::write('core', + 'Unable to determine file size of "'.$fullPath.'". Unknown OS: '.$name, + \OC_Log::ERROR); + } + + return 0; + } + + public function hash($path, $type, $raw=false) { + return hash_file($type, $this->buildPath($path), $raw); + } + + public function free_space($path) { + return @disk_free_space($this->buildPath($path)); + } + + public function search($query) { + return $this->searchInDir($query); + } + public function getLocalFile($path) { + return $this->buildPath($path); + } + public function getLocalFolder($path) { + return $this->buildPath($path); + } + + protected function searchInDir($query, $dir='') { + $files=array(); + $physicalDir = $this->buildPath($dir); + foreach (scandir($physicalDir) as $item) { + if ($item == '.' || $item == '..') + continue; + $physicalItem = $this->mapper->physicalToLogic($physicalDir.'/'.$item); + $item = substr($physicalItem, strlen($physicalDir)+1); + + if(strstr(strtolower($item), strtolower($query)) !== false) { + $files[]=$dir.'/'.$item; + } + if(is_dir($physicalItem)) { + $files=array_merge($files, $this->searchInDir($query, $dir.'/'.$item)); + } + } + return $files; + } + + /** + * check if a file or folder has been updated since $time + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + return $this->filemtime($path)>$time; + } + + private function buildPath($path, $create=true) { + $path = $this->stripLeading($path); + $fullPath = $this->datadir.$path; + return $this->mapper->logicToPhysical($fullPath, $create); + } + + private function cleanMapper($path, $isLogicPath=true, $recursive=true) { + $fullPath = $path; + if ($isLogicPath) { + $fullPath = $this->datadir.$path; + } + $this->mapper->removePath($fullPath, $isLogicPath, $recursive); + } + + private function copyMapping($path1, $path2) { + $path1 = $this->stripLeading($path1); + $path2 = $this->stripLeading($path2); + + $fullPath1 = $this->datadir.$path1; + $fullPath2 = $this->datadir.$path2; + + $this->mapper->copy($fullPath1, $fullPath2); + } + + private function stripLeading($path) { + if(strpos($path, '/') === 0) { + $path = substr($path, 1); + } + if(strpos($path, '\\') === 0) { + $path = substr($path, 1); + } + if ($path === false) { + return ''; + } + + return $path; + } +} diff --git a/lib/private/files/storage/storage.php b/lib/private/files/storage/storage.php new file mode 100644 index 00000000000..b673bb9a32d --- /dev/null +++ b/lib/private/files/storage/storage.php @@ -0,0 +1,343 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +/** + * Provide a common interface to all different storage options + * + * All paths passed to the storage are relative to the storage and should NOT have a leading slash. + */ +interface Storage extends \OCP\Files\Storage { + /** + * $parameters is a free form array with the configuration options needed to construct the storage + * + * @param array $parameters + */ + public function __construct($parameters); + + /** + * Get the identifier for the storage, + * the returned id should be the same for every storage object that is created with the same parameters + * and two storage objects with the same id should refer to two storages that display the same files. + * + * @return string + */ + public function getId(); + + /** + * see http://php.net/manual/en/function.mkdir.php + * + * @param string $path + * @return bool + */ + public function mkdir($path); + + /** + * see http://php.net/manual/en/function.rmdir.php + * + * @param string $path + * @return bool + */ + public function rmdir($path); + + /** + * see http://php.net/manual/en/function.opendir.php + * + * @param string $path + * @return resource + */ + public function opendir($path); + + /** + * see http://php.net/manual/en/function.is_dir.php + * + * @param string $path + * @return bool + */ + public function is_dir($path); + + /** + * see http://php.net/manual/en/function.is_file.php + * + * @param string $path + * @return bool + */ + public function is_file($path); + + /** + * see http://php.net/manual/en/function.stat.php + * only the following keys are required in the result: size and mtime + * + * @param string $path + * @return array + */ + public function stat($path); + + /** + * see http://php.net/manual/en/function.filetype.php + * + * @param string $path + * @return bool + */ + public function filetype($path); + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int + */ + public function filesize($path); + + /** + * check if a file can be created in $path + * + * @param string $path + * @return bool + */ + public function isCreatable($path); + + /** + * check if a file can be read + * + * @param string $path + * @return bool + */ + public function isReadable($path); + + /** + * check if a file can be written to + * + * @param string $path + * @return bool + */ + public function isUpdatable($path); + + /** + * check if a file can be deleted + * + * @param string $path + * @return bool + */ + public function isDeletable($path); + + /** + * check if a file can be shared + * + * @param string $path + * @return bool + */ + public function isSharable($path); + + /** + * get the full permissions of a path. + * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php + * + * @param string $path + * @return int + */ + public function getPermissions($path); + + /** + * see http://php.net/manual/en/function.file_exists.php + * + * @param string $path + * @return bool + */ + public function file_exists($path); + + /** + * see http://php.net/manual/en/function.filemtime.php + * + * @param string $path + * @return int + */ + public function filemtime($path); + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string + */ + public function file_get_contents($path); + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data); + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + */ + public function unlink($path); + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function rename($path1, $path2); + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function copy($path1, $path2); + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode); + + /** + * get the mimetype for a file or folder + * The mimetype for a folder is required to be "httpd/unix-directory" + * + * @param string $path + * @return string + */ + public function getMimeType($path); + + /** + * see http://php.net/manual/en/function.hash.php + * + * @param string $type + * @param string $path + * @param bool $raw + * @return string + */ + public function hash($type, $path, $raw = false); + + /** + * see http://php.net/manual/en/function.free_space.php + * + * @param string $path + * @return int + */ + public function free_space($path); + + /** + * search for occurrences of $query in file names + * + * @param string $query + * @return array + */ + public function search($query); + + /** + * see http://php.net/manual/en/function.touch.php + * If the backend does not support the operation, false should be returned + * + * @param string $path + * @param int $mtime + * @return bool + */ + public function touch($path, $mtime = null); + + /** + * get the path to a local version of the file. + * The local version of the file can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFile($path); + + /** + * get the path to a local version of the folder. + * The local version of the folder can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFolder($path); + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + * + * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. + * returning true for other changes in the folder is optional + */ + public function hasUpdated($path, $time); + + /** + * get a cache instance for the storage + * + * @param string $path + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = ''); + + /** + * get a scanner instance for the storage + * + * @param string $path + * @return \OC\Files\Cache\Scanner + */ + public function getScanner($path = ''); + + + /** + * get the user id of the owner of a file or folder + * + * @param string $path + * @return string + */ + public function getOwner($path); + + /** + * get a permissions cache instance for the cache + * + * @param string $path + * @return \OC\Files\Cache\Permissions + */ + public function getPermissionsCache($path = ''); + + /** + * get a watcher instance for the cache + * + * @param string $path + * @return \OC\Files\Cache\Watcher + */ + public function getWatcher($path = ''); + + /** + * @return \OC\Files\Cache\Storage + */ + public function getStorageCache(); + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path); +} diff --git a/lib/private/files/storage/temporary.php b/lib/private/files/storage/temporary.php new file mode 100644 index 00000000000..d84dbda2e39 --- /dev/null +++ b/lib/private/files/storage/temporary.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +/** + * local storage backend in temporary folder for testing purpose + */ +class Temporary extends Local{ + public function __construct($arguments) { + parent::__construct(array('datadir' => \OC_Helper::tmpFolder())); + } + + public function cleanUp() { + \OC_Helper::rmdirr($this->datadir); + } + + public function __destruct() { + parent::__destruct(); + $this->cleanUp(); + } +} diff --git a/lib/private/files/storage/wrapper/quota.php b/lib/private/files/storage/wrapper/quota.php new file mode 100644 index 00000000000..e2da8cf2e05 --- /dev/null +++ b/lib/private/files/storage/wrapper/quota.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage\Wrapper; + +class Quota extends Wrapper { + + /** + * @var int $quota + */ + protected $quota; + + /** + * @param array $parameters + */ + public function __construct($parameters) { + $this->storage = $parameters['storage']; + $this->quota = $parameters['quota']; + } + + protected function getSize($path) { + $cache = $this->getCache(); + $data = $cache->get($path); + if (is_array($data) and isset($data['size'])) { + return $data['size']; + } else { + return \OC\Files\SPACE_NOT_COMPUTED; + } + } + + /** + * Get free space as limited by the quota + * + * @param string $path + * @return int + */ + public function free_space($path) { + if ($this->quota < 0) { + return $this->storage->free_space($path); + } else { + $used = $this->getSize(''); + if ($used < 0) { + return \OC\Files\SPACE_NOT_COMPUTED; + } else { + $free = $this->storage->free_space($path); + return min($free, (max($this->quota - $used, 0))); + } + } + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + $free = $this->free_space(''); + if ($free < 0 or strlen($data) < $free) { + return $this->storage->file_put_contents($path, $data); + } else { + return false; + } + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $source + * @param string $target + * @return bool + */ + public function copy($source, $target) { + $free = $this->free_space(''); + if ($free < 0 or $this->getSize($source) < $free) { + return $this->storage->copy($source, $target); + } else { + return false; + } + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + $source = $this->storage->fopen($path, $mode); + $free = $this->free_space(''); + if ($free >= 0) { + return \OC\Files\Stream\Quota::wrap($source, $free); + } else { + return $source; + } + } +} diff --git a/lib/private/files/storage/wrapper/wrapper.php b/lib/private/files/storage/wrapper/wrapper.php new file mode 100644 index 00000000000..0336c27efa1 --- /dev/null +++ b/lib/private/files/storage/wrapper/wrapper.php @@ -0,0 +1,427 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage\Wrapper; + +class Wrapper implements \OC\Files\Storage\Storage { + /** + * @var \OC\Files\Storage\Storage $storage + */ + protected $storage; + + /** + * @param array $parameters + */ + public function __construct($parameters) { + $this->storage = $parameters['storage']; + } + + /** + * @return \OC\Files\Storage\Storage + */ + public function getWrapperStorage() { + return $this->storage; + } + + /** + * Get the identifier for the storage, + * the returned id should be the same for every storage object that is created with the same parameters + * and two storage objects with the same id should refer to two storages that display the same files. + * + * @return string + */ + public function getId() { + return $this->storage->getId(); + } + + /** + * see http://php.net/manual/en/function.mkdir.php + * + * @param string $path + * @return bool + */ + public function mkdir($path) { + return $this->storage->mkdir($path); + } + + /** + * see http://php.net/manual/en/function.rmdir.php + * + * @param string $path + * @return bool + */ + public function rmdir($path) { + return $this->storage->rmdir($path); + } + + /** + * see http://php.net/manual/en/function.opendir.php + * + * @param string $path + * @return resource + */ + public function opendir($path) { + return $this->storage->opendir($path); + } + + /** + * see http://php.net/manual/en/function.is_dir.php + * + * @param string $path + * @return bool + */ + public function is_dir($path) { + return $this->storage->is_dir($path); + } + + /** + * see http://php.net/manual/en/function.is_file.php + * + * @param string $path + * @return bool + */ + public function is_file($path) { + return $this->storage->is_file($path); + } + + /** + * see http://php.net/manual/en/function.stat.php + * only the following keys are required in the result: size and mtime + * + * @param string $path + * @return array + */ + public function stat($path) { + return $this->storage->stat($path); + } + + /** + * see http://php.net/manual/en/function.filetype.php + * + * @param string $path + * @return bool + */ + public function filetype($path) { + return $this->storage->filetype($path); + } + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int + */ + public function filesize($path) { + return $this->storage->filesize($path); + } + + /** + * check if a file can be created in $path + * + * @param string $path + * @return bool + */ + public function isCreatable($path) { + return $this->storage->isCreatable($path); + } + + /** + * check if a file can be read + * + * @param string $path + * @return bool + */ + public function isReadable($path) { + return $this->storage->isReadable($path); + } + + /** + * check if a file can be written to + * + * @param string $path + * @return bool + */ + public function isUpdatable($path) { + return $this->storage->isUpdatable($path); + } + + /** + * check if a file can be deleted + * + * @param string $path + * @return bool + */ + public function isDeletable($path) { + return $this->storage->isDeletable($path); + } + + /** + * check if a file can be shared + * + * @param string $path + * @return bool + */ + public function isSharable($path) { + return $this->storage->isSharable($path); + } + + /** + * get the full permissions of a path. + * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php + * + * @param string $path + * @return int + */ + public function getPermissions($path) { + return $this->storage->getPermissions($path); + } + + /** + * see http://php.net/manual/en/function.file_exists.php + * + * @param string $path + * @return bool + */ + public function file_exists($path) { + return $this->storage->file_exists($path); + } + + /** + * see http://php.net/manual/en/function.filemtime.php + * + * @param string $path + * @return int + */ + public function filemtime($path) { + return $this->storage->filemtime($path); + } + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string + */ + public function file_get_contents($path) { + return $this->storage->file_get_contents($path); + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + return $this->storage->file_put_contents($path, $data); + } + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + */ + public function unlink($path) { + return $this->storage->unlink($path); + } + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function rename($path1, $path2) { + return $this->storage->rename($path1, $path2); + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function copy($path1, $path2) { + return $this->storage->copy($path1, $path2); + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + return $this->storage->fopen($path, $mode); + } + + /** + * get the mimetype for a file or folder + * The mimetype for a folder is required to be "httpd/unix-directory" + * + * @param string $path + * @return string + */ + public function getMimeType($path) { + return $this->storage->getMimeType($path); + } + + /** + * see http://php.net/manual/en/function.hash.php + * + * @param string $type + * @param string $path + * @param bool $raw + * @return string + */ + public function hash($type, $path, $raw = false) { + return $this->storage->hash($type, $path, $raw); + } + + /** + * see http://php.net/manual/en/function.free_space.php + * + * @param string $path + * @return int + */ + public function free_space($path) { + return $this->storage->free_space($path); + } + + /** + * search for occurrences of $query in file names + * + * @param string $query + * @return array + */ + public function search($query) { + return $this->storage->search($query); + } + + /** + * see http://php.net/manual/en/function.touch.php + * If the backend does not support the operation, false should be returned + * + * @param string $path + * @param int $mtime + * @return bool + */ + public function touch($path, $mtime = null) { + return $this->storage->touch($path, $mtime); + } + + /** + * get the path to a local version of the file. + * The local version of the file can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + return $this->storage->getLocalFile($path); + } + + /** + * get the path to a local version of the folder. + * The local version of the folder can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFolder($path) { + return $this->storage->getLocalFolder($path); + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + * + * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. + * returning true for other changes in the folder is optional + */ + public function hasUpdated($path, $time) { + return $this->storage->hasUpdated($path, $time); + } + + /** + * get a cache instance for the storage + * + * @param string $path + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = '') { + return $this->storage->getCache($path); + } + + /** + * get a scanner instance for the storage + * + * @param string $path + * @return \OC\Files\Cache\Scanner + */ + public function getScanner($path = '') { + return $this->storage->getScanner($path); + } + + + /** + * get the user id of the owner of a file or folder + * + * @param string $path + * @return string + */ + public function getOwner($path) { + return $this->storage->getOwner($path); + } + + /** + * get a permissions cache instance for the cache + * + * @param string $path + * @return \OC\Files\Cache\Permissions + */ + public function getPermissionsCache($path = '') { + return $this->storage->getPermissionsCache($path); + } + + /** + * get a watcher instance for the cache + * + * @param string $path + * @return \OC\Files\Cache\Watcher + */ + public function getWatcher($path = '') { + return $this->storage->getWatcher($path); + } + + /** + * @return \OC\Files\Cache\Storage + */ + public function getStorageCache() { + return $this->storage->getStorageCache(); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + return $this->storage->getETag($path); + } +} diff --git a/lib/private/files/stream/close.php b/lib/private/files/stream/close.php new file mode 100644 index 00000000000..80de3497c36 --- /dev/null +++ b/lib/private/files/stream/close.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Stream; + +/** + * stream wrapper that provides a callback on stream close + */ +class Close { + private static $callBacks = array(); + private $path = ''; + private $source; + private static $open = array(); + + public function stream_open($path, $mode, $options, &$opened_path) { + $path = substr($path, strlen('close://')); + $this->path = $path; + $this->source = fopen($path, $mode); + if (is_resource($this->source)) { + $this->meta = stream_get_meta_data($this->source); + } + self::$open[] = $path; + return is_resource($this->source); + } + + public function stream_seek($offset, $whence = SEEK_SET) { + fseek($this->source, $offset, $whence); + } + + public function stream_tell() { + return ftell($this->source); + } + + public function stream_read($count) { + return fread($this->source, $count); + } + + public function stream_write($data) { + return fwrite($this->source, $data); + } + + public function stream_set_option($option, $arg1, $arg2) { + switch ($option) { + case STREAM_OPTION_BLOCKING: + stream_set_blocking($this->source, $arg1); + break; + case STREAM_OPTION_READ_TIMEOUT: + stream_set_timeout($this->source, $arg1, $arg2); + break; + case STREAM_OPTION_WRITE_BUFFER: + stream_set_write_buffer($this->source, $arg1, $arg2); + } + } + + public function stream_stat() { + return fstat($this->source); + } + + public function stream_lock($mode) { + flock($this->source, $mode); + } + + public function stream_flush() { + return fflush($this->source); + } + + public function stream_eof() { + return feof($this->source); + } + + public function url_stat($path) { + $path = substr($path, strlen('close://')); + if (file_exists($path)) { + return stat($path); + } else { + return false; + } + } + + public function stream_close() { + fclose($this->source); + if (isset(self::$callBacks[$this->path])) { + call_user_func(self::$callBacks[$this->path], $this->path); + } + } + + public function unlink($path) { + $path = substr($path, strlen('close://')); + return unlink($path); + } + + public static function registerCallback($path, $callback) { + self::$callBacks[$path] = $callback; + } +} diff --git a/lib/private/files/stream/dir.php b/lib/private/files/stream/dir.php new file mode 100644 index 00000000000..6ca884fc994 --- /dev/null +++ b/lib/private/files/stream/dir.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Stream; + +class Dir { + private static $dirs = array(); + private $name; + private $index; + + public function dir_opendir($path, $options) { + $this->name = substr($path, strlen('fakedir://')); + $this->index = 0; + if (!isset(self::$dirs[$this->name])) { + self::$dirs[$this->name] = array(); + } + return true; + } + + public function dir_readdir() { + if ($this->index >= count(self::$dirs[$this->name])) { + return false; + } + $filename = self::$dirs[$this->name][$this->index]; + $this->index++; + return $filename; + } + + public function dir_closedir() { + $this->name = ''; + return true; + } + + public function dir_rewinddir() { + $this->index = 0; + return true; + } + + public static function register($path, $content) { + self::$dirs[$path] = $content; + } +} diff --git a/lib/private/files/stream/oc.php b/lib/private/files/stream/oc.php new file mode 100644 index 00000000000..88e7e062df9 --- /dev/null +++ b/lib/private/files/stream/oc.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Stream; + +/** + * a stream wrappers for ownCloud's virtual filesystem + */ +class OC { + /** + * @var \OC\Files\View + */ + static private $rootView; + + private $path; + private $dirSource; + private $fileSource; + private $meta; + + private function setup(){ + if (!self::$rootView) { + self::$rootView = new \OC\Files\View(''); + } + } + + public function stream_open($path, $mode, $options, &$opened_path) { + $this->setup(); + $path = substr($path, strlen('oc://')); + $this->path = $path; + $this->fileSource = self::$rootView->fopen($path, $mode); + if (is_resource($this->fileSource)) { + $this->meta = stream_get_meta_data($this->fileSource); + } + return is_resource($this->fileSource); + } + + public function stream_seek($offset, $whence = SEEK_SET) { + fseek($this->fileSource, $offset, $whence); + } + + public function stream_tell() { + return ftell($this->fileSource); + } + + public function stream_read($count) { + return fread($this->fileSource, $count); + } + + public function stream_write($data) { + return fwrite($this->fileSource, $data); + } + + public function stream_set_option($option, $arg1, $arg2) { + switch ($option) { + case STREAM_OPTION_BLOCKING: + stream_set_blocking($this->fileSource, $arg1); + break; + case STREAM_OPTION_READ_TIMEOUT: + stream_set_timeout($this->fileSource, $arg1, $arg2); + break; + case STREAM_OPTION_WRITE_BUFFER: + stream_set_write_buffer($this->fileSource, $arg1, $arg2); + } + } + + public function stream_stat() { + return fstat($this->fileSource); + } + + public function stream_lock($mode) { + flock($this->fileSource, $mode); + } + + public function stream_flush() { + return fflush($this->fileSource); + } + + public function stream_eof() { + return feof($this->fileSource); + } + + public function url_stat($path) { + $this->setup(); + $path = substr($path, strlen('oc://')); + if (self::$rootView->file_exists($path)) { + return self::$rootView->stat($path); + } else { + return false; + } + } + + public function stream_close() { + fclose($this->fileSource); + } + + public function unlink($path) { + $this->setup(); + $path = substr($path, strlen('oc://')); + return self::$rootView->unlink($path); + } + + public function dir_opendir($path, $options) { + $this->setup(); + $path = substr($path, strlen('oc://')); + $this->path = $path; + $this->dirSource = self::$rootView->opendir($path); + if (is_resource($this->dirSource)) { + $this->meta = stream_get_meta_data($this->dirSource); + } + return is_resource($this->dirSource); + } + + public function dir_readdir() { + return readdir($this->dirSource); + } + + public function dir_closedir() { + closedir($this->dirSource); + } + + public function dir_rewinddir() { + rewinddir($this->dirSource); + } +} diff --git a/lib/private/files/stream/quota.php b/lib/private/files/stream/quota.php new file mode 100644 index 00000000000..53d8a03d30f --- /dev/null +++ b/lib/private/files/stream/quota.php @@ -0,0 +1,128 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Stream; + +/** + * stream wrapper limits the amount of data that can be written to a stream + * + * usage: void \OC\Files\Stream\Quota::register($id, $stream, $limit) + * or: resource \OC\Files\Stream\Quota::wrap($stream, $limit) + */ +class Quota { + private static $streams = array(); + + /** + * @var resource $source + */ + private $source; + + /** + * @var int $limit + */ + private $limit; + + /** + * @param string $id + * @param resource $stream + * @param int $limit + */ + public static function register($id, $stream, $limit) { + self::$streams[$id] = array($stream, $limit); + } + + /** + * remove all registered streams + */ + public static function clear() { + self::$streams = array(); + } + + /** + * @param resource $stream + * @param int $limit + * @return resource + */ + static public function wrap($stream, $limit) { + $id = uniqid(); + self::register($id, $stream, $limit); + $meta = stream_get_meta_data($stream); + return fopen('quota://' . $id, $meta['mode']); + } + + public function stream_open($path, $mode, $options, &$opened_path) { + $id = substr($path, strlen('quota://')); + if (isset(self::$streams[$id])) { + list($this->source, $this->limit) = self::$streams[$id]; + return true; + } else { + return false; + } + } + + public function stream_seek($offset, $whence = SEEK_SET) { + if ($whence === SEEK_SET) { + $this->limit += $this->stream_tell() - $offset; + } else { + $this->limit -= $offset; + } + fseek($this->source, $offset, $whence); + } + + public function stream_tell() { + return ftell($this->source); + } + + public function stream_read($count) { + $this->limit -= $count; + return fread($this->source, $count); + } + + public function stream_write($data) { + $size = strlen($data); + if ($size > $this->limit) { + $data = substr($data, 0, $this->limit); + $size = $this->limit; + } + $this->limit -= $size; + return fwrite($this->source, $data); + } + + public function stream_set_option($option, $arg1, $arg2) { + switch ($option) { + case STREAM_OPTION_BLOCKING: + stream_set_blocking($this->source, $arg1); + break; + case STREAM_OPTION_READ_TIMEOUT: + stream_set_timeout($this->source, $arg1, $arg2); + break; + case STREAM_OPTION_WRITE_BUFFER: + stream_set_write_buffer($this->source, $arg1, $arg2); + } + } + + public function stream_stat() { + return fstat($this->source); + } + + public function stream_lock($mode) { + flock($this->source, $mode); + } + + public function stream_flush() { + return fflush($this->source); + } + + public function stream_eof() { + return feof($this->source); + } + + public function stream_close() { + fclose($this->source); + } +} diff --git a/lib/private/files/stream/staticstream.php b/lib/private/files/stream/staticstream.php new file mode 100644 index 00000000000..45b1a7a81f8 --- /dev/null +++ b/lib/private/files/stream/staticstream.php @@ -0,0 +1,156 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Stream; + +class StaticStream { + const MODE_FILE = 0100000; + + public $context; + protected static $data = array(); + + protected $path = ''; + protected $pointer = 0; + protected $writable = false; + + public function stream_close() { + } + + public function stream_eof() { + return $this->pointer >= strlen(self::$data[$this->path]); + } + + public function stream_flush() { + } + + public static function clear() { + self::$data = array(); + } + + public function stream_open($path, $mode, $options, &$opened_path) { + switch ($mode[0]) { + case 'r': + if (!isset(self::$data[$path])) return false; + $this->path = $path; + $this->writable = isset($mode[1]) && $mode[1] == '+'; + break; + case 'w': + self::$data[$path] = ''; + $this->path = $path; + $this->writable = true; + break; + case 'a': + if (!isset(self::$data[$path])) self::$data[$path] = ''; + $this->path = $path; + $this->writable = true; + $this->pointer = strlen(self::$data[$path]); + break; + case 'x': + if (isset(self::$data[$path])) return false; + $this->path = $path; + $this->writable = true; + break; + case 'c': + if (!isset(self::$data[$path])) self::$data[$path] = ''; + $this->path = $path; + $this->writable = true; + break; + default: + return false; + } + $opened_path = $this->path; + return true; + } + + public function stream_read($count) { + $bytes = min(strlen(self::$data[$this->path]) - $this->pointer, $count); + $data = substr(self::$data[$this->path], $this->pointer, $bytes); + $this->pointer += $bytes; + return $data; + } + + public function stream_seek($offset, $whence = SEEK_SET) { + $len = strlen(self::$data[$this->path]); + switch ($whence) { + case SEEK_SET: + if ($offset <= $len) { + $this->pointer = $offset; + return true; + } + break; + case SEEK_CUR: + if ($this->pointer + $offset <= $len) { + $this->pointer += $offset; + return true; + } + break; + case SEEK_END: + if ($len + $offset <= $len) { + $this->pointer = $len + $offset; + return true; + } + break; + } + return false; + } + + public function stream_stat() { + return $this->url_stat($this->path); + } + + public function stream_tell() { + return $this->pointer; + } + + public function stream_write($data) { + if (!$this->writable) return 0; + $size = strlen($data); + if ($this->stream_eof()) { + self::$data[$this->path] .= $data; + } else { + self::$data[$this->path] = substr_replace( + self::$data[$this->path], + $data, + $this->pointer + ); + } + $this->pointer += $size; + return $size; + } + + public function unlink($path) { + if (isset(self::$data[$path])) { + unset(self::$data[$path]); + } + return true; + } + + public function url_stat($path) { + if (isset(self::$data[$path])) { + $size = strlen(self::$data[$path]); + $time = time(); + $data = array( + 'dev' => 0, + 'ino' => 0, + 'mode' => self::MODE_FILE | 0777, + 'nlink' => 1, + 'uid' => 0, + 'gid' => 0, + 'rdev' => '', + 'size' => $size, + 'atime' => $time, + 'mtime' => $time, + 'ctime' => $time, + 'blksize' => -1, + 'blocks' => -1, + ); + return array_values($data) + $data; + } + return false; + } +} diff --git a/lib/private/files/type/detection.php b/lib/private/files/type/detection.php new file mode 100644 index 00000000000..242a81cb5a4 --- /dev/null +++ b/lib/private/files/type/detection.php @@ -0,0 +1,121 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Type; + +/** + * Class Detection + * + * Mimetype detection + * + * @package OC\Files\Type + */ +class Detection { + protected $mimetypes = array(); + + /** + * add an extension -> mimetype mapping + * + * @param string $extension + * @param string $mimetype + */ + public function registerType($extension, $mimetype) { + $this->mimetypes[$extension] = $mimetype; + } + + /** + * add an array of extension -> mimetype mappings + * + * @param array $types + */ + public function registerTypeArray($types) { + $this->mimetypes = array_merge($this->mimetypes, $types); + } + + /** + * detect mimetype only based on filename, content of file is not used + * + * @param string $path + * @return string + */ + public function detectPath($path) { + if (strpos($path, '.')) { + //try to guess the type by the file extension + $extension = strtolower(strrchr(basename($path), ".")); + $extension = substr($extension, 1); //remove leading . + return (isset($this->mimetypes[$extension])) ? $this->mimetypes[$extension] : 'application/octet-stream'; + } else { + return 'application/octet-stream'; + } + } + + /** + * detect mimetype based on both filename and content + * + * @param string $path + * @return string + */ + public function detect($path) { + $isWrapped = (strpos($path, '://') !== false) and (substr($path, 0, 7) === 'file://'); + + if (@is_dir($path)) { + // directories are easy + return "httpd/unix-directory"; + } + + $mimeType = $this->detectPath($path); + + if ($mimeType === 'application/octet-stream' and function_exists('finfo_open') + and function_exists('finfo_file') and $finfo = finfo_open(FILEINFO_MIME) + ) { + $info = @strtolower(finfo_file($finfo, $path)); + if ($info) { + $mimeType = substr($info, 0, strpos($info, ';')); + } + finfo_close($finfo); + } + if (!$isWrapped and $mimeType === 'application/octet-stream' && function_exists("mime_content_type")) { + // use mime magic extension if available + $mimeType = mime_content_type($path); + } + if (!$isWrapped and $mimeType === 'application/octet-stream' && \OC_Helper::canExecute("file")) { + // it looks like we have a 'file' command, + // lets see if it does have mime support + $path = escapeshellarg($path); + $fp = popen("file -b --mime-type $path 2>/dev/null", "r"); + $reply = fgets($fp); + pclose($fp); + + //trim the newline + $mimeType = trim($reply); + + } + return $mimeType; + } + + /** + * detect mimetype based on the content of a string + * + * @param string $data + * @return string + */ + public function detectString($data) { + if (function_exists('finfo_open') and function_exists('finfo_file')) { + $finfo = finfo_open(FILEINFO_MIME); + return finfo_buffer($finfo, $data); + } else { + $tmpFile = \OC_Helper::tmpFile(); + $fh = fopen($tmpFile, 'wb'); + fwrite($fh, $data, 8024); + fclose($fh); + $mime = $this->detect($tmpFile); + unset($tmpFile); + return $mime; + } + } +} diff --git a/lib/private/files/type/templatemanager.php b/lib/private/files/type/templatemanager.php new file mode 100644 index 00000000000..cd1536d2732 --- /dev/null +++ b/lib/private/files/type/templatemanager.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Type; + +class TemplateManager { + protected $templates = array(); + + public function registerTemplate($mimetype, $path) { + $this->templates[$mimetype] = $path; + } + + /** + * get the path of the template for a mimetype + * + * @param string $mimetype + * @return string | null + */ + public function getTemplatePath($mimetype) { + if (isset($this->templates[$mimetype])) { + return $this->templates[$mimetype]; + } else { + return null; + } + } + + /** + * get the template content for a mimetype + * + * @param string $mimetype + * @return string + */ + public function getTemplate($mimetype) { + $path = $this->getTemplatePath($mimetype); + if ($path) { + return file_get_contents($path); + } else { + return ''; + } + } +} diff --git a/lib/private/files/utils/scanner.php b/lib/private/files/utils/scanner.php new file mode 100644 index 00000000000..2cad7dd77bd --- /dev/null +++ b/lib/private/files/utils/scanner.php @@ -0,0 +1,96 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Utils; + +use OC\Files\Filesystem; +use OC\Hooks\PublicEmitter; + +/** + * Class Scanner + * + * Hooks available in scope \OC\Utils\Scanner + * - scanFile(string $absolutePath) + * - scanFolder(string $absolutePath) + * + * @package OC\Files\Utils + */ +class Scanner extends PublicEmitter { + /** + * @var string $user + */ + private $user; + + /** + * @param string $user + */ + public function __construct($user) { + $this->user = $user; + } + + /** + * get all storages for $dir + * + * @param string $dir + * @return \OC\Files\Mount\Mount[] + */ + protected function getMounts($dir) { + //TODO: move to the node based fileapi once that's done + \OC_Util::tearDownFS(); + \OC_Util::setupFS($this->user); + $absolutePath = Filesystem::getView()->getAbsolutePath($dir); + + $mountManager = Filesystem::getMountManager(); + $mounts = $mountManager->findIn($absolutePath); + $mounts[] = $mountManager->find($absolutePath); + $mounts = array_reverse($mounts); //start with the mount of $dir + + return $mounts; + } + + /** + * attach listeners to the scanner + * + * @param \OC\Files\Mount\Mount $mount + */ + protected function attachListener($mount) { + $scanner = $mount->getStorage()->getScanner(); + $emitter = $this; + $scanner->listen('\OC\Files\Cache\Scanner', 'scanFile', function ($path) use ($mount, $emitter) { + $emitter->emit('\OC\Files\Utils\Scanner', 'scanFile', array($mount->getMountPoint() . $path)); + }); + $scanner->listen('\OC\Files\Cache\Scanner', 'scanFolder', function ($path) use ($mount, $emitter) { + $emitter->emit('\OC\Files\Utils\Scanner', 'scanFolder', array($mount->getMountPoint() . $path)); + }); + } + + public function backgroundScan($dir) { + $mounts = $this->getMounts($dir); + foreach ($mounts as $mount) { + if (is_null($mount->getStorage())) { + continue; + } + $scanner = $mount->getStorage()->getScanner(); + $this->attachListener($mount); + $scanner->backgroundScan(); + } + } + + public function scan($dir) { + $mounts = $this->getMounts($dir); + foreach ($mounts as $mount) { + if (is_null($mount->getStorage())) { + continue; + } + $scanner = $mount->getStorage()->getScanner(); + $this->attachListener($mount); + $scanner->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE, \OC\Files\Cache\Scanner::REUSE_ETAG); + } + } +} + diff --git a/lib/private/files/view.php b/lib/private/files/view.php new file mode 100644 index 00000000000..aa08a5f7cc9 --- /dev/null +++ b/lib/private/files/view.php @@ -0,0 +1,1078 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * Class to provide access to ownCloud filesystem via a "view", and methods for + * working with files within that view (e.g. read, write, delete, etc.). Each + * view is restricted to a set of directories via a virtual root. The default view + * uses the currently logged in user's data directory as root (parts of + * OC_Filesystem are merely a wrapper for OC_FilesystemView). + * + * Apps that need to access files outside of the user data folders (to modify files + * belonging to a user other than the one currently logged in, for example) should + * use this class directly rather than using OC_Filesystem, or making use of PHP's + * built-in file manipulation functions. This will ensure all hooks and proxies + * are triggered correctly. + * + * Filesystem functions are not called directly; they are passed to the correct + * \OC\Files\Storage\Storage object + */ + +namespace OC\Files; + +class View { + private $fakeRoot = ''; + private $internal_path_cache = array(); + private $storage_cache = array(); + + public function __construct($root = '') { + $this->fakeRoot = $root; + } + + public function getAbsolutePath($path = '/') { + if (!$path) { + $path = '/'; + } + if ($path[0] !== '/') { + $path = '/' . $path; + } + return $this->fakeRoot . $path; + } + + /** + * change the root to a fake root + * + * @param string $fakeRoot + * @return bool + */ + public function chroot($fakeRoot) { + if (!$fakeRoot == '') { + if ($fakeRoot[0] !== '/') { + $fakeRoot = '/' . $fakeRoot; + } + } + $this->fakeRoot = $fakeRoot; + } + + /** + * get the fake root + * + * @return string + */ + public function getRoot() { + return $this->fakeRoot; + } + + /** + * get path relative to the root of the view + * + * @param string $path + * @return string + */ + public function getRelativePath($path) { + if ($this->fakeRoot == '') { + return $path; + } + if (strpos($path, $this->fakeRoot) !== 0) { + return null; + } else { + $path = substr($path, strlen($this->fakeRoot)); + if (strlen($path) === 0) { + return '/'; + } else { + return $path; + } + } + } + + /** + * get the mountpoint of the storage object for a path + * ( note: because a storage is not always mounted inside the fakeroot, the + * returned mountpoint is relative to the absolute root of the filesystem + * and doesn't take the chroot into account ) + * + * @param string $path + * @return string + */ + public function getMountPoint($path) { + return Filesystem::getMountPoint($this->getAbsolutePath($path)); + } + + /** + * resolve a path to a storage and internal path + * + * @param string $path + * @return array consisting of the storage and the internal path + */ + public function resolvePath($path) { + return Filesystem::resolvePath($this->getAbsolutePath($path)); + } + + /** + * return the path to a local version of the file + * we need this because we can't know if a file is stored local or not from + * outside the filestorage and for some purposes a local file is needed + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + $parent = substr($path, 0, strrpos($path, '/')); + $path = $this->getAbsolutePath($path); + list($storage, $internalPath) = Filesystem::resolvePath($path); + if (Filesystem::isValidPath($parent) and $storage) { + return $storage->getLocalFile($internalPath); + } else { + return null; + } + } + + /** + * @param string $path + * @return string + */ + public function getLocalFolder($path) { + $parent = substr($path, 0, strrpos($path, '/')); + $path = $this->getAbsolutePath($path); + list($storage, $internalPath) = Filesystem::resolvePath($path); + if (Filesystem::isValidPath($parent) and $storage) { + return $storage->getLocalFolder($internalPath); + } else { + return null; + } + } + + /** + * the following functions operate with arguments and return values identical + * to those of their PHP built-in equivalents. Mostly they are merely wrappers + * for \OC\Files\Storage\Storage via basicOperation(). + */ + public function mkdir($path) { + return $this->basicOperation('mkdir', $path, array('create', 'write')); + } + + public function rmdir($path) { + return $this->basicOperation('rmdir', $path, array('delete')); + } + + public function opendir($path) { + return $this->basicOperation('opendir', $path, array('read')); + } + + public function readdir($handle) { + $fsLocal = new Storage\Local(array('datadir' => '/')); + return $fsLocal->readdir($handle); + } + + public function is_dir($path) { + if ($path == '/') { + return true; + } + return $this->basicOperation('is_dir', $path); + } + + public function is_file($path) { + if ($path == '/') { + return false; + } + return $this->basicOperation('is_file', $path); + } + + public function stat($path) { + return $this->basicOperation('stat', $path); + } + + public function filetype($path) { + return $this->basicOperation('filetype', $path); + } + + public function filesize($path) { + return $this->basicOperation('filesize', $path); + } + + public function readfile($path) { + @ob_end_clean(); + $handle = $this->fopen($path, 'rb'); + if ($handle) { + $chunkSize = 8192; // 8 kB chunks + while (!feof($handle)) { + echo fread($handle, $chunkSize); + flush(); + } + $size = $this->filesize($path); + return $size; + } + return false; + } + + public function isCreatable($path) { + return $this->basicOperation('isCreatable', $path); + } + + public function isReadable($path) { + return $this->basicOperation('isReadable', $path); + } + + public function isUpdatable($path) { + return $this->basicOperation('isUpdatable', $path); + } + + public function isDeletable($path) { + return $this->basicOperation('isDeletable', $path); + } + + public function isSharable($path) { + return $this->basicOperation('isSharable', $path); + } + + public function file_exists($path) { + if ($path == '/') { + return true; + } + return $this->basicOperation('file_exists', $path); + } + + public function filemtime($path) { + return $this->basicOperation('filemtime', $path); + } + + public function touch($path, $mtime = null) { + if (!is_null($mtime) and !is_numeric($mtime)) { + $mtime = strtotime($mtime); + } + + $hooks = array('touch'); + + if (!$this->file_exists($path)) { + $hooks[] = 'create'; + $hooks[] = 'write'; + } + $result = $this->basicOperation('touch', $path, $hooks, $mtime); + if (!$result) { //if native touch fails, we emulate it by changing the mtime in the cache + $this->putFileInfo($path, array('mtime' => $mtime)); + } + return true; + } + + public function file_get_contents($path) { + return $this->basicOperation('file_get_contents', $path, array('read')); + } + + public function file_put_contents($path, $data) { + if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (\OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data) + and Filesystem::isValidPath($path) + and !Filesystem::isFileBlacklisted($path) + ) { + $path = $this->getRelativePath($absolutePath); + $exists = $this->file_exists($path); + $run = true; + if ($this->shouldEmitHooks($path)) { + if (!$exists) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_create, + array( + Filesystem::signal_param_path => $this->getHookPath($path), + Filesystem::signal_param_run => &$run + ) + ); + } + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_write, + array( + Filesystem::signal_param_path => $this->getHookPath($path), + Filesystem::signal_param_run => &$run + ) + ); + } + if (!$run) { + return false; + } + $target = $this->fopen($path, 'w'); + if ($target) { + list ($count, $result) = \OC_Helper::streamCopy($data, $target); + fclose($target); + fclose($data); + if ($this->shouldEmitHooks($path) && $result !== false) { + if (!$exists) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_create, + array(Filesystem::signal_param_path => $this->getHookPath($path)) + ); + } + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_write, + array(Filesystem::signal_param_path => $this->getHookPath($path)) + ); + } + \OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count); + return $result; + } else { + return false; + } + } else { + return false; + } + } else { + return $this->basicOperation('file_put_contents', $path, array('create', 'write'), $data); + } + } + + public function unlink($path) { + return $this->basicOperation('unlink', $path, array('delete')); + } + + public function deleteAll($directory, $empty = false) { + return $this->rmdir($directory); + } + + public function rename($path1, $path2) { + $postFix1 = (substr($path1, -1, 1) === '/') ? '/' : ''; + $postFix2 = (substr($path2, -1, 1) === '/') ? '/' : ''; + $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); + $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); + if ( + \OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2) + and Filesystem::isValidPath($path2) + and Filesystem::isValidPath($path1) + and !Filesystem::isFileBlacklisted($path2) + ) { + $path1 = $this->getRelativePath($absolutePath1); + $path2 = $this->getRelativePath($absolutePath2); + + if ($path1 == null or $path2 == null) { + return false; + } + $run = true; + if ($this->shouldEmitHooks() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) { + // if it was a rename from a part file to a regular file it was a write and not a rename operation + \OC_Hook::emit( + Filesystem::CLASSNAME, Filesystem::signal_write, + array( + Filesystem::signal_param_path => $this->getHookPath($path2), + Filesystem::signal_param_run => &$run + ) + ); + } elseif ($this->shouldEmitHooks()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, Filesystem::signal_rename, + array( + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2), + Filesystem::signal_param_run => &$run + ) + ); + } + if ($run) { + $mp1 = $this->getMountPoint($path1 . $postFix1); + $mp2 = $this->getMountPoint($path2 . $postFix2); + if ($mp1 == $mp2) { + list($storage, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1); + list(, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2); + if ($storage) { + $result = $storage->rename($internalPath1, $internalPath2); + \OC_FileProxy::runPostProxies('rename', $absolutePath1, $absolutePath2); + } else { + $result = false; + } + } else { + if ($this->is_dir($path1)) { + $result = $this->copy($path1, $path2); + if ($result === true) { + list($storage1, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1); + $result = $storage1->deleteAll($internalPath1); + } + } else { + $source = $this->fopen($path1 . $postFix1, 'r'); + $target = $this->fopen($path2 . $postFix2, 'w'); + list($count, $result) = \OC_Helper::streamCopy($source, $target); + + // close open handle - especially $source is necessary because unlink below will + // throw an exception on windows because the file is locked + fclose($source); + fclose($target); + + if ($result !== false) { + list($storage1, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1); + $storage1->unlink($internalPath1); + } + } + } + if ($this->shouldEmitHooks() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) { + // if it was a rename from a part file to a regular file it was a write and not a rename operation + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_write, + array( + Filesystem::signal_param_path => $this->getHookPath($path2), + ) + ); + } elseif ($this->shouldEmitHooks() && $result !== false) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_rename, + array( + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2) + ) + ); + } + return $result; + } else { + return false; + } + } else { + return false; + } + } + + public function copy($path1, $path2) { + $postFix1 = (substr($path1, -1, 1) === '/') ? '/' : ''; + $postFix2 = (substr($path2, -1, 1) === '/') ? '/' : ''; + $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); + $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); + if ( + \OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2) + and Filesystem::isValidPath($path2) + and Filesystem::isValidPath($path1) + and !Filesystem::isFileBlacklisted($path2) + ) { + $path1 = $this->getRelativePath($absolutePath1); + $path2 = $this->getRelativePath($absolutePath2); + + if ($path1 == null or $path2 == null) { + return false; + } + $run = true; + $exists = $this->file_exists($path2); + if ($this->shouldEmitHooks()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_copy, + array( + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2), + Filesystem::signal_param_run => &$run + ) + ); + if ($run and !$exists) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_create, + array( + Filesystem::signal_param_path => $this->getHookPath($path2), + Filesystem::signal_param_run => &$run + ) + ); + } + if ($run) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_write, + array( + Filesystem::signal_param_path => $this->getHookPath($path2), + Filesystem::signal_param_run => &$run + ) + ); + } + } + if ($run) { + $mp1 = $this->getMountPoint($path1 . $postFix1); + $mp2 = $this->getMountPoint($path2 . $postFix2); + if ($mp1 == $mp2) { + list($storage, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1); + list(, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2); + if ($storage) { + $result = $storage->copy($internalPath1, $internalPath2); + } else { + $result = false; + } + } else { + if ($this->is_dir($path1) && ($dh = $this->opendir($path1))) { + $result = $this->mkdir($path2); + if (is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if (!Filesystem::isIgnoredDir($file)) { + $result = $this->copy($path1 . '/' . $file, $path2 . '/' . $file); + } + } + } + } else { + $source = $this->fopen($path1 . $postFix1, 'r'); + $target = $this->fopen($path2 . $postFix2, 'w'); + list($count, $result) = \OC_Helper::streamCopy($source, $target); + } + } + if ($this->shouldEmitHooks() && $result !== false) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_copy, + array( + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2) + ) + ); + if (!$exists) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_create, + array(Filesystem::signal_param_path => $this->getHookPath($path2)) + ); + } + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_write, + array(Filesystem::signal_param_path => $this->getHookPath($path2)) + ); + } + return $result; + } else { + return false; + } + } else { + return false; + } + } + + public function fopen($path, $mode) { + $hooks = array(); + switch ($mode) { + case 'r': + case 'rb': + $hooks[] = 'read'; + break; + case 'r+': + case 'rb+': + case 'w+': + case 'wb+': + case 'x+': + case 'xb+': + case 'a+': + case 'ab+': + $hooks[] = 'read'; + $hooks[] = 'write'; + break; + case 'w': + case 'wb': + case 'x': + case 'xb': + case 'a': + case 'ab': + $hooks[] = 'write'; + break; + default: + \OC_Log::write('core', 'invalid mode (' . $mode . ') for ' . $path, \OC_Log::ERROR); + } + + return $this->basicOperation('fopen', $path, $hooks, $mode); + } + + public function toTmpFile($path) { + if (Filesystem::isValidPath($path)) { + $source = $this->fopen($path, 'r'); + if ($source) { + $extension = pathinfo($path, PATHINFO_EXTENSION); + $tmpFile = \OC_Helper::tmpFile($extension); + file_put_contents($tmpFile, $source); + return $tmpFile; + } else { + return false; + } + } else { + return false; + } + } + + public function fromTmpFile($tmpFile, $path) { + if (Filesystem::isValidPath($path)) { + if (!$tmpFile) { + debug_print_backtrace(); + } + $source = fopen($tmpFile, 'r'); + if ($source) { + $this->file_put_contents($path, $source); + unlink($tmpFile); + return true; + } else { + return false; + } + } else { + return false; + } + } + + public function getMimeType($path) { + return $this->basicOperation('getMimeType', $path); + } + + public function hash($type, $path, $raw = false) { + $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (\OC_FileProxy::runPreProxies('hash', $absolutePath) && Filesystem::isValidPath($path)) { + $path = $this->getRelativePath($absolutePath); + if ($path == null) { + return false; + } + if ($this->shouldEmitHooks($path)) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_read, + array(Filesystem::signal_param_path => $this->getHookPath($path)) + ); + } + list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix); + if ($storage) { + $result = $storage->hash($type, $internalPath, $raw); + $result = \OC_FileProxy::runPostProxies('hash', $absolutePath, $result); + return $result; + } + } + return null; + } + + public function free_space($path = '/') { + return $this->basicOperation('free_space', $path); + } + + /** + * @brief abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage + * @param string $operation + * @param string $path + * @param array $hooks (optional) + * @param mixed $extraParam (optional) + * @return mixed + * + * This method takes requests for basic filesystem functions (e.g. reading & writing + * files), processes hooks and proxies, sanitises paths, and finally passes them on to + * \OC\Files\Storage\Storage for delegation to a storage backend for execution + */ + private function basicOperation($operation, $path, $hooks = array(), $extraParam = null) { + $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (\OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam) + and Filesystem::isValidPath($path) + and !Filesystem::isFileBlacklisted($path) + ) { + $path = $this->getRelativePath($absolutePath); + if ($path == null) { + return false; + } + + $run = $this->runHooks($hooks, $path); + list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix); + if ($run and $storage) { + if (!is_null($extraParam)) { + $result = $storage->$operation($internalPath, $extraParam); + } else { + $result = $storage->$operation($internalPath); + } + $result = \OC_FileProxy::runPostProxies($operation, $this->getAbsolutePath($path), $result); + if ($this->shouldEmitHooks($path) && $result !== false) { + if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open + $this->runHooks($hooks, $path, true); + } + } + return $result; + } + } + return null; + } + + /** + * get the path relative to the default root for hook usage + * + * @param string $path + * @return string + */ + private function getHookPath($path) { + if (!Filesystem::getView()) { + return $path; + } + return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path)); + } + + private function shouldEmitHooks($path = '') { + if ($path && Cache\Scanner::isPartialFile($path)) { + return false; + } + if (!Filesystem::$loaded) { + return false; + } + $defaultRoot = Filesystem::getRoot(); + return (strlen($this->fakeRoot) >= strlen($defaultRoot)) && (substr($this->fakeRoot, 0, strlen($defaultRoot)) === $defaultRoot); + } + + private function runHooks($hooks, $path, $post = false) { + $path = $this->getHookPath($path); + $prefix = ($post) ? 'post_' : ''; + $run = true; + if ($this->shouldEmitHooks($path)) { + foreach ($hooks as $hook) { + if ($hook != 'read') { + \OC_Hook::emit( + Filesystem::CLASSNAME, + $prefix . $hook, + array( + Filesystem::signal_param_run => &$run, + Filesystem::signal_param_path => $path + ) + ); + } elseif (!$post) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + $prefix . $hook, + array( + Filesystem::signal_param_path => $path + ) + ); + } + } + } + return $run; + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + return $this->basicOperation('hasUpdated', $path, array(), $time); + } + + /** + * get the filesystem info + * + * @param string $path + * @return array + * + * returns an associative array with the following keys: + * - size + * - mtime + * - mimetype + * - encrypted + * - versioned + */ + public function getFileInfo($path) { + $data = array(); + if (!Filesystem::isValidPath($path)) { + return $data; + } + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = Filesystem::resolvePath($path); + if ($storage) { + $cache = $storage->getCache($internalPath); + $permissionsCache = $storage->getPermissionsCache($internalPath); + $user = \OC_User::getUser(); + + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } else { + $watcher = $storage->getWatcher($internalPath); + $watcher->checkUpdate($internalPath); + } + + $data = $cache->get($internalPath); + + if ($data and $data['fileid']) { + if ($data['mimetype'] === 'httpd/unix-directory') { + //add the sizes of other mountpoints to the folder + $mountPoints = Filesystem::getMountPoints($path); + foreach ($mountPoints as $mountPoint) { + $subStorage = Filesystem::getStorage($mountPoint); + if ($subStorage) { + $subCache = $subStorage->getCache(''); + $rootEntry = $subCache->get(''); + $data['size'] += isset($rootEntry['size']) ? $rootEntry['size'] : 0; + } + } + } + + $permissions = $permissionsCache->get($data['fileid'], $user); + if ($permissions === -1) { + $permissions = $storage->getPermissions($internalPath); + $permissionsCache->set($data['fileid'], $user, $permissions); + } + $data['permissions'] = $permissions; + } + } + + $data = \OC_FileProxy::runPostProxies('getFileInfo', $path, $data); + + return $data; + } + + /** + * get the content of a directory + * + * @param string $directory path under datadirectory + * @param string $mimetype_filter limit returned content to this mimetype or mimepart + * @return array + */ + public function getDirectoryContent($directory, $mimetype_filter = '') { + $result = array(); + if (!Filesystem::isValidPath($directory)) { + return $result; + } + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $directory); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = Filesystem::resolvePath($path); + if ($storage) { + $cache = $storage->getCache($internalPath); + $permissionsCache = $storage->getPermissionsCache($internalPath); + $user = \OC_User::getUser(); + + if ($cache->getStatus($internalPath) < Cache\Cache::COMPLETE) { + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } else { + $watcher = $storage->getWatcher($internalPath); + $watcher->checkUpdate($internalPath); + } + + $files = $cache->getFolderContents($internalPath); //TODO: mimetype_filter + $permissions = $permissionsCache->getDirectoryPermissions($cache->getId($internalPath), $user); + + $ids = array(); + foreach ($files as $i => $file) { + $files[$i]['type'] = $file['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $ids[] = $file['fileid']; + + if (!isset($permissions[$file['fileid']])) { + $permissions[$file['fileid']] = $storage->getPermissions($file['path']); + $permissionsCache->set($file['fileid'], $user, $permissions[$file['fileid']]); + } + $files[$i]['permissions'] = $permissions[$file['fileid']]; + } + + //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders + $mountPoints = Filesystem::getMountPoints($path); + $dirLength = strlen($path); + foreach ($mountPoints as $mountPoint) { + $subStorage = Filesystem::getStorage($mountPoint); + if ($subStorage) { + $subCache = $subStorage->getCache(''); + + if ($subCache->getStatus('') === Cache\Cache::NOT_FOUND) { + $subScanner = $subStorage->getScanner(''); + $subScanner->scanFile(''); + } + + $rootEntry = $subCache->get(''); + if ($rootEntry) { + $relativePath = trim(substr($mountPoint, $dirLength), '/'); + if ($pos = strpos($relativePath, '/')) { + //mountpoint inside subfolder add size to the correct folder + $entryName = substr($relativePath, 0, $pos); + foreach ($files as &$entry) { + if ($entry['name'] === $entryName) { + $entry['size'] += $rootEntry['size']; + } + } + } else { //mountpoint in this folder, add an entry for it + $rootEntry['name'] = $relativePath; + $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $subPermissionsCache = $subStorage->getPermissionsCache(''); + $permissions = $subPermissionsCache->get($rootEntry['fileid'], $user); + if ($permissions === -1) { + $permissions = $subStorage->getPermissions($rootEntry['path']); + $subPermissionsCache->set($rootEntry['fileid'], $user, $permissions); + } + $rootEntry['permissions'] = $permissions; + + //remove any existing entry with the same name + foreach ($files as $i => $file) { + if ($file['name'] === $rootEntry['name']) { + unset($files[$i]); + break; + } + } + $files[] = $rootEntry; + } + } + } + } + + if ($mimetype_filter) { + foreach ($files as $file) { + if (strpos($mimetype_filter, '/')) { + if ($file['mimetype'] === $mimetype_filter) { + $result[] = $file; + } + } else { + if ($file['mimepart'] === $mimetype_filter) { + $result[] = $file; + } + } + } + } else { + $result = $files; + } + } + return $result; + } + + /** + * change file metadata + * + * @param string $path + * @param array $data + * @return int + * + * returns the fileid of the updated file + */ + public function putFileInfo($path, $data) { + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = Filesystem::resolvePath($path); + if ($storage) { + $cache = $storage->getCache($path); + + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } + + return $cache->put($internalPath, $data); + } else { + return -1; + } + } + + /** + * search for files with the name matching $query + * + * @param string $query + * @return array + */ + public function search($query) { + return $this->searchCommon('%' . $query . '%', 'search'); + } + + /** + * search for files by mimetype + * + * @param string $mimetype + * @return array + */ + public function searchByMime($mimetype) { + return $this->searchCommon($mimetype, 'searchByMime'); + } + + /** + * @param string $query + * @param string $method + * @return array + */ + private function searchCommon($query, $method) { + $files = array(); + $rootLength = strlen($this->fakeRoot); + + $mountPoint = Filesystem::getMountPoint($this->fakeRoot); + $storage = Filesystem::getStorage($mountPoint); + if ($storage) { + $cache = $storage->getCache(''); + + $results = $cache->$method($query); + foreach ($results as $result) { + if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') { + $result['path'] = substr($mountPoint . $result['path'], $rootLength); + $files[] = $result; + } + } + + $mountPoints = Filesystem::getMountPoints($this->fakeRoot); + foreach ($mountPoints as $mountPoint) { + $storage = Filesystem::getStorage($mountPoint); + if ($storage) { + $cache = $storage->getCache(''); + + $relativeMountPoint = substr($mountPoint, $rootLength); + $results = $cache->$method($query); + if ($results) { + foreach ($results as $result) { + $result['path'] = $relativeMountPoint . $result['path']; + $files[] = $result; + } + } + } + } + } + return $files; + } + + /** + * Get the owner for a file or folder + * + * @param string $path + * @return string + */ + public function getOwner($path) { + return $this->basicOperation('getOwner', $path); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + /** + * @var Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = $this->resolvePath($path); + if ($storage) { + return $storage->getETag($internalPath); + } else { + return null; + } + } + + /** + * Get the path of a file by id, relative to the view + * + * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file + * + * @param int $id + * @return string + */ + public function getPath($id) { + list($storage, $internalPath) = Cache\Cache::getById($id); + $mounts = Filesystem::getMountByStorageId($storage); + foreach ($mounts as $mount) { + /** + * @var \OC\Files\Mount $mount + */ + $fullPath = $mount->getMountPoint() . $internalPath; + if (!is_null($path = $this->getRelativePath($fullPath))) { + return $path; + } + } + return null; + } +} diff --git a/lib/private/geo.php b/lib/private/geo.php new file mode 100644 index 00000000000..ed01ad0b616 --- /dev/null +++ b/lib/private/geo.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright (c) 2012 Georg Ehrke <ownclouddev at georgswebsite dot de> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class OC_Geo{ + /* + * @brief returns the closest timezone to coordinates + * @param (string) $latitude - Latitude + * @param (string) $longitude - Longitude + * @return (string) $timezone - closest timezone + */ + public static function timezone($latitude, $longitude) { + $alltimezones = DateTimeZone::listIdentifiers(); + $variances = array(); + //calculate for all timezones the system know + foreach($alltimezones as $timezone) { + $datetimezoneobj = new DateTimeZone($timezone); + $locationinformations = $datetimezoneobj->getLocation(); + $latitudeoftimezone = $locationinformations['latitude']; + $longitudeoftimezone = $locationinformations['longitude']; + $variances[abs($latitudeoftimezone - $latitude) + abs($longitudeoftimezone - $longitude)] = $timezone; + } + //sort array and return the timezone with the smallest difference + ksort($variances); + reset($variances); + return current($variances); + } +} diff --git a/lib/private/group.php b/lib/private/group.php new file mode 100644 index 00000000000..ba93dc129a1 --- /dev/null +++ b/lib/private/group.php @@ -0,0 +1,301 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * This class provides all methods needed for managing groups. + * + * 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 { + /** + * @var \OC\Group\Manager $manager + */ + private static $manager; + + /** + * @var \OC\User\Manager + */ + private static $userManager; + + /** + * @return \OC\Group\Manager + */ + public static function getManager() { + if (self::$manager) { + return self::$manager; + } + self::$userManager = \OC_User::getManager(); + self::$manager = new \OC\Group\Manager(self::$userManager); + return self::$manager; + } + + /** + * @brief set the group backend + * @param \OC_Group_Backend $backend The backend to use for user managment + * @return bool + */ + public static function useBackend($backend) { + self::getManager()->addBackend($backend); + return true; + } + + /** + * remove all used backends + */ + public static function clearBackends() { + self::getManager()->clearBackends(); + } + + /** + * @brief 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 + */ + public static function createGroup($gid) { + OC_Hook::emit("OC_Group", "pre_createGroup", array("run" => true, "gid" => $gid)); + + if (self::getManager()->createGroup($gid)) { + OC_Hook::emit("OC_User", "post_createGroup", array("gid" => $gid)); + return true; + } else { + return false; + } + } + + /** + * @brief 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 + */ + public static function deleteGroup($gid) { + // Prevent users from deleting group admin + if ($gid == "admin") { + return false; + } + + OC_Hook::emit("OC_Group", "pre_deleteGroup", array("run" => true, "gid" => $gid)); + + $group = self::getManager()->get($gid); + if ($group) { + if ($group->delete()) { + OC_Hook::emit("OC_User", "post_deleteGroup", array("gid" => $gid)); + return true; + } + } + return false; + } + + /** + * @brief 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. + */ + public static function inGroup($uid, $gid) { + $group = self::getManager()->get($gid); + $user = self::$userManager->get($uid); + if ($group and $user) { + return $group->inGroup($user); + } + return false; + } + + /** + * @brief 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. + */ + public static function addToGroup($uid, $gid) { + $group = self::getManager()->get($gid); + $user = self::$userManager->get($uid); + if ($group and $user) { + OC_Hook::emit("OC_Group", "pre_addToGroup", array("run" => true, "uid" => $uid, "gid" => $gid)); + $group->addUser($user); + OC_Hook::emit("OC_User", "post_addToGroup", array("uid" => $uid, "gid" => $gid)); + return true; + } else { + return false; + } + } + + /** + * @brief 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::$userManager->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; + } + } + + /** + * @brief Get all groups a user belongs to + * @param string $uid Name of the user + * @return array with group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + public static function getUserGroups($uid) { + $user = self::$userManager->get($uid); + if ($user) { + $groups = self::getManager()->getUserGroups($user); + $groupIds = array(); + foreach ($groups as $group) { + $groupIds[] = $group->getGID(); + } + return $groupIds; + } else { + return array(); + } + } + + /** + * @brief get a list of all groups + * @returns array with 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 + */ + public static function groupExists($gid) { + return self::getManager()->groupExists($gid); + } + + /** + * @brief get a list of all users in a group + * @returns array with 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(); + } + } + + /** + * @brief get a list of all users in several groups + * @param array $gids + * @param string $search + * @param int $limit + * @param int $offset + * @return array with 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; + } + + /** + * @brief get a list of all display names in a group + * @returns array with display names (value) and user ids(key) + */ + public static function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0) { + $group = self::getManager()->get($gid); + if ($group) { + $users = $group->searchDisplayName($search . $limit, $offset); + $displayNames = array(); + foreach ($users as $user) { + $displayNames[] = $user->getDisplayName(); + } + return $displayNames; + } else { + return array(); + } + } + + /** + * @brief get a list of all display names in several groups + * @param array $gids + * @param string $search + * @param int $limit + * @param int $offset + * @return array with 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) { + $displayNames = array_merge($diff, $displayNames); + } + } + return $displayNames; + } +} diff --git a/lib/private/group/backend.php b/lib/private/group/backend.php new file mode 100644 index 00000000000..2e17b5d0b7f --- /dev/null +++ b/lib/private/group/backend.php @@ -0,0 +1,157 @@ +<?php + +/** +* ownCloud +* +* @author Frank Karlitschek +* @copyright 2012 Frank Karlitschek frank@owncloud.org +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +/** + * error code for functions not provided by the group backend + */ +define('OC_GROUP_BACKEND_NOT_IMPLEMENTED', -501); + +/** + * actions that user backends can define + */ +define('OC_GROUP_BACKEND_CREATE_GROUP', 0x00000001); +define('OC_GROUP_BACKEND_DELETE_GROUP', 0x00000010); +define('OC_GROUP_BACKEND_ADD_TO_GROUP', 0x00000100); +define('OC_GROUP_BACKEND_REMOVE_FROM_GOUP', 0x00001000); +define('OC_GROUP_BACKEND_GET_DISPLAYNAME', 0x00010000); + +/** + * Abstract base class for user management + */ +abstract class OC_Group_Backend implements OC_Group_Interface { + protected $possibleActions = array( + OC_GROUP_BACKEND_CREATE_GROUP => 'createGroup', + OC_GROUP_BACKEND_DELETE_GROUP => 'deleteGroup', + OC_GROUP_BACKEND_ADD_TO_GROUP => 'addToGroup', + OC_GROUP_BACKEND_REMOVE_FROM_GOUP => 'removeFromGroup', + OC_GROUP_BACKEND_GET_DISPLAYNAME => 'displayNamesInGroup', + ); + + /** + * @brief Get all supported actions + * @return int bitwise-or'ed actions + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function getSupportedActions() { + $actions = 0; + foreach($this->possibleActions AS $action => $methodName) { + if(method_exists($this, $methodName)) { + $actions |= $action; + } + } + + return $actions; + } + + /** + * @brief Check if backend implements actions + * @param int $actions bitwise-or'ed actions + * @return boolean + * + * Returns the supported actions as int to be + * compared with OC_GROUP_BACKEND_CREATE_GROUP etc. + */ + public function implementsActions($actions) { + return (bool)($this->getSupportedActions() & $actions); + } + + /** + * @brief 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. + */ + public function inGroup($uid, $gid) { + return in_array($gid, $this->getUserGroups($uid)); + } + + /** + * @brief Get all groups a user belongs to + * @param string $uid Name of the user + * @return array with group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + public function getUserGroups($uid) { + return array(); + } + + /** + * @brief get a list of all groups + * @param string $search + * @param int $limit + * @param int $offset + * @return array with group names + * + * Returns a list with all groups + */ + + public function getGroups($search = '', $limit = -1, $offset = 0) { + return array(); + } + + /** + * check if a group exists + * @param string $gid + * @return bool + */ + public function groupExists($gid) { + return in_array($gid, $this->getGroups($gid, 1)); + } + + /** + * @brief get a list of all users in a group + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array with user ids + */ + public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { + return array(); + } + + /** + * @brief get a list of all display names in a group + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array with display names (value) and user ids (key) + */ + public function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0) { + $displayNames = array(); + $users = $this->usersInGroup($gid, $search, $limit, $offset); + foreach ($users as $user) { + $displayNames[$user] = $user; + } + + return $displayNames; + } + +} diff --git a/lib/private/group/database.php b/lib/private/group/database.php new file mode 100644 index 00000000000..d0974685ff6 --- /dev/null +++ b/lib/private/group/database.php @@ -0,0 +1,239 @@ +<?php + +/** + * ownCloud + * + * @author Frank Karlitschek + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +/* + * + * The following SQL statement is just a help for developers and will not be + * executed! + * + * CREATE TABLE `groups` ( + * `gid` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + * PRIMARY KEY (`gid`) + * ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + * + * CREATE TABLE `group_user` ( + * `gid` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + * `uid` varchar(64) COLLATE utf8_unicode_ci NOT NULL + * ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + * + */ + +/** + * Class for group management in a SQL Database (e.g. MySQL, SQLite) + */ +class OC_Group_Database extends OC_Group_Backend { + + /** + * @brief 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. + */ + public function createGroup( $gid ) { + // Check for existence + $stmt = OC_DB::prepare( "SELECT `gid` FROM `*PREFIX*groups` WHERE `gid` = ?" ); + $result = $stmt->execute( array( $gid )); + + if( $result->fetchRow() ) { + // Can not add an existing group + return false; + } + else{ + // Add group and exit + $stmt = OC_DB::prepare( "INSERT INTO `*PREFIX*groups` ( `gid` ) VALUES( ? )" ); + $result = $stmt->execute( array( $gid )); + + return $result ? true : false; + } + } + + /** + * @brief 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 + */ + public function deleteGroup( $gid ) { + // Delete the group + $stmt = OC_DB::prepare( "DELETE FROM `*PREFIX*groups` WHERE `gid` = ?" ); + $stmt->execute( array( $gid )); + + // Delete the group-user relation + $stmt = OC_DB::prepare( "DELETE FROM `*PREFIX*group_user` WHERE `gid` = ?" ); + $stmt->execute( array( $gid )); + + return true; + } + + /** + * @brief 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. + */ + public function inGroup( $uid, $gid ) { + // check + $stmt = OC_DB::prepare( "SELECT `uid` FROM `*PREFIX*group_user` WHERE `gid` = ? AND `uid` = ?" ); + $result = $stmt->execute( array( $gid, $uid )); + + return $result->fetchRow() ? true : false; + } + + /** + * @brief 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. + */ + public function addToGroup( $uid, $gid ) { + // No duplicate entries! + if( !$this->inGroup( $uid, $gid )) { + $stmt = OC_DB::prepare( "INSERT INTO `*PREFIX*group_user` ( `uid`, `gid` ) VALUES( ?, ? )" ); + $stmt->execute( array( $uid, $gid )); + return true; + }else{ + return false; + } + } + + /** + * @brief 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 function removeFromGroup( $uid, $gid ) { + $stmt = OC_DB::prepare( "DELETE FROM `*PREFIX*group_user` WHERE `uid` = ? AND `gid` = ?" ); + $stmt->execute( array( $uid, $gid )); + + return true; + } + + /** + * @brief Get all groups a user belongs to + * @param string $uid Name of the user + * @return array with group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + public function getUserGroups( $uid ) { + // No magic! + $stmt = OC_DB::prepare( "SELECT `gid` FROM `*PREFIX*group_user` WHERE `uid` = ?" ); + $result = $stmt->execute( array( $uid )); + + $groups = array(); + while( $row = $result->fetchRow()) { + $groups[] = $row["gid"]; + } + + return $groups; + } + + /** + * @brief get a list of all groups + * @param string $search + * @param int $limit + * @param int $offset + * @return array with group names + * + * Returns a list with all groups + */ + public function getGroups($search = '', $limit = null, $offset = null) { + $stmt = OC_DB::prepare('SELECT `gid` FROM `*PREFIX*groups` WHERE `gid` LIKE ?', $limit, $offset); + $result = $stmt->execute(array($search.'%')); + $groups = array(); + while ($row = $result->fetchRow()) { + $groups[] = $row['gid']; + } + return $groups; + } + + /** + * check if a group exists + * @param string $gid + * @return bool + */ + public function groupExists($gid) { + $query = OC_DB::prepare('SELECT `gid` FROM `*PREFIX*groups` WHERE `gid` = ?'); + $result = $query->execute(array($gid))->fetchOne(); + if ($result) { + return true; + } + return false; + } + + /** + * @brief get a list of all users in a group + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array with user ids + */ + public function usersInGroup($gid, $search = '', $limit = null, $offset = null) { + $stmt = OC_DB::prepare('SELECT `uid` FROM `*PREFIX*group_user` WHERE `gid` = ? AND `uid` LIKE ?', + $limit, + $offset); + $result = $stmt->execute(array($gid, $search.'%')); + $users = array(); + while ($row = $result->fetchRow()) { + $users[] = $row['uid']; + } + return $users; + } + + /** + * @brief get a list of all display names in a group + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array with display names (value) and user ids (key) + */ + public function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0) { + $displayNames = array(); + + $stmt = OC_DB::prepare('SELECT `*PREFIX*users`.`uid`, `*PREFIX*users`.`displayname`' + .' FROM `*PREFIX*users`' + .' INNER JOIN `*PREFIX*group_user` ON `*PREFIX*group_user`.`uid` = `*PREFIX*users`.`uid`' + .' WHERE `gid` = ? AND `*PREFIX*group_user`.`uid` LIKE ?', + $limit, + $offset); + $result = $stmt->execute(array($gid, $search.'%')); + $users = array(); + while ($row = $result->fetchRow()) { + $displayName = trim($row['displayname'], ' '); + $displayNames[$row['uid']] = empty($displayName) ? $row['uid'] : $displayName; + } + return $displayNames; + } +} diff --git a/lib/private/group/dummy.php b/lib/private/group/dummy.php new file mode 100644 index 00000000000..9516fd52ff8 --- /dev/null +++ b/lib/private/group/dummy.php @@ -0,0 +1,160 @@ +<?php + +/** +* ownCloud +* +* @author Frank Karlitschek +* @copyright 2012 Frank Karlitschek frank@owncloud.org +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +/** + * dummy group backend, does not keep state, only for testing use + */ +class OC_Group_Dummy extends OC_Group_Backend { + private $groups=array(); + /** + * @brief Try to create a new group + * @param $gid The name of the group to create + * @returns true/false + * + * Trys to create a new group. If the group name already exists, false will + * be returned. + */ + public function createGroup($gid) { + if(!isset($this->groups[$gid])) { + $this->groups[$gid]=array(); + return true; + }else{ + return false; + } + } + + /** + * @brief delete a group + * @param $gid gid of the group to delete + * @returns true/false + * + * Deletes a group and removes it from the group_user-table + */ + public function deleteGroup($gid) { + if(isset($this->groups[$gid])) { + unset($this->groups[$gid]); + return true; + }else{ + return false; + } + } + + /** + * @brief is user in group? + * @param $uid uid of the user + * @param $gid gid of the group + * @returns true/false + * + * Checks whether the user is member of a group or not. + */ + public function inGroup($uid, $gid) { + if(isset($this->groups[$gid])) { + return (array_search($uid, $this->groups[$gid])!==false); + }else{ + return false; + } + } + + /** + * @brief Add a user to a group + * @param $uid Name of the user to add to group + * @param $gid Name of the group in which add the user + * @returns true/false + * + * Adds a user to a group. + */ + public function addToGroup($uid, $gid) { + if(isset($this->groups[$gid])) { + if(array_search($uid, $this->groups[$gid])===false) { + $this->groups[$gid][]=$uid; + return true; + }else{ + return false; + } + }else{ + return false; + } + } + + /** + * @brief Removes a user from a group + * @param $uid NameUSER of the user to remove from group + * @param $gid Name of the group from which remove the user + * @returns true/false + * + * removes the user from a group. + */ + public function removeFromGroup($uid, $gid) { + if(isset($this->groups[$gid])) { + if(($index=array_search($uid, $this->groups[$gid]))!==false) { + unset($this->groups[$gid][$index]); + }else{ + return false; + } + }else{ + return false; + } + } + + /** + * @brief Get all groups a user belongs to + * @param $uid Name of the user + * @returns array with group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + public function getUserGroups($uid) { + $groups=array(); + $allGroups=array_keys($this->groups); + foreach($allGroups as $group) { + if($this->inGroup($uid, $group)) { + $groups[]=$group; + } + } + return $groups; + } + + /** + * @brief get a list of all groups + * @returns array with group names + * + * Returns a list with all groups + */ + public function getGroups($search = '', $limit = -1, $offset = 0) { + return array_keys($this->groups); + } + + /** + * @brief get a list of all users in a group + * @returns array with user ids + */ + public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { + if(isset($this->groups[$gid])) { + return $this->groups[$gid]; + }else{ + return array(); + } + } + +} diff --git a/lib/private/group/example.php b/lib/private/group/example.php new file mode 100644 index 00000000000..3519b9ed92f --- /dev/null +++ b/lib/private/group/example.php @@ -0,0 +1,109 @@ +<?php + +/** +* ownCloud +* +* @author Frank Karlitschek +* @copyright 2012 Frank Karlitschek frank@owncloud.org +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +/** + * abstract reference class for group management + * this class should only be used as a reference for method signatures and their descriptions + */ +abstract class OC_Group_Example { + /** + * @brief Try to create a new group + * @param $gid The name of the group to create + * @returns true/false + * + * Trys to create a new group. If the group name already exists, false will + * be returned. + */ + abstract public static function createGroup($gid); + + /** + * @brief delete a group + * @param $gid gid of the group to delete + * @returns true/false + * + * Deletes a group and removes it from the group_user-table + */ + abstract public static function deleteGroup($gid); + + /** + * @brief is user in group? + * @param $uid uid of the user + * @param $gid gid of the group + * @returns true/false + * + * Checks whether the user is member of a group or not. + */ + abstract public static function inGroup($uid, $gid); + + /** + * @brief Add a user to a group + * @param $uid Name of the user to add to group + * @param $gid Name of the group in which add the user + * @returns true/false + * + * Adds a user to a group. + */ + abstract public static function addToGroup($uid, $gid); + + /** + * @brief Removes a user from a group + * @param $uid NameUSER of the user to remove from group + * @param $gid Name of the group from which remove the user + * @returns true/false + * + * removes the user from a group. + */ + abstract public static function removeFromGroup($uid, $gid); + + /** + * @brief Get all groups a user belongs to + * @param $uid Name of the user + * @returns array with group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + abstract public static function getUserGroups($uid); + + /** + * @brief get a list of all groups + * @returns array with group names + * + * Returns a list with all groups + */ + abstract public static function getGroups($search = '', $limit = -1, $offset = 0); + + /** + * check if a group exists + * @param string $gid + * @return bool + */ + abstract public function groupExists($gid); + + /** + * @brief get a list of all users in a group + * @returns array with user ids + */ + abstract public static function usersInGroup($gid, $search = '', $limit = -1, $offset = 0); + +} diff --git a/lib/private/group/group.php b/lib/private/group/group.php new file mode 100644 index 00000000000..bcd2419b309 --- /dev/null +++ b/lib/private/group/group.php @@ -0,0 +1,248 @@ +<?php + +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Group; + +class Group { + /** + * @var string $id + */ + private $gid; + + /** + * @var \OC\User\User[] $users + */ + private $users; + + /** + * @var \OC_Group_Backend[] | \OC_Group_Database[] $backend + */ + private $backends; + + /** + * @var \OC\Hooks\PublicEmitter $emitter; + */ + private $emitter; + + /** + * @var \OC\User\Manager $userManager + */ + private $userManager; + + /** + * @param string $gid + * @param \OC_Group_Backend[] $backends + * @param \OC\User\Manager $userManager + * @param \OC\Hooks\PublicEmitter $emitter + */ + public function __construct($gid, $backends, $userManager, $emitter = null) { + $this->gid = $gid; + $this->backends = $backends; + $this->userManager = $userManager; + $this->emitter = $emitter; + } + + public function getGID() { + return $this->gid; + } + + /** + * get all users in the group + * + * @return \OC\User\User[] + */ + public function getUsers() { + if ($this->users) { + return $this->users; + } + + $userIds = array(); + foreach ($this->backends as $backend) { + $diff = array_diff( + $backend->usersInGroup($this->gid), + $userIds + ); + if ($diff) { + $userIds = array_merge($userIds, $diff); + } + } + + $this->users = $this->getVerifiedUsers($userIds); + return $this->users; + } + + /** + * check if a user is in the group + * + * @param \OC\User\User $user + * @return bool + */ + public function inGroup($user) { + foreach ($this->backends as $backend) { + if ($backend->inGroup($user->getUID(), $this->gid)) { + return true; + } + } + return false; + } + + /** + * add a user to the group + * + * @param \OC\User\User $user + */ + public function addUser($user) { + if ($this->inGroup($user)) { + return; + } + + if ($this->emitter) { + $this->emitter->emit('\OC\Group', 'preAddUser', array($this, $user)); + } + foreach ($this->backends as $backend) { + if ($backend->implementsActions(OC_GROUP_BACKEND_ADD_TO_GROUP)) { + $backend->addToGroup($user->getUID(), $this->gid); + if ($this->users) { + $this->users[$user->getUID()] = $user; + } + if ($this->emitter) { + $this->emitter->emit('\OC\Group', 'postAddUser', array($this, $user)); + } + return; + } + } + } + + /** + * remove a user from the group + * + * @param \OC\User\User $user + */ + public function removeUser($user) { + $result = false; + if ($this->emitter) { + $this->emitter->emit('\OC\Group', 'preRemoveUser', array($this, $user)); + } + foreach ($this->backends as $backend) { + if ($backend->implementsActions(OC_GROUP_BACKEND_REMOVE_FROM_GOUP) and $backend->inGroup($user->getUID(), $this->gid)) { + $backend->removeFromGroup($user->getUID(), $this->gid); + $result = true; + } + } + if ($result) { + if ($this->emitter) { + $this->emitter->emit('\OC\Group', 'postRemoveUser', array($this, $user)); + } + if ($this->users) { + foreach ($this->users as $index => $groupUser) { + if ($groupUser->getUID() === $user->getUID()) { + unset($this->users[$index]); + return; + } + } + } + } + } + + /** + * search for users in the group by userid + * + * @param string $search + * @param int $limit + * @param int $offset + * @return \OC\User\User[] + */ + public function searchUsers($search, $limit = null, $offset = null) { + $users = array(); + foreach ($this->backends as $backend) { + $userIds = $backend->usersInGroup($this->gid, $search, $limit, $offset); + if (!is_null($limit)) { + $limit -= count($userIds); + } + if (!is_null($offset)) { + $offset -= count($userIds); + } + $users += $this->getVerifiedUsers($userIds); + if (!is_null($limit) and $limit <= 0) { + return array_values($users); + } + } + return array_values($users); + } + + /** + * search for users in the group by displayname + * + * @param string $search + * @param int $limit + * @param int $offset + * @return \OC\User\User[] + */ + public function searchDisplayName($search, $limit = null, $offset = null) { + foreach ($this->backends as $backend) { + if ($backend->implementsActions(OC_GROUP_BACKEND_GET_DISPLAYNAME)) { + $userIds = array_keys($backend->displayNamesInGroup($this->gid, $search, $limit, $offset)); + } else { + $userIds = $backend->usersInGroup($this->gid, $search, $limit, $offset); + } + if (!is_null($limit)) { + $limit -= count($userIds); + } + if (!is_null($offset)) { + $offset -= count($userIds); + } + $users = $this->getVerifiedUsers($userIds); + if (!is_null($limit) and $limit <= 0) { + return array_values($users); + } + } + return array_values($users); + } + + /** + * delete the group + * + * @return bool + */ + public function delete() { + $result = false; + if ($this->emitter) { + $this->emitter->emit('\OC\Group', 'preDelete', array($this)); + } + foreach ($this->backends as $backend) { + if ($backend->implementsActions(OC_GROUP_BACKEND_DELETE_GROUP)) { + $result = true; + $backend->deleteGroup($this->gid); + } + } + if ($result and $this->emitter) { + $this->emitter->emit('\OC\Group', 'postDelete', array($this)); + } + return $result; + } + + /** + * @brief returns all the Users from an array that really exists + * @param $userIds an array containing user IDs + * @return an Array with the userId as Key and \OC\User\User as value + */ + private function getVerifiedUsers($userIds) { + if(!is_array($userIds)) { + return array(); + } + $users = array(); + foreach ($userIds as $userId) { + $user = $this->userManager->get($userId); + if(!is_null($user)) { + $users[$userId] = $user; + } + } + return $users; + } +} diff --git a/lib/private/group/interface.php b/lib/private/group/interface.php new file mode 100644 index 00000000000..4ef3663837f --- /dev/null +++ b/lib/private/group/interface.php @@ -0,0 +1,83 @@ +<?php + +/** + * ownCloud - group interface + * + * @author Arthur Schiwon + * @copyright 2012 Arthur Schiwon blizzz@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +interface OC_Group_Interface { + /** + * @brief Check if backend implements actions + * @param int $actions bitwise-or'ed actions + * @return boolean + * + * Returns the supported actions as int to be + * compared with OC_GROUP_BACKEND_CREATE_GROUP etc. + */ + public function implementsActions($actions); + + /** + * @brief 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. + */ + public function inGroup($uid, $gid); + + /** + * @brief Get all groups a user belongs to + * @param string $uid Name of the user + * @return array with group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + public function getUserGroups($uid); + + /** + * @brief get a list of all groups + * @param string $search + * @param int $limit + * @param int $offset + * @return array with group names + * + * Returns a list with all groups + */ + public function getGroups($search = '', $limit = -1, $offset = 0); + + /** + * check if a group exists + * @param string $gid + * @return bool + */ + public function groupExists($gid); + + /** + * @brief get a list of all users in a group + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array with user ids + */ + public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0); + +} diff --git a/lib/private/group/manager.php b/lib/private/group/manager.php new file mode 100644 index 00000000000..bf469d51d12 --- /dev/null +++ b/lib/private/group/manager.php @@ -0,0 +1,169 @@ +<?php + +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Group; + +use OC\Hooks\PublicEmitter; + +/** + * Class Manager + * + * Hooks available in scope \OC\Group: + * - preAddUser(\OC\Group\Group $group, \OC\User\User $user) + * - postAddUser(\OC\Group\Group $group, \OC\User\User $user) + * - preRemoveUser(\OC\Group\Group $group, \OC\User\User $user) + * - postRemoveUser(\OC\Group\Group $group, \OC\User\User $user) + * - preDelete(\OC\Group\Group $group) + * - postDelete(\OC\Group\Group $group) + * - preCreate(string $groupId) + * - postCreate(\OC\Group\Group $group) + * + * @package OC\Group + */ +class Manager extends PublicEmitter { + /** + * @var \OC_Group_Backend[] | \OC_Group_Database[] $backends + */ + private $backends = array(); + + /** + * @var \OC\User\Manager $userManager + */ + private $userManager; + + /** + * @var \OC\Group\Group[] + */ + private $cachedGroups; + + /** + * @param \OC\User\Manager $userManager + */ + public function __construct($userManager) { + $this->userManager = $userManager; + $cache = & $this->cachedGroups; + $this->listen('\OC\Group', 'postDelete', function ($group) use (&$cache) { + /** + * @var \OC\Group\Group $group + */ + unset($cache[$group->getGID()]); + }); + } + + /** + * @param \OC_Group_Backend $backend + */ + public function addBackend($backend) { + $this->backends[] = $backend; + } + + public function clearBackends() { + $this->backends = array(); + $this->cachedGroups = array(); + } + + /** + * @param string $gid + * @return \OC\Group\Group + */ + public function get($gid) { + if (isset($this->cachedGroups[$gid])) { + return $this->cachedGroups[$gid]; + } + foreach ($this->backends as $backend) { + if ($backend->groupExists($gid)) { + return $this->getGroupObject($gid); + } + } + return null; + } + + protected function getGroupObject($gid) { + $backends = array(); + foreach ($this->backends as $backend) { + if ($backend->groupExists($gid)) { + $backends[] = $backend; + } + } + $this->cachedGroups[$gid] = new Group($gid, $backends, $this->userManager, $this); + return $this->cachedGroups[$gid]; + } + + /** + * @param string $gid + * @return bool + */ + public function groupExists($gid) { + return !is_null($this->get($gid)); + } + + /** + * @param string $gid + * @return \OC\Group\Group + */ + public function createGroup($gid) { + if (!$gid) { + return false; + } else if ($this->groupExists($gid)) { + return $this->get($gid); + } else { + $this->emit('\OC\Group', 'preCreate', array($gid)); + foreach ($this->backends as $backend) { + if ($backend->implementsActions(OC_GROUP_BACKEND_CREATE_GROUP)) { + $backend->createGroup($gid); + $group = $this->getGroupObject($gid); + $this->emit('\OC\Group', 'postCreate', array($group)); + return $group; + } + } + return null; + } + } + + /** + * @param string $search + * @param int $limit + * @param int $offset + * @return \OC\Group\Group[] + */ + public function search($search, $limit = null, $offset = null) { + $groups = array(); + foreach ($this->backends as $backend) { + $groupIds = $backend->getGroups($search, $limit, $offset); + if (!is_null($limit)) { + $limit -= count($groupIds); + } + if (!is_null($offset)) { + $offset -= count($groupIds); + } + foreach ($groupIds as $groupId) { + $groups[$groupId] = $this->getGroupObject($groupId); + } + if (!is_null($limit) and $limit <= 0) { + return array_values($groups); + } + } + return array_values($groups); + } + + /** + * @param \OC\User\User $user + * @return \OC\Group\Group[] + */ + public function getUserGroups($user) { + $groups = array(); + foreach ($this->backends as $backend) { + $groupIds = $backend->getUserGroups($user->getUID()); + foreach ($groupIds as $groupId) { + $groups[$groupId] = $this->getGroupObject($groupId); + } + } + return array_values($groups); + } +} diff --git a/lib/private/helper.php b/lib/private/helper.php new file mode 100644 index 00000000000..66e7acb407a --- /dev/null +++ b/lib/private/helper.php @@ -0,0 +1,934 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @author Jakob Sack + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * Collection of useful functions + */ +class OC_Helper { + private static $tmpFiles = array(); + private static $mimetypeIcons = array(); + private static $mimetypeDetector; + private static $templateManager; + + /** + * @brief Creates an url using a defined route + * @param $route + * @param array $parameters + * @return + * @internal param array $args with param=>value, will be appended to the returned url + * @returns the url + * + * Returns a url to the given app and file. + */ + public static function linkToRoute($route, $parameters = array()) { + $urlLinkTo = OC::getRouter()->generate($route, $parameters); + return $urlLinkTo; + } + + /** + * @brief Creates an url + * @param string $app app + * @param string $file file + * @param array $args array with param=>value, will be appended to the returned url + * The value of $args will be urlencoded + * @return string the url + * + * Returns a url to the given app and file. + */ + public static function linkTo( $app, $file, $args = array() ) { + if( $app != '' ) { + $app_path = OC_App::getAppPath($app); + // Check if the app is in the app folder + if ($app_path && file_exists($app_path . '/' . $file)) { + if (substr($file, -3) == 'php' || substr($file, -3) == 'css') { + $urlLinkTo = OC::$WEBROOT . '/index.php/apps/' . $app; + $urlLinkTo .= ($file != 'index.php') ? '/' . $file : ''; + } else { + $urlLinkTo = OC_App::getAppWebPath($app) . '/' . $file; + } + } else { + $urlLinkTo = OC::$WEBROOT . '/' . $app . '/' . $file; + } + } else { + if (file_exists(OC::$SERVERROOT . '/core/' . $file)) { + $urlLinkTo = OC::$WEBROOT . '/core/' . $file; + } else { + $urlLinkTo = OC::$WEBROOT . '/' . $file; + } + } + + if ($args && $query = http_build_query($args, '', '&')) { + $urlLinkTo .= '?' . $query; + } + + return $urlLinkTo; + } + + /** + * @brief Creates an absolute url + * @param string $app app + * @param string $file file + * @param array $args array with param=>value, will be appended to the returned url + * The value of $args will be urlencoded + * @return string the url + * + * Returns a absolute url to the given app and file. + */ + public static function linkToAbsolute($app, $file, $args = array()) { + $urlLinkTo = self::linkTo($app, $file, $args); + return self::makeURLAbsolute($urlLinkTo); + } + + /** + * @brief Makes an $url absolute + * @param string $url the url + * @return string the absolute url + * + * Returns a absolute url to the given app and file. + */ + public static function makeURLAbsolute($url) { + return OC_Request::serverProtocol() . '://' . OC_Request::serverHost() . $url; + } + + /** + * @brief Creates an url for remote use + * @param string $service id + * @return string the url + * + * Returns a url to the given service. + */ + public static function linkToRemoteBase($service) { + return self::linkTo('', 'remote.php') . '/' . $service; + } + + /** + * @brief Creates an absolute url for remote use + * @param string $service id + * @param bool $add_slash + * @return string the url + * + * Returns a absolute url to the given service. + */ + public static function linkToRemote($service, $add_slash = true) { + return self::makeURLAbsolute(self::linkToRemoteBase($service)) + . (($add_slash && $service[strlen($service) - 1] != '/') ? '/' : ''); + } + + /** + * @brief 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) { + return self::linkToAbsolute('', 'public.php') . '?service=' . $service + . (($add_slash && $service[strlen($service) - 1] != '/') ? '/' : ''); + } + + /** + * @brief Creates path to an image + * @param string $app app + * @param string $image image name + * @return string the url + * + * Returns the path to the image. + */ + public static function imagePath($app, $image) { + // Read the selected theme from the config file + $theme = OC_Util::getTheme(); + + // Check if the app is in the app folder + if (file_exists(OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$image")) { + return OC::$WEBROOT . "/themes/$theme/apps/$app/img/$image"; + } elseif (file_exists(OC_App::getAppPath($app) . "/img/$image")) { + return OC_App::getAppWebPath($app) . "/img/$image"; + } elseif (!empty($app) and file_exists(OC::$SERVERROOT . "/themes/$theme/$app/img/$image")) { + return OC::$WEBROOT . "/themes/$theme/$app/img/$image"; + } elseif (!empty($app) and file_exists(OC::$SERVERROOT . "/$app/img/$image")) { + return OC::$WEBROOT . "/$app/img/$image"; + } elseif (file_exists(OC::$SERVERROOT . "/themes/$theme/core/img/$image")) { + return OC::$WEBROOT . "/themes/$theme/core/img/$image"; + } elseif (file_exists(OC::$SERVERROOT . "/core/img/$image")) { + return OC::$WEBROOT . "/core/img/$image"; + } else { + throw new RuntimeException('image not found: image:' . $image . ' webroot:' . OC::$WEBROOT . ' serverroot:' . OC::$SERVERROOT); + } + } + + /** + * @brief get path to icon of file type + * @param string $mimetype mimetype + * @return string the url + * + * Returns the path to the image of this file type. + */ + public static function mimetypeIcon($mimetype) { + $alias = array( + 'application/xml' => 'code/xml', + 'application/msword' => 'x-office/document', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'x-office/document', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'x-office/document', + 'application/vnd.ms-word.document.macroEnabled.12' => 'x-office/document', + 'application/vnd.ms-word.template.macroEnabled.12' => 'x-office/document', + 'application/vnd.oasis.opendocument.text' => 'x-office/document', + 'application/vnd.oasis.opendocument.text-template' => 'x-office/document', + 'application/vnd.oasis.opendocument.text-web' => 'x-office/document', + 'application/vnd.oasis.opendocument.text-master' => 'x-office/document', + 'application/vnd.ms-powerpoint' => 'x-office/presentation', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'x-office/presentation', + 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'x-office/presentation', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'x-office/presentation', + 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => 'x-office/presentation', + 'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => 'x-office/presentation', + 'application/vnd.ms-powerpoint.template.macroEnabled.12' => 'x-office/presentation', + 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => 'x-office/presentation', + 'application/vnd.oasis.opendocument.presentation' => 'x-office/presentation', + 'application/vnd.oasis.opendocument.presentation-template' => 'x-office/presentation', + 'application/vnd.ms-excel' => 'x-office/spreadsheet', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'x-office/spreadsheet', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'x-office/spreadsheet', + 'application/vnd.ms-excel.sheet.macroEnabled.12' => 'x-office/spreadsheet', + 'application/vnd.ms-excel.template.macroEnabled.12' => 'x-office/spreadsheet', + 'application/vnd.ms-excel.addin.macroEnabled.12' => 'x-office/spreadsheet', + 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => 'x-office/spreadsheet', + 'application/vnd.oasis.opendocument.spreadsheet' => 'x-office/spreadsheet', + 'application/vnd.oasis.opendocument.spreadsheet-template' => 'x-office/spreadsheet', + ); + + if (isset($alias[$mimetype])) { + $mimetype = $alias[$mimetype]; + } + if (isset(self::$mimetypeIcons[$mimetype])) { + return self::$mimetypeIcons[$mimetype]; + } + // Replace slash and backslash with a minus + $icon = str_replace('/', '-', $mimetype); + $icon = str_replace('\\', '-', $icon); + + // Is it a dir? + if ($mimetype === 'dir') { + self::$mimetypeIcons[$mimetype] = OC::$WEBROOT . '/core/img/filetypes/folder.png'; + return OC::$WEBROOT . '/core/img/filetypes/folder.png'; + } + if ($mimetype === 'dir-shared') { + self::$mimetypeIcons[$mimetype] = OC::$WEBROOT . '/core/img/filetypes/folder-shared.png'; + return OC::$WEBROOT . '/core/img/filetypes/folder-shared.png'; + } + if ($mimetype === 'dir-external') { + self::$mimetypeIcons[$mimetype] = OC::$WEBROOT . '/core/img/filetypes/folder-external.png'; + return OC::$WEBROOT . '/core/img/filetypes/folder-external.png'; + } + + // Icon exists? + if (file_exists(OC::$SERVERROOT . '/core/img/filetypes/' . $icon . '.png')) { + self::$mimetypeIcons[$mimetype] = OC::$WEBROOT . '/core/img/filetypes/' . $icon . '.png'; + return OC::$WEBROOT . '/core/img/filetypes/' . $icon . '.png'; + } + + // Try only the first part of the filetype + $mimePart = substr($icon, 0, strpos($icon, '-')); + if (file_exists(OC::$SERVERROOT . '/core/img/filetypes/' . $mimePart . '.png')) { + self::$mimetypeIcons[$mimetype] = OC::$WEBROOT . '/core/img/filetypes/' . $mimePart . '.png'; + return OC::$WEBROOT . '/core/img/filetypes/' . $mimePart . '.png'; + } else { + self::$mimetypeIcons[$mimetype] = OC::$WEBROOT . '/core/img/filetypes/file.png'; + return OC::$WEBROOT . '/core/img/filetypes/file.png'; + } + } + + /** + * @brief 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 self::linkToRoute( 'core_ajax_preview', array('x' => 36, 'y' => 36, 'file' => urlencode($path) )); + } + + public static function publicPreviewIcon( $path, $token ) { + return self::linkToRoute( 'core_ajax_public_preview', array('x' => 36, 'y' => 36, 'file' => urlencode($path), 't' => $token)); + } + + /** + * @brief 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, 1); + 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"; + } + + /** + * @brief Make a computer file size + * @param string $str file size in a fancy format + * @return int 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); + + $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]]; + } + + $bytes = round($bytes, 2); + + return $bytes; + } + + /** + * @brief Recursive editing of file permissions + * @param string $path path to file or folder + * @param int $filemode unix style file permissions + * @return bool + */ + static function chmodr($path, $filemode) { + if (!is_dir($path)) + return chmod($path, $filemode); + $dh = opendir($path); + if(is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if ($file != '.' && $file != '..') { + $fullpath = $path . '/' . $file; + if (is_link($fullpath)) + return false; + elseif (!is_dir($fullpath) && !@chmod($fullpath, $filemode)) + return false; elseif (!self::chmodr($fullpath, $filemode)) + return false; + } + } + closedir($dh); + } + if (@chmod($path, $filemode)) + return true; + else + return false; + } + + /** + * @brief 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); + } + } + + /** + * @brief Recursive deletion of folders + * @param string $dir path to the folder + * @return bool + */ + static function rmdirr($dir) { + if (is_dir($dir)) { + $files = scandir($dir); + foreach ($files as $file) { + if ($file != "." && $file != "..") { + self::rmdirr("$dir/$file"); + } + } + rmdir($dir); + } elseif (file_exists($dir)) { + unlink($dir); + } + if (file_exists($dir)) { + return false; + } else { + return true; + } + } + + /** + * @return \OC\Files\Type\Detection + */ + static public function getMimetypeDetector() { + if (!self::$mimetypeDetector) { + self::$mimetypeDetector = new \OC\Files\Type\Detection(); + self::$mimetypeDetector->registerTypeArray(include 'mimetypes.list.php'); + } + return self::$mimetypeDetector; + } + + /** + * @return \OC\Files\Type\TemplateManager + */ + static public function getFileTemplateManager() { + if (!self::$templateManager) { + self::$templateManager = new \OC\Files\Type\TemplateManager(); + } + return self::$templateManager; + } + + /** + * Try to guess the mimetype based on filename + * + * @param string $path + * @return string + */ + static public function getFileNameMimeType($path) { + return self::getMimetypeDetector()->detectPath($path); + } + + /** + * get the mimetype form a local file + * + * @param string $path + * @return string + * does NOT work for ownClouds filesystem, use OC_FileSystem::getMimeType instead + */ + static function getMimeType($path) { + return self::getMimetypeDetector()->detect($path); + } + + /** + * get the mimetype form a data string + * + * @param string $data + * @return string + */ + static function getStringMimeType($data) { + return self::getMimetypeDetector()->detectString($data); + } + + /** + * @brief Checks $_REQUEST contains a var for the $s key. If so, returns the html-escaped value of this var; otherwise returns the default value provided by $d. + * @param string $s name of the var to escape, if set. + * @param string $d default value. + * @return string the print-safe value. + * + */ + + //FIXME: should also check for value validation (i.e. the email is an email). + public static function init_var($s, $d = "") { + $r = $d; + if (isset($_REQUEST[$s]) && !empty($_REQUEST[$s])) { + $r = OC_Util::sanitizeHTML($_REQUEST[$s]); + } + + return $r; + } + + /** + * returns "checked"-attribute if request contains selected radio element + * OR if radio element is the default one -- maybe? + * + * @param string $s Name of radio-button element name + * @param string $v Value of current radio-button element + * @param string $d Value of default radio-button element + */ + public static function init_radio($s, $v, $d) { + if ((isset($_REQUEST[$s]) && $_REQUEST[$s] == $v) || (!isset($_REQUEST[$s]) && $v == $d)) + print "checked=\"checked\" "; + } + + /** + * detect if a given program is found in the search PATH + * + * @param $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 = ini_get('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 int the number of bytes copied + */ + public static function streamCopy($source, $target) { + if (!$source or !$target) { + return false; + } + $result = true; + $count = 0; + while (!feof($source)) { + if (($c = fwrite($target, fread($source, 8192))) === false) { + $result = false; + } else { + $count += $c; + } + } + return array($count, $result); + } + + /** + * create a temporary file with an unique filename + * + * @param string $postfix + * @return string + * + * temporary files are automatically cleaned up after the script is finished + */ + public static function tmpFile($postfix = '') { + $file = get_temp_dir() . '/' . md5(time() . rand()) . $postfix; + $fh = fopen($file, 'w'); + fclose($fh); + self::$tmpFiles[] = $file; + return $file; + } + + /** + * move a file to oc-noclean temp dir + * + * @param string $filename + * @return mixed + * + */ + public static function moveToNoClean($filename = '') { + if ($filename == '') { + return false; + } + $tmpDirNoClean = get_temp_dir() . '/oc-noclean/'; + if (!file_exists($tmpDirNoClean) || !is_dir($tmpDirNoClean)) { + if (file_exists($tmpDirNoClean)) { + unlink($tmpDirNoClean); + } + mkdir($tmpDirNoClean); + } + $newname = $tmpDirNoClean . basename($filename); + if (rename($filename, $newname)) { + return $newname; + } else { + return false; + } + } + + /** + * create a temporary folder with an unique filename + * + * @return string + * + * temporary files are automatically cleaned up after the script is finished + */ + public static function tmpFolder() { + $path = get_temp_dir() . '/' . md5(time() . rand()); + mkdir($path); + self::$tmpFiles[] = $path; + return $path . '/'; + } + + /** + * remove all files created by self::tmpFile + */ + public static function cleanTmp() { + $leftoversFile = get_temp_dir() . '/oc-not-deleted'; + if (file_exists($leftoversFile)) { + $leftovers = file($leftoversFile); + foreach ($leftovers as $file) { + self::rmdirr($file); + } + unlink($leftoversFile); + } + + foreach (self::$tmpFiles as $file) { + if (file_exists($file)) { + if (!self::rmdirr($file)) { + file_put_contents($leftoversFile, $file . "\n", FILE_APPEND); + } + } + } + } + + /** + * remove all files in PHP /oc-noclean temp dir + */ + public static function cleanTmpNoClean() { + $tmpDirNoCleanName=get_temp_dir() . '/oc-noclean/'; + if(file_exists($tmpDirNoCleanName) && is_dir($tmpDirNoCleanName)) { + $files=scandir($tmpDirNoCleanName); + foreach($files as $file) { + $fileName = $tmpDirNoCleanName . $file; + if (!\OC\Files\Filesystem::isIgnoredDir($file) && filemtime($fileName) + 600 < time()) { + unlink($fileName); + } + } + // if oc-noclean is empty delete it + $isTmpDirNoCleanEmpty = true; + $tmpDirNoClean = opendir($tmpDirNoCleanName); + if(is_resource($tmpDirNoClean)) { + while (false !== ($file = readdir($tmpDirNoClean))) { + if (!\OC\Files\Filesystem::isIgnoredDir($file)) { + $isTmpDirNoCleanEmpty = false; + } + } + } + if ($isTmpDirNoCleanEmpty) { + rmdir($tmpDirNoCleanName); + } + } + } + + /** + * Adds a suffix to the name in case the file exists + * + * @param $path + * @param $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 $path + * @param $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; + $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; + } + + /** + * @brief Checks if $sub is a subdirectory of $parent + * + * @param string $sub + * @param string $parent + * @return bool + */ + public static function issubdirectory($sub, $parent) { + if (strpos(realpath($sub), realpath($parent)) === 0) { + return true; + } + return false; + } + + /** + * @brief 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; + } + + /** + * @brief replaces a copy of string delimited by the start and (optionally) length parameters with the string given in replacement. + * + * @param $string + * @param string $replacement The replacement string. + * @param int $start If start is positive, the replacing will begin at the start'th offset into string. If start is negative, the replacing will begin at the start'th character from the end of string. + * @param int $length Length of the part to be replaced + * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8 + * @internal param string $input The input string. .Opposite to the PHP build-in function does not accept an array. + * @return string + */ + public static function mb_substr_replace($string, $replacement, $start, $length = null, $encoding = 'UTF-8') { + $start = intval($start); + $length = intval($length); + $string = mb_substr($string, 0, $start, $encoding) . + $replacement . + mb_substr($string, $start + $length, mb_strlen($string, 'UTF-8') - $start, $encoding); + + return $string; + } + + /** + * @brief Replace all occurrences of the search string with the replacement string + * + * @param string $search The value being searched for, otherwise known as the needle. + * @param string $replace The replacement + * @param string $subject The string or array being searched and replaced on, otherwise known as the haystack. + * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8 + * @param int $count If passed, this will be set to the number of replacements performed. + * @return string + * + */ + public static function mb_str_replace($search, $replace, $subject, $encoding = 'UTF-8', &$count = null) { + $offset = -1; + $length = mb_strlen($search, $encoding); + while (($i = mb_strrpos($subject, $search, $offset, $encoding)) !== false) { + $subject = OC_Helper::mb_substr_replace($subject, $replace, $i, $length); + $offset = $i - mb_strlen($subject, $encoding); + $count++; + } + return $subject; + } + + /** + * @brief 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; + } + + /** + * @brief calculates the maximum upload size respecting system settings, free space and user quota + * + * @param $dir the current folder where the user currently operates + * @return number of bytes representing + */ + public static function maxUploadFilesize($dir) { + $upload_max_filesize = OCP\Util::computerFileSize(ini_get('upload_max_filesize')); + $post_max_size = OCP\Util::computerFileSize(ini_get('post_max_size')); + $freeSpace = \OC\Files\Filesystem::free_space($dir); + if ((int)$upload_max_filesize === 0 and (int)$post_max_size === 0) { + $maxUploadFilesize = \OC\Files\SPACE_UNLIMITED; + } elseif ((int)$upload_max_filesize === 0 or (int)$post_max_size === 0) { + $maxUploadFilesize = max($upload_max_filesize, $post_max_size); //only the non 0 value counts + } else { + $maxUploadFilesize = min($upload_max_filesize, $post_max_size); + } + + if ($freeSpace !== \OC\Files\SPACE_UNKNOWN) { + $freeSpace = max($freeSpace, 0); + + return min($maxUploadFilesize, $freeSpace); + } else { + return $maxUploadFilesize; + } + } + + /** + * 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; + } + $disabled = explode(', ', ini_get('disable_functions')); + if (in_array($function_name, $disabled)) { + return false; + } + $disabled = explode(', ', ini_get('suhosin.executor.func.blacklist')); + if (in_array($function_name, $disabled)) { + return false; + } + return true; + } + + /** + * Calculate the disc space for the given path + * + * @param string $path + * @return array + */ + public static function getStorageInfo($path) { + $rootInfo = \OC\Files\Filesystem::getFileInfo($path); + $used = $rootInfo['size']; + if ($used < 0) { + $used = 0; + } + $free = \OC\Files\Filesystem::free_space($path); + if ($free >= 0) { + $total = $free + $used; + } else { + $total = $free; //either unknown or unlimited + } + if ($total > 0) { + // 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); + } +} diff --git a/lib/private/hintexception.php b/lib/private/hintexception.php new file mode 100644 index 00000000000..3934ae2a4c2 --- /dev/null +++ b/lib/private/hintexception.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC; + +class HintException extends \Exception { + + private $hint; + + public function __construct($message, $hint = '', $code = 0, Exception $previous = null) { + $this->hint = $hint; + parent::__construct($message, $code, $previous); + } + + public function __toString() { + return __CLASS__ . ": [{$this->code}]: {$this->message} ({$this->hint})\n"; + } + + public function getHint() { + return $this->hint; + } +} diff --git a/lib/private/hook.php b/lib/private/hook.php new file mode 100644 index 00000000000..8516cf0dcff --- /dev/null +++ b/lib/private/hook.php @@ -0,0 +1,100 @@ +<?php + +/** + * This class manages the hooks. It basically provides two functions: adding + * slots and emitting signals. + */ +class OC_Hook{ + static private $registered = array(); + + /** + * @brief connects a function to a hook + * @param string $signalclass class name of emitter + * @param string $signalname name of signal + * @param string $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(); + } + + // Connect the hook handler to the requested emitter + self::$registered[$signalclass][$signalname][] = array( + "class" => $slotclass, + "name" => $slotname + ); + + // No chance for failure ;-) + return true; + } + + /** + * @brief emits a signal + * @param string $signalclass class name of emitter + * @param string $signalname name of signal + * @param array $params defautl: array() array with additional data + * @return bool, true if slots exists or false if not + * + * Emits a signal. To get data from the slot use references! + * + * TODO: write example + */ + static public function emit( $signalclass, $signalname, $params = array()) { + + // 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){ + OC_Log::write('hook', + 'error while running hook (' . $i["class"] . '::' . $i["name"] . '): '.$e->getMessage(), + OC_Log::ERROR); + } + } + + // return true + 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(); + } + } +} diff --git a/lib/private/hooks/basicemitter.php b/lib/private/hooks/basicemitter.php new file mode 100644 index 00000000000..9ffe1af2314 --- /dev/null +++ b/lib/private/hooks/basicemitter.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Hooks; + +abstract class BasicEmitter implements Emitter { + + /** + * @var (callable[])[] $listeners + */ + protected $listeners = array(); + + /** + * @param string $scope + * @param string $method + * @param callable $callback + */ + public function listen($scope, $method, $callback) { + $eventName = $scope . '::' . $method; + if (!isset($this->listeners[$eventName])) { + $this->listeners[$eventName] = array(); + } + if (array_search($callback, $this->listeners[$eventName]) === false) { + $this->listeners[$eventName][] = $callback; + } + } + + /** + * @param string $scope optional + * @param string $method optional + * @param callable $callback optional + */ + public function removeListener($scope = null, $method = null, $callback = null) { + $names = array(); + $allNames = array_keys($this->listeners); + if ($scope and $method) { + $name = $scope . '::' . $method; + if (isset($this->listeners[$name])) { + $names[] = $name; + } + } elseif ($scope) { + foreach ($allNames as $name) { + $parts = explode('::', $name, 2); + if ($parts[0] == $scope) { + $names[] = $name; + } + } + } elseif ($method) { + foreach ($allNames as $name) { + $parts = explode('::', $name, 2); + if ($parts[1] == $method) { + $names[] = $name; + } + } + } else { + $names = $allNames; + } + + foreach ($names as $name) { + if ($callback) { + $index = array_search($callback, $this->listeners[$name]); + if ($index !== false) { + unset($this->listeners[$name][$index]); + } + } else { + $this->listeners[$name] = array(); + } + } + } + + /** + * @param string $scope + * @param string $method + * @param array $arguments optional + */ + protected function emit($scope, $method, $arguments = array()) { + $eventName = $scope . '::' . $method; + if (isset($this->listeners[$eventName])) { + foreach ($this->listeners[$eventName] as $callback) { + call_user_func_array($callback, $arguments); + } + } + } +} diff --git a/lib/private/hooks/emitter.php b/lib/private/hooks/emitter.php new file mode 100644 index 00000000000..8e9074bad67 --- /dev/null +++ b/lib/private/hooks/emitter.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Hooks; + +/** + * Class Emitter + * + * interface for all classes that are able to emit events + * + * @package OC\Hooks + */ +interface Emitter { + /** + * @param string $scope + * @param string $method + * @param callable $callback + */ + public function listen($scope, $method, $callback); + + /** + * @param string $scope optional + * @param string $method optional + * @param callable $callback optional + */ + public function removeListener($scope = null, $method = null, $callback = null); +} diff --git a/lib/private/hooks/forwardingemitter.php b/lib/private/hooks/forwardingemitter.php new file mode 100644 index 00000000000..1aacc4012e0 --- /dev/null +++ b/lib/private/hooks/forwardingemitter.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Hooks; + +/** + * Class ForwardingEmitter + * + * allows forwarding all listen calls to other emitters + * + * @package OC\Hooks + */ +abstract class ForwardingEmitter extends BasicEmitter { + /** + * @var \OC\Hooks\Emitter[] array + */ + private $forwardEmitters = array(); + + /** + * @param string $scope + * @param string $method + * @param callable $callback + */ + public function listen($scope, $method, $callback) { + parent::listen($scope, $method, $callback); + foreach ($this->forwardEmitters as $emitter) { + $emitter->listen($scope, $method, $callback); + } + } + + /** + * @param \OC\Hooks\Emitter $emitter + */ + protected function forward($emitter) { + $this->forwardEmitters[] = $emitter; + + //forward all previously connected hooks + foreach ($this->listeners as $key => $listeners) { + list($scope, $method) = explode('::', $key, 2); + foreach ($listeners as $listener) { + $emitter->listen($scope, $method, $listener); + } + } + } +} diff --git a/lib/private/hooks/legacyemitter.php b/lib/private/hooks/legacyemitter.php new file mode 100644 index 00000000000..a2d16ace9a7 --- /dev/null +++ b/lib/private/hooks/legacyemitter.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Hooks; + +abstract class LegacyEmitter extends BasicEmitter { + protected function emit($scope, $method, $arguments = array()) { + \OC_Hook::emit($scope, $method, $arguments); + parent::emit($scope, $method, $arguments); + } +} diff --git a/lib/private/hooks/publicemitter.php b/lib/private/hooks/publicemitter.php new file mode 100644 index 00000000000..e2371713ac3 --- /dev/null +++ b/lib/private/hooks/publicemitter.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Hooks; + +class PublicEmitter extends BasicEmitter { + /** + * @param string $scope + * @param string $method + * @param array $arguments optional + */ + public function emit($scope, $method, $arguments = array()) { + parent::emit($scope, $method, $arguments); + } +} diff --git a/lib/private/image.php b/lib/private/image.php new file mode 100644 index 00000000000..7761a3c7737 --- /dev/null +++ b/lib/private/image.php @@ -0,0 +1,1020 @@ +<?php + +/** +* ownCloud +* +* @author Thomas Tanghus +* @copyright 2011 Thomas Tanghus <thomas@tanghus.net> +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ +/** + * Class for basic image manipulation + */ +class OC_Image { + protected $resource = false; // tmp resource. + protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident. + protected $mimeType = "image/png"; // Default to png + protected $bitDepth = 24; + protected $filePath = null; + + private $fileInfo; + + /** + * @brief Get mime type for an image file. + * @param $filepath The path to a local image file. + * @returns 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 (filesize($filePath) > 11) { + $imageType = exif_imagetype($filePath); + } + else { + $imageType = false; + } + return $imageType ? image_type_to_mime_type($imageType) : ''; + } + + /** + * @brief Constructor. + * @param $imageref The path to a local file, a base64 encoded string or a resource created by an imagecreate* function. + * @returns bool False on error + */ + public function __construct($imageRef = null) { + //OC_Log::write('core',__METHOD__.'(): start', OC_Log::DEBUG); + if(!extension_loaded('gd') || !function_exists('gd_info')) { + OC_Log::write('core', __METHOD__.'(): GD module not installed', OC_Log::ERROR); + return false; + } + + if (\OC_Util::fileInfoLoaded()) { + $this->fileInfo = new finfo(FILEINFO_MIME_TYPE); + } + + if(!is_null($imageRef)) { + $this->load($imageRef); + } + } + + /** + * @brief Determine whether the object contains an image resource. + * @returns bool + */ + public function valid() { // apparently you can't name a method 'empty'... + return is_resource($this->resource); + } + + /** + * @brief Returns the MIME type of the image or an empty string if no image is loaded. + * @returns int + */ + public function mimeType() { + return $this->valid() ? $this->mimeType : ''; + } + + /** + * @brief Returns the width of the image or -1 if no image is loaded. + * @returns int + */ + public function width() { + return $this->valid() ? imagesx($this->resource) : -1; + } + + /** + * @brief Returns the height of the image or -1 if no image is loaded. + * @returns int + */ + public function height() { + return $this->valid() ? imagesy($this->resource) : -1; + } + + /** + * @brief Returns the width when the image orientation is top-left. + * @returns int + */ + public function widthTopLeft() { + $o = $this->getOrientation(); + OC_Log::write('core', 'OC_Image->widthTopLeft() Orientation: '.$o, OC_Log::DEBUG); + switch($o) { + case -1: + case 1: + case 2: // Not tested + case 3: + case 4: // Not tested + return $this->width(); + break; + case 5: // Not tested + case 6: + case 7: // Not tested + case 8: + return $this->height(); + break; + } + return $this->width(); + } + + /** + * @brief Returns the height when the image orientation is top-left. + * @returns int + */ + public function heightTopLeft() { + $o = $this->getOrientation(); + OC_Log::write('core', 'OC_Image->heightTopLeft() Orientation: '.$o, OC_Log::DEBUG); + switch($o) { + case -1: + case 1: + case 2: // Not tested + case 3: + case 4: // Not tested + return $this->height(); + break; + case 5: // Not tested + case 6: + case 7: // Not tested + case 8: + return $this->width(); + break; + } + return $this->height(); + } + + /** + * @brief Outputs the image. + * @returns bool + */ + public function show() { + header('Content-Type: '.$this->mimeType()); + return $this->_output(); + } + + /** + * @brief Saves the image. + * @returns bool + */ + + public function save($filePath=null) { + if($filePath === null && $this->filePath === null) { + OC_Log::write('core', __METHOD__.'(): called with no path.', OC_Log::ERROR); + return false; + } elseif($filePath === null && $this->filePath !== null) { + $filePath = $this->filePath; + } + return $this->_output($filePath); + } + + /** + * @brief Outputs/saves the image. + */ + private function _output($filePath=null) { + if($filePath) { + if (!file_exists(dirname($filePath))) + mkdir(dirname($filePath), 0777, true); + if(!is_writable(dirname($filePath))) { + OC_Log::write('core', + __METHOD__.'(): Directory \''.dirname($filePath).'\' is not writable.', + OC_Log::ERROR); + return false; + } elseif(is_writable(dirname($filePath)) && file_exists($filePath) && !is_writable($filePath)) { + OC_Log::write('core', __METHOD__.'(): File \''.$filePath.'\' is not writable.', OC_Log::ERROR); + return false; + } + } + if (!$this->valid()) { + return false; + } + + $retVal = false; + switch($this->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: + $retVal = imagexbm($this->resource, $filePath); + 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; + } + + /** + * @brief Prints the image when called as $image(). + */ + public function __invoke() { + return $this->show(); + } + + /** + * @returns Returns the image resource in any. + */ + public function resource() { + return $this->resource; + } + + /** + * @returns Returns the raw image data. + */ + function data() { + 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); + OC_Log::write('core', 'OC_Image->data. Couldn\'t guess mimetype, defaulting to png', OC_Log::INFO); + break; + } + if (!$res) { + OC_Log::write('core', 'OC_Image->data. Error getting image data.', OC_Log::ERROR); + } + return ob_get_clean(); + } + + /** + * @returns Returns a base64 encoded string suitable for embedding in a VCard. + */ + function __toString() { + return base64_encode($this->data()); + } + + /** + * (I'm open for suggestions on better method name ;) + * @brief Get the orientation based on EXIF data. + * @returns The orientation or -1 if no EXIF data is available. + */ + public function getOrientation() { + if(!is_callable('exif_read_data')) { + OC_Log::write('core', 'OC_Image->fixOrientation() Exif module not enabled.', OC_Log::DEBUG); + return -1; + } + if(!$this->valid()) { + OC_Log::write('core', 'OC_Image->fixOrientation() No image loaded.', OC_Log::DEBUG); + return -1; + } + if(is_null($this->filePath) || !is_readable($this->filePath)) { + OC_Log::write('core', 'OC_Image->fixOrientation() No readable file path set.', OC_Log::DEBUG); + 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 ;) + * @brief Fixes orientation based on EXIF data. + * @returns bool. + */ + public function fixOrientation() { + $o = $this->getOrientation(); + OC_Log::write('core', 'OC_Image->fixOrientation() Orientation: '.$o, OC_Log::DEBUG); + $rotate = 0; + $flip = false; + switch($o) { + case -1: + return false; //Nothing to fix + break; + case 1: + $rotate = 0; + $flip = false; + break; + case 2: // Not tested + $rotate = 0; + $flip = true; + break; + case 3: + $rotate = 180; + $flip = false; + break; + case 4: // Not tested + $rotate = 180; + $flip = true; + break; + case 5: // Not tested + $rotate = 90; + $flip = true; + break; + case 6: + //$rotate = 90; + $rotate = 270; + $flip = false; + break; + case 7: // Not tested + $rotate = 270; + $flip = true; + break; + case 8: + $rotate = 90; + $flip = false; + break; + } + if($rotate) { + $res = imagerotate($this->resource, $rotate, -1); + if($res) { + if(imagealphablending($res, true)) { + if(imagesavealpha($res, true)) { + imagedestroy($this->resource); + $this->resource = $res; + return true; + } else { + OC_Log::write('core', 'OC_Image->fixOrientation() Error during alphasaving.', OC_Log::DEBUG); + return false; + } + } else { + OC_Log::write('core', 'OC_Image->fixOrientation() Error during alphablending.', OC_Log::DEBUG); + return false; + } + } else { + OC_Log::write('core', 'OC_Image->fixOrientation() Error during oriention fixing.', OC_Log::DEBUG); + return false; + } + } + } + + /** + * @brief Loads an image from a local file, a base64 encoded string or a resource created by an imagecreate* function. + * @param $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 ). + * @returns 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->loadFromFile($imageRef) !== false) { + return $this->resource; + } elseif($this->loadFromBase64($imageRef) !== false) { + return $this->resource; + } elseif($this->loadFromData($imageRef) !== false) { + return $this->resource; + } else { + OC_Log::write('core', __METHOD__.'(): couldn\'t load anything. Giving up!', OC_Log::DEBUG); + return false; + } + } + + /** + * @brief 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 $handle + * @returns An image resource or false on error + */ + public function loadFromFileHandle($handle) { + OC_Log::write('core', __METHOD__.'(): Trying', OC_Log::DEBUG); + $contents = stream_get_contents($handle); + if($this->loadFromData($contents)) { + return $this->resource; + } + } + + /** + * @brief Loads an image from a local file. + * @param $imageref The path to a local file. + * @returns 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)) { + // Debug output disabled because this method is tried before loadFromBase64? + OC_Log::write('core', 'OC_Image->loadFromFile, couldn\'t load: '.$imagePath, OC_Log::DEBUG); + return false; + } + $iType = exif_imagetype($imagePath); + switch ($iType) { + case IMAGETYPE_GIF: + if (imagetypes() & IMG_GIF) { + $this->resource = imagecreatefromgif($imagePath); + } else { + OC_Log::write('core', + 'OC_Image->loadFromFile, GIF images not supported: '.$imagePath, + OC_Log::DEBUG); + } + break; + case IMAGETYPE_JPEG: + if (imagetypes() & IMG_JPG) { + $this->resource = imagecreatefromjpeg($imagePath); + } else { + OC_Log::write('core', + 'OC_Image->loadFromFile, JPG images not supported: '.$imagePath, + OC_Log::DEBUG); + } + break; + case IMAGETYPE_PNG: + if (imagetypes() & IMG_PNG) { + $this->resource = imagecreatefrompng($imagePath); + } else { + OC_Log::write('core', + 'OC_Image->loadFromFile, PNG images not supported: '.$imagePath, + OC_Log::DEBUG); + } + break; + case IMAGETYPE_XBM: + if (imagetypes() & IMG_XPM) { + $this->resource = imagecreatefromxbm($imagePath); + } else { + OC_Log::write('core', + 'OC_Image->loadFromFile, XBM/XPM images not supported: '.$imagePath, + OC_Log::DEBUG); + } + break; + case IMAGETYPE_WBMP: + if (imagetypes() & IMG_WBMP) { + $this->resource = imagecreatefromwbmp($imagePath); + } else { + OC_Log::write('core', + 'OC_Image->loadFromFile, WBMP images not supported: '.$imagePath, + OC_Log::DEBUG); + } + 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; + OC_Log::write('core', 'OC_Image->loadFromFile, Default', OC_Log::DEBUG); + break; + } + if($this->valid()) { + $this->imageType = $iType; + $this->mimeType = image_type_to_mime_type($iType); + $this->filePath = $imagePath; + } + return $this->resource; + } + + /** + * @brief Loads an image from a string of data. + * @param $str A string of image data as read from a file. + * @returns 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) { + OC_Log::write('core', 'OC_Image->loadFromData, couldn\'t load', OC_Log::DEBUG); + return false; + } + return $this->resource; + } + + /** + * @brief Loads an image from a base64 encoded string. + * @param $str A string base64 encoded string of image data. + * @returns 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) { + OC_Log::write('core', 'OC_Image->loadFromBase64, couldn\'t load', OC_Log::DEBUG); + 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 <p> + * Path to the BMP image. + * </p> + * @return resource an image resource identifier on success, <b>FALSE</b> on errors. + */ + private function imagecreatefrombmp($fileName) { + if (!($fh = fopen($fileName, 'rb'))) { + trigger_error('imagecreatefrombmp: Can not open ' . $fileName, E_USER_WARNING); + return false; + } + // read file header + $meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14)); + // check for bitmap + if ($meta['type'] != 19778) { + trigger_error('imagecreatefrombmp: ' . $fileName . ' is not a bitmap!', E_USER_WARNING); + 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) { + trigger_error('imagecreatefrombmp: Can not obtain filesize of ' . $filename . '!', E_USER_WARNING); + 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']); + $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))) { + trigger_error($error, E_USER_WARNING); + return $im; + } + $color = unpack('V', $part . $vide); + break; + case 16: + if (!($part = substr($data, $p, 2))) { + trigger_error($error, E_USER_WARNING); + 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: + trigger_error('imagecreatefrombmp: ' + . $fileName . ' has ' . $meta['bits'] . ' bits and this is not supported!', + E_USER_WARNING); + return false; + } + imagesetpixel($im, $x, $y, $color[1]); + $x++; + $p += $meta['bytes']; + } + $y--; + $p += $meta['decal']; + } + fclose($fh); + return $im; + } + + /** + * @brief Resizes the image preserving ratio. + * @param $maxsize The maximum size of either the width or height. + * @returns bool + */ + public function resize($maxSize) { + if(!$this->valid()) { + OC_Log::write('core', __METHOD__.'(): No image loaded', OC_Log::ERROR); + 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; + } + + public function preciseResize($width, $height) { + if (!$this->valid()) { + OC_Log::write('core', __METHOD__.'(): No image loaded', OC_Log::ERROR); + return false; + } + $widthOrig=imageSX($this->resource); + $heightOrig=imageSY($this->resource); + $process = imagecreatetruecolor($width, $height); + + if ($process == false) { + OC_Log::write('core', __METHOD__.'(): Error creating true color image', OC_Log::ERROR); + 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) { + OC_Log::write('core', __METHOD__.'(): Error resampling process image '.$width.'x'.$height, OC_Log::ERROR); + imagedestroy($process); + return false; + } + imagedestroy($this->resource); + $this->resource = $process; + return true; + } + + /** + * @brief Crops the image to the middle square. If the image is already square it just returns. + * @param int maximum size for the result (optional) + * @returns bool for success or failure + */ + public function centerCrop($size=0) { + if(!$this->valid()) { + OC_Log::write('core', 'OC_Image->centerCrop, No image loaded', OC_Log::ERROR); + 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) { + OC_Log::write('core', 'OC_Image->centerCrop. Error creating true color image', OC_Log::ERROR); + 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) { + OC_Log::write('core', + 'OC_Image->centerCrop. Error resampling process image '.$width.'x'.$height, + OC_Log::ERROR); + imagedestroy($process); + return false; + } + imagedestroy($this->resource); + $this->resource = $process; + return true; + } + + /** + * @brief Crops the image from point $x$y with dimension $wx$h. + * @param $x Horizontal position + * @param $y Vertical position + * @param $w Width + * @param $h Height + * @returns bool for success or failure + */ + public function crop($x, $y, $w, $h) { + if(!$this->valid()) { + OC_Log::write('core', __METHOD__.'(): No image loaded', OC_Log::ERROR); + return false; + } + $process = imagecreatetruecolor($w, $h); + if ($process == false) { + OC_Log::write('core', __METHOD__.'(): Error creating true color image', OC_Log::ERROR); + imagedestroy($process); + return false; + } + imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h); + if ($process == false) { + OC_Log::write('core', __METHOD__.'(): Error resampling process image '.$w.'x'.$h, OC_Log::ERROR); + imagedestroy($process); + return false; + } + imagedestroy($this->resource); + $this->resource = $process; + return true; + } + + /** + * @brief Resizes the image to fit within a boundry while preserving ratio. + * @param $maxWidth + * @param $maxHeight + * @returns bool + */ + public function fitIn($maxWidth, $maxHeight) { + if(!$this->valid()) { + OC_Log::write('core', __METHOD__.'(): No image loaded', OC_Log::ERROR); + 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; + } + + 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 <legendsky@hotmail.com> + * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm + * @author mgutt <marc@gutt.it> + * @version 1.00 + * @param resource $image + * @param string $filename [optional] <p>The path to save the file to.</p> + * @param int $bit [optional] <p>Bit depth, (default is 24).</p> + * @param int $compression [optional] + * @return bool <b>TRUE</b> on success or <b>FALSE</b> 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($same_num) . 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 new file mode 100644 index 00000000000..e082c7eeee9 --- /dev/null +++ b/lib/private/installer.php @@ -0,0 +1,481 @@ +<?php +/** + * ownCloud + * + * @author Robin Appelman + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * This class provides the functionality needed to install, update and remove plugins/apps + */ +class OC_Installer{ + /** + * @brief Installs an app + * @param $data array with all information + * @throws \Exception + * @returns integer + * + * 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. + */ + public static function installApp( $data = array()) { + $l = \OC_L10N::get('lib'); + + if(!isset($data['source'])) { + throw new \Exception($l->t("No source specified when installing app")); + } + + //download the file if necesary + if($data['source']=='http') { + $path=OC_Helper::tmpFile(); + if(!isset($data['href'])) { + throw new \Exception($l->t("No href specified when installing app from http")); + } + copy($data['href'], $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_Helper::getMimeType($path); + if($mime=='application/zip') { + rename($path, $path.'.zip'); + $path.='.zip'; + }elseif($mime=='application/x-gzip') { + rename($path, $path.'.tgz'); + $path.='.tgz'; + }else{ + throw new \Exception($l->t("Archives of type %s are not supported", array($mime))); + } + + //extract the archive in a temporary folder + $extractDir=OC_Helper::tmpFolder(); + 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")); + } + + //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); + // check the code for not allowed calls + if(!OC_Installer::checkCode($info['id'], $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( + !isset($info['require']) + or !OC_App::isAppVersionCompatible(OC_Util::getVersion(), $info['require']) + ) { + 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(isset($info['shipped']) and ($info['shipped']=='true')) { + OC_Helper::rmdirr($extractDir); + throw new \Exception($l->t("App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps")); + } + + // check if the ocs version is the same as the version in info.xml/version + if(!isset($info['version']) or ($info['version']<>$data['appdata']['version'])) { + OC_Helper::rmdirr($extractDir); + throw new \Exception($l->t("App can't be installed because the version in info.xml/version is not the same as the version reported from the app store")); + } + + $basedir=OC_App::getInstallPath().'/'.$info['id']; + //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(isset($data['pretent']) and $data['pretent']==true) { + 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))); + } + OC_Helper::copyr($extractDir, $basedir); + + //remove temporary files + OC_Helper::rmdirr($extractDir); + + //install the database + if(is_file($basedir.'/appinfo/database.xml')) { + OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml'); + } + + //run appinfo/install.php + if((!isset($data['noinstall']) or $data['noinstall']==false) and file_exists($basedir.'/appinfo/install.php')) { + include $basedir.'/appinfo/install.php'; + } + + //set the installed version + OC_Appconfig::setValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'])); + OC_Appconfig::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 $app app + * @returns true/false + * + * Checks whether or not an app is installed, i.e. registered in apps table. + */ + public static function isInstalled( $app ) { + + if( null == OC_Appconfig::getValue( $app, "installed_version" )) { + return false; + } + + return true; + } + + /** + * @brief Update an application + * @param $data array with all information + * + * 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 + * - 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_Appconfig::getValue($appid, 'installed_version')" + */ + public static function updateApp( $app ) { + $ocsid=OC_Appconfig::getValue( $app, 'ocsid'); + OC_App::disable($app); + OC_App::enable($ocsid); + return(true); + } + + /** + * @brief Check if an update for the app is available + * @param $name name of the application + * @returns empty string is no update available or the version number of the update + * + * The function will check if an update for a version is available + */ + public static function isUpdateAvailable( $app ) { + $ocsid=OC_Appconfig::getValue( $app, 'ocsid', ''); + + if($ocsid<>'') { + + $ocsdata=OC_OCSClient::getApplication($ocsid); + $ocsversion= (string) $ocsdata['version']; + $currentversion=OC_App::getAppVersion($app); + if($ocsversion<>$currentversion) { + return($ocsversion); + + }else{ + return(''); + } + + }else{ + return(''); + } + + } + + /** + * @brief Check if app is already downloaded + * @param $name name of the application to remove + * @returns true/false + * + * The function will check if the app is already downloaded in the apps repository + */ + public static function isDownloaded( $name ) { + + $downloaded=false; + foreach(OC::$APPSROOTS as $dir) { + if(is_dir($dir['path'].'/'.$name)) $downloaded=true; + } + return($downloaded); + } + + /** + * @brief Removes an app + * @param $name name of the application to remove + * @param $options array with options + * @returns true/false + * + * 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); + + }else{ + OC_Log::write('core', 'can\'t remove app '.$name.'. It is not installed.', OC_Log::ERROR); + + } + + } + + /** + * @brief Installs shipped apps + * + * This function installs all apps found in the 'apps' directory that should be enabled by default; + */ + public static function installShippedApps() { + 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/app.php" )) { + if(!OC_Installer::isInstalled($filename)) { + $info=OC_App::getAppInfo($filename); + $enabled = isset($info['default_enable']); + if( $enabled ) { + OC_Installer::installShippedApp($filename); + OC_Appconfig::setValue($filename, 'enabled', 'yes'); + } + } + } + } + } + closedir( $dir ); + } + } + } + + /** + * install an app already placed in the app folder + * @param string $app id of the app to install + * @returns array see OC_App::getAppInfo + */ + public static function installShippedApp($app) { + //install the database + if(is_file(OC_App::getAppPath($app)."/appinfo/database.xml")) { + OC_DB::createDbFromStructure(OC_App::getAppPath($app)."/appinfo/database.xml"); + } + + //run appinfo/install.php + if(is_file(OC_App::getAppPath($app)."/appinfo/install.php")) { + include OC_App::getAppPath($app)."/appinfo/install.php"; + } + $info=OC_App::getAppInfo($app); + OC_Appconfig::setValue($app, 'installed_version', OC_App::getAppVersion($app)); + + //set remote/public handelers + foreach($info['remote'] as $name=>$path) { + OCP\CONFIG::setAppValue('core', 'remote_'.$name, $app.'/'.$path); + } + foreach($info['public'] as $name=>$path) { + OCP\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 + * @returns true for app is o.k. and false for app is not o.k. + */ + public static function checkCode($appname, $folder) { + + $blacklist=array( + 'exec(', + 'eval(', + // more evil pattern will go here later + + // classes replaced by the public api + 'OC_API::', + 'OC_App::', + 'OC_AppConfig::', + 'OC_Avatar', + 'OC_BackgroundJob::', + 'OC_Config::', + 'OC_DB::', + 'OC_Files::', + 'OC_Helper::', + 'OC_Hook::', + 'OC_Image::', + 'OC_JSON::', + 'OC_L10N::', + 'OC_Log::', + 'OC_Mail::', + 'OC_Preferences::', + 'OC_Request::', + 'OC_Response::', + 'OC_Template::', + 'OC_User::', + 'OC_Util::', + ); + + // is the code checker enabled? + if(OC_Config::getValue('appcodechecker', false)) { + + // check if grep is installed + $grep = exec('which grep'); + if($grep=='') { + OC_Log::write('core', + 'grep not installed. So checking the code of the app "'.$appname.'" was not possible', + OC_Log::ERROR); + return true; + } + + // iterate the bad patterns + foreach($blacklist as $bl) { + $cmd = 'grep -ri '.escapeshellarg($bl).' '.$folder.''; + $result = exec($cmd); + // bad pattern found + if($result<>'') { + OC_Log::write('core', + 'App "'.$appname.'" is using a not allowed call "'.$bl.'". Installation refused.', + OC_Log::ERROR); + return false; + } + } + return true; + + }else{ + return true; + } + } +} diff --git a/lib/private/json.php b/lib/private/json.php new file mode 100644 index 00000000000..6ba0b13806b --- /dev/null +++ b/lib/private/json.php @@ -0,0 +1,115 @@ +<?php +/** + * Copyright (c) 2011 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_JSON{ + static protected $send_content_type_header = false; + /** + * set Content-Type header to jsonrequest + */ + 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 + */ + public static function checkAppEnabled($app) { + if( !OC_App::isEnabled($app)) { + $l = OC_L10N::get('lib'); + self::error(array( 'data' => array( 'message' => $l->t('Application is not enabled') ))); + exit(); + } + } + + /** + * Check if the user is logged in, send json error msg if not + */ + public static function checkLoggedIn() { + if( !OC_User::isLoggedIn()) { + $l = OC_L10N::get('lib'); + self::error(array( 'data' => array( 'message' => $l->t('Authentication error') ))); + exit(); + } + } + + /** + * @brief Check an ajax get/post call if the request token is valid. + * @return json Error msg if not valid. + */ + public static function callCheck() { + if( !OC_Util::isCallRegistered()) { + $l = OC_L10N::get('lib'); + self::error(array( 'data' => array( 'message' => $l->t('Token expired. Please reload page.') ))); + exit(); + } + } + + /** + * Check if the user is a admin, send json error msg if not + */ + public static function checkAdminUser() { + if( !OC_User::isAdminUser(OC_User::getUser())) { + $l = OC_L10N::get('lib'); + self::error(array( 'data' => array( 'message' => $l->t('Authentication error') ))); + exit(); + } + } + + /** + * Check if the user is a subadmin, send json error msg if not + */ + public static function checkSubAdminUser() { + if(!OC_SubAdmin::isSubAdmin(OC_User::getUser())) { + $l = OC_L10N::get('lib'); + self::error(array( 'data' => array( 'message' => $l->t('Authentication error') ))); + exit(); + } + } + + /** + * Send json error msg + */ + public static function error($data = array()) { + $data['status'] = 'error'; + self::encodedPrint($data); + } + + /** + * Send json success msg + */ + 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 + */ + public static function encodedPrint($data, $setContentType=true) { + // Disable mimesniffing, don't move this to setContentTypeHeader! + header( 'X-Content-Type-Options: nosniff' ); + if($setContentType) { + self::setContentTypeHeader(); + } + array_walk_recursive($data, array('OC_JSON', 'to_string')); + echo json_encode($data); + } +} diff --git a/lib/private/l10n.php b/lib/private/l10n.php new file mode 100644 index 00000000000..f93443b886a --- /dev/null +++ b/lib/private/l10n.php @@ -0,0 +1,546 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @author Jakob Sack + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * @copyright 2013 Jakob Sack + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * This class is for i18n and l10n + */ +class OC_L10N { + /** + * cached instances + */ + protected static $instances=array(); + + /** + * cache + */ + protected static $cache = array(); + + /** + * The best language + */ + protected static $language = ''; + + /** + * App of this object + */ + protected $app; + + /** + * Language of this object + */ + protected $lang; + + /** + * Translations + */ + private $translations = array(); + + /** + * Plural forms (string) + */ + private $plural_form_string = 'nplurals=2; plural=(n != 1);'; + + /** + * Plural forms (function) + */ + private $plural_form_function = null; + + /** + * Localization + */ + private $localizations = array( + 'jsdate' => 'dd.mm.yy', + 'date' => '%d.%m.%Y', + 'datetime' => '%d.%m.%Y %H:%M:%S', + 'time' => '%H:%M:%S', + 'firstday' => 0); + + /** + * get an L10N instance + * @param $app string + * @param $lang string|null + * @return OC_L10N + */ + public static function get($app, $lang=null) { + if(is_null($lang)) { + if(!isset(self::$instances[$app])) { + self::$instances[$app]=new OC_L10N($app); + } + return self::$instances[$app]; + }else{ + return new OC_L10N($app, $lang); + } + } + + /** + * @brief The constructor + * @param $app string app requesting l10n + * @param $lang string default: null Language + * @returns OC_L10N-Object + * + * If language is not set, the constructor tries to find the right + * language. + */ + public function __construct($app, $lang = null) { + $this->app = $app; + $this->lang = $lang; + } + + public function load($transFile) { + $this->app = true; + include $transFile; + if(isset($TRANSLATIONS) && is_array($TRANSLATIONS)) { + $this->translations = $TRANSLATIONS; + } + if(isset($PLURAL_FORMS)) { + $this->plural_form_string = $PLURAL_FORMS; + } + } + + protected function init() { + if ($this->app === true) { + return; + } + $app = OC_App::cleanAppId($this->app); + $lang = $this->lang; + $this->app = true; + // Find the right language + if(is_null($lang) || $lang == '') { + $lang = self::findLanguage($app); + } + + // Use cache if possible + if(array_key_exists($app.'::'.$lang, self::$cache)) { + + $this->translations = self::$cache[$app.'::'.$lang]['t']; + $this->localizations = self::$cache[$app.'::'.$lang]['l']; + } + else{ + $i18ndir = self::findI18nDir($app); + // Localization is in /l10n, Texts are in $i18ndir + // (Just no need to define date/time format etc. twice) + if((OC_Helper::issubdirectory($i18ndir.$lang.'.php', OC_App::getAppPath($app).'/l10n/') + || OC_Helper::issubdirectory($i18ndir.$lang.'.php', OC::$SERVERROOT.'/core/l10n/') + || OC_Helper::issubdirectory($i18ndir.$lang.'.php', OC::$SERVERROOT.'/lib/l10n/') + || OC_Helper::issubdirectory($i18ndir.$lang.'.php', OC::$SERVERROOT.'/settings') + ) + && file_exists($i18ndir.$lang.'.php')) { + // Include the file, save the data from $CONFIG + $transFile = strip_tags($i18ndir).strip_tags($lang).'.php'; + include $transFile; + if(isset($TRANSLATIONS) && is_array($TRANSLATIONS)) { + $this->translations = $TRANSLATIONS; + //merge with translations from theme + $theme = OC_Config::getValue( "theme" ); + if (!is_null($theme)) { + $transFile = OC::$SERVERROOT.'/themes/'.$theme.substr($transFile, strlen(OC::$SERVERROOT)); + if (file_exists($transFile)) { + include $transFile; + if (isset($TRANSLATIONS) && is_array($TRANSLATIONS)) { + $this->translations = array_merge($this->translations, $TRANSLATIONS); + } + } + } + } + if(isset($PLURAL_FORMS)) { + $this->plural_form_string = $PLURAL_FORMS; + } + } + + if(file_exists(OC::$SERVERROOT.'/core/l10n/l10n-'.$lang.'.php')) { + // Include the file, save the data from $CONFIG + include OC::$SERVERROOT.'/core/l10n/l10n-'.$lang.'.php'; + if(isset($LOCALIZATIONS) && is_array($LOCALIZATIONS)) { + $this->localizations = array_merge($this->localizations, $LOCALIZATIONS); + } + } + + self::$cache[$app.'::'.$lang]['t'] = $this->translations; + self::$cache[$app.'::'.$lang]['l'] = $this->localizations; + } + } + + /** + * @brief Creates a function that The constructor + * + * If language is not set, the constructor tries to find the right + * language. + * + * Parts of the code is copied from Habari: + * https://github.com/habari/system/blob/master/classes/locale.php + * @param $string string + * @return string + */ + protected function createPluralFormFunction($string){ + if(preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $string, $matches)) { + // sanitize + $nplurals = preg_replace( '/[^0-9]/', '', $matches[1] ); + $plural = preg_replace( '#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2] ); + + $body = str_replace( + array( 'plural', 'n', '$n$plurals', ), + array( '$plural', '$n', '$nplurals', ), + 'nplurals='. $nplurals . '; plural=' . $plural + ); + + // add parents + // important since PHP's ternary evaluates from left to right + $body .= ';'; + $res = ''; + $p = 0; + for($i = 0; $i < strlen($body); $i++) { + $ch = $body[$i]; + switch ( $ch ) { + case '?': + $res .= ' ? ('; + $p++; + break; + case ':': + $res .= ') : ('; + break; + case ';': + $res .= str_repeat( ')', $p ) . ';'; + $p = 0; + break; + default: + $res .= $ch; + } + } + + $body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);'; + return create_function('$n', $body); + } + else { + // default: one plural form for all cases but n==1 (english) + return create_function( + '$n', + '$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);' + ); + } + } + + /** + * @brief Translating + * @param $text String The text we need a translation for + * @param array $parameters default:array() Parameters for sprintf + * @return \OC_L10N_String Translation or the same text + * + * Returns the translation. If no translation is found, $text will be + * returned. + */ + public function t($text, $parameters = array()) { + return new OC_L10N_String($this, $text, $parameters); + } + + /** + * @brief Translating + * @param $text_singular String the string to translate for exactly one object + * @param $text_plural String the string to translate for n objects + * @param $count Integer Number of objects + * @param array $parameters default:array() Parameters for sprintf + * @return \OC_L10N_String Translation or the same text + * + * Returns the translation. If no translation is found, $text will be + * returned. %n will be replaced with the number of objects. + * + * The correct plural is determined by the plural_forms-function + * provided by the po file. + * + */ + public function n($text_singular, $text_plural, $count, $parameters = array()) { + $this->init(); + $identifier = "_${text_singular}__${text_plural}_"; + if( array_key_exists($identifier, $this->translations)) { + return new OC_L10N_String( $this, $identifier, $parameters, $count ); + } + else{ + if($count === 1) { + return new OC_L10N_String($this, $text_singular, $parameters, $count); + } + else{ + return new OC_L10N_String($this, $text_plural, $parameters, $count); + } + } + } + + /** + * @brief Translating + * @param $textArray The text array we need a translation for + * @returns Translation or the same text + * + * Returns the translation. If no translation is found, $textArray will be + * returned. + * + * + * @deprecated deprecated since ownCloud version 5.0 + * This method will probably be removed with ownCloud 6.0 + * + * + */ + public function tA($textArray) { + OC_Log::write('core', 'DEPRECATED: the method tA is deprecated and will be removed soon.', OC_Log::WARN); + $result = array(); + foreach($textArray as $key => $text) { + $result[$key] = (string)$this->t($text); + } + return $result; + } + + /** + * @brief getTranslations + * @returns Fetch all translations + * + * Returns an associative array with all translations + */ + public function getTranslations() { + $this->init(); + return $this->translations; + } + + /** + * @brief getPluralFormString + * @returns string containing the gettext "Plural-Forms"-string + * + * Returns a string like "nplurals=2; plural=(n != 1);" + */ + public function getPluralFormString() { + $this->init(); + return $this->plural_form_string; + } + + /** + * @brief getPluralFormFunction + * @returns string the plural form function + * + * returned function accepts the argument $n + */ + public function getPluralFormFunction() { + $this->init(); + if(is_null($this->plural_form_function)) { + $this->plural_form_function = $this->createPluralFormFunction($this->plural_form_string); + } + return $this->plural_form_function; + } + + /** + * @brief get localizations + * @returns Fetch all localizations + * + * Returns an associative array with all localizations + */ + public function getLocalizations() { + $this->init(); + return $this->localizations; + } + + /** + * @brief Localization + * @param $type Type of localization + * @param $params parameters for this localization + * @returns String or false + * + * Returns the localized data. + * + * Implemented types: + * - date + * - Creates a date + * - l10n-field: date + * - params: timestamp (int/string) + * - datetime + * - Creates date and time + * - l10n-field: datetime + * - params: timestamp (int/string) + * - time + * - Creates a time + * - l10n-field: time + * - params: timestamp (int/string) + */ + public function l($type, $data) { + $this->init(); + switch($type) { + // If you add something don't forget to add it to $localizations + // at the top of the page + case 'date': + case 'datetime': + case 'time': + if($data instanceof DateTime) { + return $data->format($this->localizations[$type]); + } elseif(is_string($data) && !is_numeric($data)) { + $data = strtotime($data); + } + $locales = array(self::findLanguage()); + if (strlen($locales[0]) == 2) { + $locales[] = $locales[0].'_'.strtoupper($locales[0]); + } + setlocale(LC_TIME, $locales); + $format = $this->localizations[$type]; + // Check for Windows to find and replace the %e modifier correctly + if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { + $format = preg_replace('#(?<!%)((?:%%)*)%e#', '\1%#d', $format); + } + return strftime($format, $data); + break; + case 'firstday': + case 'jsdate': + return $this->localizations[$type]; + default: + return false; + } + } + + /** + * @brief Choose a language + * @param $texts Associative Array with possible strings + * @returns String + * + * $text is an array 'de' => 'hallo welt', 'en' => 'hello world', ... + * + * This function is useful to avoid loading thousands of files if only one + * simple string is needed, for example in appinfo.php + */ + public static function selectLanguage($text) { + $lang = self::findLanguage(array_keys($text)); + return $text[$lang]; + } + + /** + * @brief find the best language + * @param $app Array or string, details below + * @returns language + * + * If $app is an array, ownCloud assumes that these are the available + * languages. Otherwise ownCloud tries to find the files in the l10n + * folder. + * + * If nothing works it returns 'en' + */ + public static function findLanguage($app = null) { + if(!is_array($app) && self::$language != '') { + return self::$language; + } + + if(OC_User::getUser() && OC_Preferences::getValue(OC_User::getUser(), 'core', 'lang')) { + $lang = OC_Preferences::getValue(OC_User::getUser(), 'core', 'lang'); + self::$language = $lang; + if(is_array($app)) { + $available = $app; + $lang_exists = array_search($lang, $available) !== false; + } + else { + $lang_exists = self::languageExists($app, $lang); + } + if($lang_exists) { + return $lang; + } + } + + $default_language = OC_Config::getValue('default_language', false); + + if($default_language !== false) { + return $default_language; + } + + if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + $accepted_languages = preg_split('/,\s*/', strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE'])); + if(is_array($app)) { + $available = $app; + } + else{ + $available = self::findAvailableLanguages($app); + } + foreach($accepted_languages as $i) { + $temp = explode(';', $i); + $temp[0] = str_replace('-', '_', $temp[0]); + if( ($key = array_search($temp[0], $available)) !== false) { + if (is_null($app)) { + self::$language = $available[$key]; + } + return $available[$key]; + } + foreach($available as $l) { + if ( $temp[0] == substr($l, 0, 2) ) { + if (is_null($app)) { + self::$language = $l; + } + return $l; + } + } + } + } + + // Last try: English + return 'en'; + } + + /** + * @brief find the l10n directory + * @param $app App that needs to be translated + * @returns directory + */ + protected static function findI18nDir($app) { + // find the i18n dir + $i18ndir = OC::$SERVERROOT.'/core/l10n/'; + if($app != '') { + // Check if the app is in the app folder + if(file_exists(OC_App::getAppPath($app).'/l10n/')) { + $i18ndir = OC_App::getAppPath($app).'/l10n/'; + } + else{ + $i18ndir = OC::$SERVERROOT.'/'.$app.'/l10n/'; + } + } + return $i18ndir; + } + + /** + * @brief find all available languages for an app + * @param $app App that needs to be translated + * @returns array an array of available languages + */ + public static function findAvailableLanguages($app=null) { + $available=array('en');//english is always available + $dir = self::findI18nDir($app); + if(is_dir($dir)) { + $files=scandir($dir); + foreach($files as $file) { + if(substr($file, -4, 4) === '.php' && substr($file, 0, 4) !== 'l10n') { + $i = substr($file, 0, -4); + $available[] = $i; + } + } + } + return $available; + } + + public static function languageExists($app, $lang) { + if ($lang == 'en') {//english is always available + return true; + } + $dir = self::findI18nDir($app); + if(is_dir($dir)) { + return file_exists($dir.'/'.$lang.'.php'); + } + return false; + } +} diff --git a/lib/private/l10n/ach.php b/lib/private/l10n/ach.php new file mode 100644 index 00000000000..406ff5f5a26 --- /dev/null +++ b/lib/private/l10n/ach.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n > 1);"; diff --git a/lib/private/l10n/af_ZA.php b/lib/private/l10n/af_ZA.php new file mode 100644 index 00000000000..d6bf5771e8d --- /dev/null +++ b/lib/private/l10n/af_ZA.php @@ -0,0 +1,14 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Hulp", +"Personal" => "Persoonlik", +"Settings" => "Instellings", +"Users" => "Gebruikers", +"Admin" => "Admin", +"web services under your control" => "webdienste onder jou beheer", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/ar.php b/lib/private/l10n/ar.php new file mode 100644 index 00000000000..f626dcdfda6 --- /dev/null +++ b/lib/private/l10n/ar.php @@ -0,0 +1,50 @@ +<?php +$TRANSLATIONS = array( +"Help" => "المساعدة", +"Personal" => "شخصي", +"Settings" => "إعدادات", +"Users" => "المستخدمين", +"Admin" => "المدير", +"web services under your control" => "خدمات الشبكة تحت سيطرتك", +"ZIP download is turned off." => "تحميل ملفات ZIP متوقف", +"Files need to be downloaded one by one." => "الملفات بحاجة الى ان يتم تحميلها واحد تلو الاخر", +"Back to Files" => "العودة الى الملفات", +"Selected files too large to generate zip file." => "الملفات المحددة كبيرة جدا ليتم ضغطها في ملف zip", +"Application is not enabled" => "التطبيق غير مفعّل", +"Authentication error" => "لم يتم التأكد من الشخصية بنجاح", +"Token expired. Please reload page." => "انتهت صلاحية الكلمة , يرجى اعادة تحميل الصفحة", +"Files" => "الملفات", +"Text" => "معلومات إضافية", +"Images" => "صور", +"%s enter the database username." => "%s ادخل اسم المستخدم الخاص بقاعدة البيانات.", +"%s enter the database name." => "%s ادخل اسم فاعدة البيانات", +"%s you may not use dots in the database name" => "%s لا يسمح لك باستخدام نقطه (.) في اسم قاعدة البيانات", +"MS SQL username and/or password not valid: %s" => "اسم المستخدم و/أو كلمة المرور لنظام MS SQL غير صحيح : %s", +"You need to enter either an existing account or the administrator." => "انت بحاجة لكتابة اسم مستخدم موجود أو حساب المدير.", +"MySQL username and/or password not valid" => "اسم المستخدم و/أو كلمة المرور لنظام MySQL غير صحيح", +"DB Error: \"%s\"" => "خطأ في قواعد البيانات : \"%s\"", +"Offending command was: \"%s\"" => "الأمر المخالف كان : \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "أسم المستخدم '%s'@'localhost' الخاص بـ MySQL موجود مسبقا", +"Drop this user from MySQL" => "احذف اسم المستخدم هذا من الـ MySQL", +"MySQL user '%s'@'%%' already exists" => "أسم المستخدم '%s'@'%%' الخاص بـ MySQL موجود مسبقا", +"Drop this user from MySQL." => "احذف اسم المستخدم هذا من الـ MySQL.", +"Oracle username and/or password not valid" => "اسم المستخدم و/أو كلمة المرور لنظام Oracle غير صحيح", +"Offending command was: \"%s\", name: %s, password: %s" => "الأمر المخالف كان : \"%s\", اسم المستخدم : %s, كلمة المرور: %s", +"PostgreSQL username and/or password not valid" => "اسم المستخدم / أو كلمة المرور الخاصة بـPostgreSQL غير صحيحة", +"Set an admin username." => "اعداد اسم مستخدم للمدير", +"Set an admin password." => "اعداد كلمة مرور للمدير", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "اعدادات خادمك غير صحيحة بشكل تسمح لك بمزامنة ملفاتك وذلك بسبب أن واجهة WebDAV تبدو معطلة", +"Please double check the <a href='%s'>installation guides</a>." => "الرجاء التحقق من <a href='%s'>دليل التنصيب</a>.", +"seconds ago" => "منذ ثواني", +"_%n minute ago_::_%n minutes ago_" => array("","","","","",""), +"_%n hour ago_::_%n hours ago_" => array("","","","","",""), +"today" => "اليوم", +"yesterday" => "يوم أمس", +"_%n day go_::_%n days ago_" => array("","","","","",""), +"last month" => "الشهر الماضي", +"_%n month ago_::_%n months ago_" => array("","","","","",""), +"last year" => "السنةالماضية", +"years ago" => "سنة مضت", +"Could not find category \"%s\"" => "تعذر العثور على المجلد \"%s\"" +); +$PLURAL_FORMS = "nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;"; diff --git a/lib/private/l10n/be.php b/lib/private/l10n/be.php new file mode 100644 index 00000000000..1570411eb86 --- /dev/null +++ b/lib/private/l10n/be.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array("","","",""), +"_%n hour ago_::_%n hours ago_" => array("","","",""), +"_%n day go_::_%n days ago_" => array("","","",""), +"_%n month ago_::_%n months ago_" => array("","","","") +); +$PLURAL_FORMS = "nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/lib/private/l10n/bg_BG.php b/lib/private/l10n/bg_BG.php new file mode 100644 index 00000000000..b6cc949eb8a --- /dev/null +++ b/lib/private/l10n/bg_BG.php @@ -0,0 +1,51 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Помощ", +"Personal" => "Лични", +"Settings" => "Настройки", +"Users" => "Потребители", +"Admin" => "Админ", +"web services under your control" => "уеб услуги под Ваш контрол", +"ZIP download is turned off." => "Изтеглянето като ZIP е изключено.", +"Files need to be downloaded one by one." => "Файловете трябва да се изтеглят един по един.", +"Back to Files" => "Назад към файловете", +"Selected files too large to generate zip file." => "Избраните файлове са прекалено големи за генерирането на ZIP архив.", +"Application is not enabled" => "Приложението не е включено.", +"Authentication error" => "Възникна проблем с идентификацията", +"Token expired. Please reload page." => "Ключът е изтекъл, моля презаредете страницата", +"Files" => "Файлове", +"Text" => "Текст", +"Images" => "Снимки", +"%s enter the database username." => "%s въведете потребителско име за базата с данни.", +"%s enter the database name." => "%s въведете име на базата с данни.", +"%s you may not use dots in the database name" => "%s, не можете да ползвате точки в името на базата от данни", +"MS SQL username and/or password not valid: %s" => "Невалидно MS SQL потребителско име и/или парола: %s", +"You need to enter either an existing account or the administrator." => "Необходимо е да влезете в всъществуващ акаунт или като администратора", +"MySQL username and/or password not valid" => "Невалидно MySQL потребителско име и/или парола", +"DB Error: \"%s\"" => "Грешка в базата от данни: \"%s\"", +"Offending command was: \"%s\"" => "Проблемната команда беше: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL потребителят '%s'@'localhost' вече съществува", +"Drop this user from MySQL" => "Изтриване на потребителя от MySQL", +"MySQL user '%s'@'%%' already exists" => "MySQL потребителят '%s'@'%%' вече съществува.", +"Drop this user from MySQL." => "Изтриване на потребителя от MySQL.", +"Oracle connection could not be established" => "Oracle връзка не можа да се осъществи", +"Oracle username and/or password not valid" => "Невалидно Oracle потребителско име и/или парола", +"Offending command was: \"%s\", name: %s, password: %s" => "Проблемната команда беше: \"%s\", име: %s, парола: %s", +"PostgreSQL username and/or password not valid" => "Невалидно PostgreSQL потребителско име и/или парола", +"Set an admin username." => "Въведете потребителско име за администратор.", +"Set an admin password." => "Въведете парола за администратор.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Вашият web сървър все още не е удачно настроен да позволява синхронизация на файлове, защото WebDAV интерфейсът изглежда не работи.", +"Please double check the <a href='%s'>installation guides</a>." => "Моля направете повторна справка с <a href='%s'>ръководството за инсталиране</a>.", +"seconds ago" => "преди секунди", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "днес", +"yesterday" => "вчера", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "последният месец", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "последната година", +"years ago" => "последните години", +"Could not find category \"%s\"" => "Невъзможно откриване на категорията \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/bn_BD.php b/lib/private/l10n/bn_BD.php new file mode 100644 index 00000000000..a42435a2a47 --- /dev/null +++ b/lib/private/l10n/bn_BD.php @@ -0,0 +1,29 @@ +<?php +$TRANSLATIONS = array( +"Help" => "সহায়িকা", +"Personal" => "ব্যক্তিগত", +"Settings" => "নিয়ামকসমূহ", +"Users" => "ব্যবহারকারী", +"Admin" => "প্রশাসন", +"web services under your control" => "ওয়েব সার্ভিস আপনার হাতের মুঠোয়", +"ZIP download is turned off." => "ZIP ডাউনলোড বন্ধ করা আছে।", +"Files need to be downloaded one by one." => "ফাইলগুলো একে একে ডাউনলোড করা আবশ্যক।", +"Back to Files" => "ফাইলে ফিরে চল", +"Selected files too large to generate zip file." => "নির্বাচিত ফাইলগুলো এতই বৃহৎ যে জিপ ফাইল তৈরী করা সম্ভব নয়।", +"Application is not enabled" => "অ্যাপ্লিকেসনটি সক্রিয় নয়", +"Authentication error" => "অনুমোদন ঘটিত সমস্যা", +"Token expired. Please reload page." => "টোকেন মেয়াদোত্তীর্ণ। দয়া করে পৃষ্ঠাটি পূনরায় লোড করুন।", +"Files" => "ফাইল", +"Text" => "টেক্সট", +"seconds ago" => "সেকেন্ড পূর্বে", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "আজ", +"yesterday" => "গতকাল", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "গত মাস", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "গত বছর", +"years ago" => "বছর পূর্বে" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/bs.php b/lib/private/l10n/bs.php new file mode 100644 index 00000000000..3cb98906e62 --- /dev/null +++ b/lib/private/l10n/bs.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array("","",""), +"_%n hour ago_::_%n hours ago_" => array("","",""), +"_%n day go_::_%n days ago_" => array("","",""), +"_%n month ago_::_%n months ago_" => array("","","") +); +$PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/lib/private/l10n/ca.php b/lib/private/l10n/ca.php new file mode 100644 index 00000000000..a8769224705 --- /dev/null +++ b/lib/private/l10n/ca.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "L'aplicació \"%s\" no es pot instal·lar perquè no és compatible amb aquesta versió d'ownCloud.", +"No app name specified" => "No heu especificat cap nom d'aplicació", +"Help" => "Ajuda", +"Personal" => "Personal", +"Settings" => "Configuració", +"Users" => "Usuaris", +"Admin" => "Administració", +"Failed to upgrade \"%s\"." => "Ha fallat l'actualització \"%s\".", +"Custom profile pictures don't work with encryption yet" => "Les imatges de perfil personals encara no funcionen amb encriptació", +"Unknown filetype" => "Tipus de fitxer desconegut", +"Invalid image" => "Imatge no vàlida", +"web services under your control" => "controleu els vostres serveis web", +"cannot open \"%s\"" => "no es pot obrir \"%s\"", +"ZIP download is turned off." => "La baixada en ZIP està desactivada.", +"Files need to be downloaded one by one." => "Els fitxers s'han de baixar d'un en un.", +"Back to Files" => "Torna a Fitxers", +"Selected files too large to generate zip file." => "Els fitxers seleccionats son massa grans per generar un fitxer zip.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Baixeu els fitxers en trossos petits, de forma separada, o pregunteu a l'administrador.", +"No source specified when installing app" => "No heu especificat la font en instal·lar l'aplicació", +"No href specified when installing app from http" => "No heu especificat href en instal·lar l'aplicació des de http", +"No path specified when installing app from local file" => "No heu seleccionat el camí en instal·lar una aplicació des d'un fitxer local", +"Archives of type %s are not supported" => "Els fitxers del tipus %s no són compatibles", +"Failed to open archive when installing app" => "Ha fallat l'obertura del fitxer en instal·lar l'aplicació", +"App does not provide an info.xml file" => "L'aplicació no proporciona un fitxer info.xml", +"App can't be installed because of not allowed code in the App" => "L'aplicació no es pot instal·lar perquè hi ha codi no autoritzat en l'aplicació", +"App can't be installed because it is not compatible with this version of ownCloud" => "L'aplicació no es pot instal·lar perquè no és compatible amb aquesta versió d'ownCloud", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "L'aplicació no es pot instal·lar perquè conté l'etiqueta <shipped>vertader</shipped> que no es permet per aplicacions no enviades", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "L'aplicació no es pot instal·lar perquè la versió a info.xml/version no és la mateixa que la versió indicada des de la botiga d'aplicacions", +"App directory already exists" => "La carpeta de l'aplicació ja existeix", +"Can't create app folder. Please fix permissions. %s" => "No es pot crear la carpeta de l'aplicació. Arregleu els permisos. %s", +"Application is not enabled" => "L'aplicació no està habilitada", +"Authentication error" => "Error d'autenticació", +"Token expired. Please reload page." => "El testimoni ha expirat. Torneu a carregar la pàgina.", +"Files" => "Fitxers", +"Text" => "Text", +"Images" => "Imatges", +"%s enter the database username." => "%s escriviu el nom d'usuari de la base de dades.", +"%s enter the database name." => "%s escriviu el nom de la base de dades.", +"%s you may not use dots in the database name" => "%s no podeu usar punts en el nom de la base de dades", +"MS SQL username and/or password not valid: %s" => "Nom d'usuari i/o contrasenya MS SQL no vàlids: %s", +"You need to enter either an existing account or the administrator." => "Heu d'escriure un compte existent o el d'administrador.", +"MySQL username and/or password not valid" => "Nom d'usuari i/o contrasenya MySQL no vàlids", +"DB Error: \"%s\"" => "Error DB: \"%s\"", +"Offending command was: \"%s\"" => "L'ordre en conflicte és: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "L'usuari MySQL '%s'@'localhost' ja existeix.", +"Drop this user from MySQL" => "Elimina aquest usuari de MySQL", +"MySQL user '%s'@'%%' already exists" => "L'usuari MySQL '%s'@'%%' ja existeix", +"Drop this user from MySQL." => "Elimina aquest usuari de MySQL.", +"Oracle connection could not be established" => "No s'ha pogut establir la connexió Oracle", +"Oracle username and/or password not valid" => "Nom d'usuari i/o contrasenya Oracle no vàlids", +"Offending command was: \"%s\", name: %s, password: %s" => "L'ordre en conflicte és: \"%s\", nom: %s, contrasenya: %s", +"PostgreSQL username and/or password not valid" => "Nom d'usuari i/o contrasenya PostgreSQL no vàlids", +"Set an admin username." => "Establiu un nom d'usuari per l'administrador.", +"Set an admin password." => "Establiu una contrasenya per l'administrador.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "El servidor web no està configurat correctament per permetre la sincronització de fitxers perquè la interfície WebDAV sembla no funcionar correctament.", +"Please double check the <a href='%s'>installation guides</a>." => "Comproveu les <a href='%s'>guies d'instal·lació</a>.", +"seconds ago" => "segons enrere", +"_%n minute ago_::_%n minutes ago_" => array("fa %n minut","fa %n minuts"), +"_%n hour ago_::_%n hours ago_" => array("fa %n hora","fa %n hores"), +"today" => "avui", +"yesterday" => "ahir", +"_%n day go_::_%n days ago_" => array("fa %n dia","fa %n dies"), +"last month" => "el mes passat", +"_%n month ago_::_%n months ago_" => array("fa %n mes","fa %n mesos"), +"last year" => "l'any passat", +"years ago" => "anys enrere", +"Caused by:" => "Provocat per:", +"Could not find category \"%s\"" => "No s'ha trobat la categoria \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/cs_CZ.php b/lib/private/l10n/cs_CZ.php new file mode 100644 index 00000000000..ed31ae79529 --- /dev/null +++ b/lib/private/l10n/cs_CZ.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Aplikace \"%s\" nemůže být nainstalována, protože není kompatibilní s touto verzí ownCloud.", +"No app name specified" => "Nebyl zadan název aplikace", +"Help" => "Nápověda", +"Personal" => "Osobní", +"Settings" => "Nastavení", +"Users" => "Uživatelé", +"Admin" => "Administrace", +"Failed to upgrade \"%s\"." => "Selhala aktualizace verze \"%s\".", +"Custom profile pictures don't work with encryption yet" => "Vlastní profilové obrázky zatím nefungují v kombinaci se šifrováním", +"Unknown filetype" => "Neznámý typ souboru", +"Invalid image" => "Chybný obrázek", +"web services under your control" => "webové služby pod Vaší kontrolou", +"cannot open \"%s\"" => "nelze otevřít \"%s\"", +"ZIP download is turned off." => "Stahování v ZIPu je vypnuto.", +"Files need to be downloaded one by one." => "Soubory musí být stahovány jednotlivě.", +"Back to Files" => "Zpět k souborům", +"Selected files too large to generate zip file." => "Vybrané soubory jsou příliš velké pro vytvoření ZIP souboru.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Stáhněte soubory po menších částech, samostatně, nebo se obraťte na správce.", +"No source specified when installing app" => "Nebyl zadán zdroj při instalaci aplikace", +"No href specified when installing app from http" => "Nebyl zadán odkaz pro instalaci aplikace z HTTP", +"No path specified when installing app from local file" => "Nebyla zadána cesta pro instalaci aplikace z místního souboru", +"Archives of type %s are not supported" => "Archivy typu %s nejsou podporovány", +"Failed to open archive when installing app" => "Chyba při otevírání archivu během instalace aplikace", +"App does not provide an info.xml file" => "Aplikace neposkytuje soubor info.xml", +"App can't be installed because of not allowed code in the App" => "Aplikace nemůže být nainstalována, protože obsahuje nepovolený kód", +"App can't be installed because it is not compatible with this version of ownCloud" => "Aplikace nemůže být nainstalována, protože není kompatibilní s touto verzí ownCloud", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "Aplikace nemůže být nainstalována, protože obsahuje značku\n<shipped>\n\ntrue\n</shipped>\n\ncož není povoleno pro nedodávané aplikace", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "Aplikace nemůže být nainstalována, protože verze uvedená v info.xml/version nesouhlasí s verzí oznámenou z úložiště aplikací.", +"App directory already exists" => "Adresář aplikace již existuje", +"Can't create app folder. Please fix permissions. %s" => "Nelze vytvořit složku aplikace. Opravte práva souborů. %s", +"Application is not enabled" => "Aplikace není povolena", +"Authentication error" => "Chyba ověření", +"Token expired. Please reload page." => "Token vypršel. Obnovte prosím stránku.", +"Files" => "Soubory", +"Text" => "Text", +"Images" => "Obrázky", +"%s enter the database username." => "Zadejte uživatelské jméno %s databáze.", +"%s enter the database name." => "Zadejte název databáze pro %s databáze.", +"%s you may not use dots in the database name" => "V názvu databáze %s nesmíte používat tečky.", +"MS SQL username and/or password not valid: %s" => "Uživatelské jméno či heslo MSSQL není platné: %s", +"You need to enter either an existing account or the administrator." => "Musíte zadat existující účet či správce.", +"MySQL username and/or password not valid" => "Uživatelské jméno či heslo MySQL není platné", +"DB Error: \"%s\"" => "Chyba databáze: \"%s\"", +"Offending command was: \"%s\"" => "Příslušný příkaz byl: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "Uživatel '%s'@'localhost' již v MySQL existuje.", +"Drop this user from MySQL" => "Zrušte tohoto uživatele z MySQL", +"MySQL user '%s'@'%%' already exists" => "Uživatel '%s'@'%%' již v MySQL existuje", +"Drop this user from MySQL." => "Zrušte tohoto uživatele z MySQL", +"Oracle connection could not be established" => "Spojení s Oracle nemohlo být navázáno", +"Oracle username and/or password not valid" => "Uživatelské jméno či heslo Oracle není platné", +"Offending command was: \"%s\", name: %s, password: %s" => "Příslušný příkaz byl: \"%s\", jméno: %s, heslo: %s", +"PostgreSQL username and/or password not valid" => "Uživatelské jméno či heslo PostgreSQL není platné", +"Set an admin username." => "Zadejte uživatelské jméno správce.", +"Set an admin password." => "Zadejte heslo správce.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Váš webový server není správně nastaven pro umožnění synchronizace, rozhraní WebDAV se zdá být rozbité.", +"Please double check the <a href='%s'>installation guides</a>." => "Zkonzultujte, prosím, <a href='%s'>průvodce instalací</a>.", +"seconds ago" => "před pár sekundami", +"_%n minute ago_::_%n minutes ago_" => array("před %n minutou","před %n minutami","před %n minutami"), +"_%n hour ago_::_%n hours ago_" => array("před %n hodinou","před %n hodinami","před %n hodinami"), +"today" => "dnes", +"yesterday" => "včera", +"_%n day go_::_%n days ago_" => array("před %n dnem","před %n dny","před %n dny"), +"last month" => "minulý měsíc", +"_%n month ago_::_%n months ago_" => array("před %n měsícem","před %n měsíci","před %n měsíci"), +"last year" => "minulý rok", +"years ago" => "před lety", +"Caused by:" => "Příčina:", +"Could not find category \"%s\"" => "Nelze nalézt kategorii \"%s\"" +); +$PLURAL_FORMS = "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"; diff --git a/lib/private/l10n/cy_GB.php b/lib/private/l10n/cy_GB.php new file mode 100644 index 00000000000..6973b51878f --- /dev/null +++ b/lib/private/l10n/cy_GB.php @@ -0,0 +1,50 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Cymorth", +"Personal" => "Personol", +"Settings" => "Gosodiadau", +"Users" => "Defnyddwyr", +"Admin" => "Gweinyddu", +"web services under your control" => "gwasanaethau gwe a reolir gennych", +"ZIP download is turned off." => "Mae llwytho ZIP wedi ei ddiffodd.", +"Files need to be downloaded one by one." => "Mae angen llwytho ffeiliau i lawr fesul un.", +"Back to Files" => "Nôl i Ffeiliau", +"Selected files too large to generate zip file." => "Mae'r ffeiliau ddewiswyd yn rhy fawr i gynhyrchu ffeil zip.", +"Application is not enabled" => "Nid yw'r pecyn wedi'i alluogi", +"Authentication error" => "Gwall dilysu", +"Token expired. Please reload page." => "Tocyn wedi dod i ben. Ail-lwythwch y dudalen.", +"Files" => "Ffeiliau", +"Text" => "Testun", +"Images" => "Delweddau", +"%s enter the database username." => "%s rhowch enw defnyddiwr y gronfa ddata.", +"%s enter the database name." => "%s rhowch enw'r gronfa ddata.", +"%s you may not use dots in the database name" => "%s does dim hawl defnyddio dot yn enw'r gronfa ddata", +"MS SQL username and/or password not valid: %s" => "Enw a/neu gyfrinair MS SQL annilys: %s", +"You need to enter either an existing account or the administrator." => "Rhaid i chi naill ai gyflwyno cyfrif presennol neu'r gweinyddwr.", +"MySQL username and/or password not valid" => "Enw a/neu gyfrinair MySQL annilys", +"DB Error: \"%s\"" => "Gwall DB: \"%s\"", +"Offending command was: \"%s\"" => "Y gorchymyn wnaeth beri tramgwydd oedd: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "Defnyddiwr MySQL '%s'@'localhost' yn bodoli eisoes.", +"Drop this user from MySQL" => "Gollwng y defnyddiwr hwn o MySQL", +"MySQL user '%s'@'%%' already exists" => "Defnyddiwr MySQL '%s'@'%%' eisoes yn bodoli", +"Drop this user from MySQL." => "Gollwng y defnyddiwr hwn o MySQL.", +"Oracle username and/or password not valid" => "Enw a/neu gyfrinair Oracle annilys", +"Offending command was: \"%s\", name: %s, password: %s" => "Y gorchymyn wnaeth beri tramgwydd oedd: \"%s\", enw: %s, cyfrinair: %s", +"PostgreSQL username and/or password not valid" => "Enw a/neu gyfrinair PostgreSQL annilys", +"Set an admin username." => "Creu enw defnyddiwr i'r gweinyddwr.", +"Set an admin password." => "Gosod cyfrinair y gweinyddwr.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Nid yw eich gweinydd wedi'i gyflunio eto i ganiatáu cydweddu ffeiliau oherwydd bod y rhyngwyneb WebDAV wedi torri.", +"Please double check the <a href='%s'>installation guides</a>." => "Gwiriwch y <a href='%s'>canllawiau gosod</a> eto.", +"seconds ago" => "eiliad yn ôl", +"_%n minute ago_::_%n minutes ago_" => array("","","",""), +"_%n hour ago_::_%n hours ago_" => array("","","",""), +"today" => "heddiw", +"yesterday" => "ddoe", +"_%n day go_::_%n days ago_" => array("","","",""), +"last month" => "mis diwethaf", +"_%n month ago_::_%n months ago_" => array("","","",""), +"last year" => "y llynedd", +"years ago" => "blwyddyn yn ôl", +"Could not find category \"%s\"" => "Methu canfod categori \"%s\"" +); +$PLURAL_FORMS = "nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;"; diff --git a/lib/private/l10n/da.php b/lib/private/l10n/da.php new file mode 100644 index 00000000000..05a43f42ed9 --- /dev/null +++ b/lib/private/l10n/da.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "App'en \"%s\" kan ikke blive installeret, da den ikke er kompatibel med denne version af ownCloud.", +"No app name specified" => "Intet app-navn angivet", +"Help" => "Hjælp", +"Personal" => "Personligt", +"Settings" => "Indstillinger", +"Users" => "Brugere", +"Admin" => "Admin", +"Failed to upgrade \"%s\"." => "Upgradering af \"%s\" fejlede", +"Custom profile pictures don't work with encryption yet" => "Personligt profilbillede virker endnu ikke sammen med kryptering", +"Unknown filetype" => "Ukendt filtype", +"Invalid image" => "Ugyldigt billede", +"web services under your control" => "Webtjenester under din kontrol", +"cannot open \"%s\"" => "Kan ikke åbne \"%s\"", +"ZIP download is turned off." => "ZIP-download er slået fra.", +"Files need to be downloaded one by one." => "Filer skal downloades en for en.", +"Back to Files" => "Tilbage til Filer", +"Selected files too large to generate zip file." => "De markerede filer er for store til at generere en ZIP-fil.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Download filerne i små bider, seperat, eller kontakt venligst din administrator.", +"No source specified when installing app" => "Ingen kilde angivet under installation af app", +"No href specified when installing app from http" => "Ingen href angivet under installation af app via http", +"No path specified when installing app from local file" => "Ingen sti angivet under installation af app fra lokal fil", +"Archives of type %s are not supported" => "Arkiver af type %s understøttes ikke", +"Failed to open archive when installing app" => "Kunne ikke åbne arkiv under installation af appen", +"App does not provide an info.xml file" => "Der følger ingen info.xml-fil med appen", +"App can't be installed because of not allowed code in the App" => "Appen kan ikke installeres, da den indeholder ikke-tilladt kode", +"App can't be installed because it is not compatible with this version of ownCloud" => "Appen kan ikke installeres, da den ikke er kompatibel med denne version af ownCloud.", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "Appen kan ikke installeres, da den indeholder taget\n<shipped>\n\ntrue\n</shipped>\n\nhvilket ikke er tilladt for ikke-medfølgende apps", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "App kan ikke installeres, da versionen i info.xml/version ikke er den samme som versionen rapporteret fra app-storen", +"App directory already exists" => "App-mappe findes allerede", +"Can't create app folder. Please fix permissions. %s" => "Kan ikke oprette app-mappe. Ret tilladelser. %s", +"Application is not enabled" => "Programmet er ikke aktiveret", +"Authentication error" => "Adgangsfejl", +"Token expired. Please reload page." => "Adgang er udløbet. Genindlæs siden.", +"Files" => "Filer", +"Text" => "SMS", +"Images" => "Billeder", +"%s enter the database username." => "%s indtast database brugernavnet.", +"%s enter the database name." => "%s indtast database navnet.", +"%s you may not use dots in the database name" => "%s du må ikke bruge punktummer i databasenavnet.", +"MS SQL username and/or password not valid: %s" => "MS SQL brugernavn og/eller adgangskode ikke er gyldigt: %s", +"You need to enter either an existing account or the administrator." => "Du bliver nødt til at indtaste en eksisterende bruger eller en administrator.", +"MySQL username and/or password not valid" => "MySQL brugernavn og/eller kodeord er ikke gyldigt.", +"DB Error: \"%s\"" => "Databasefejl: \"%s\"", +"Offending command was: \"%s\"" => "Fejlende kommando var: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL brugeren '%s'@'localhost' eksisterer allerede.", +"Drop this user from MySQL" => "Slet denne bruger fra MySQL", +"MySQL user '%s'@'%%' already exists" => "MySQL brugeren '%s'@'%%' eksisterer allerede.", +"Drop this user from MySQL." => "Slet denne bruger fra MySQL", +"Oracle connection could not be established" => "Oracle forbindelsen kunne ikke etableres", +"Oracle username and/or password not valid" => "Oracle brugernavn og/eller kodeord er ikke gyldigt.", +"Offending command was: \"%s\", name: %s, password: %s" => "Fejlende kommando var: \"%s\", navn: %s, password: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL brugernavn og/eller kodeord er ikke gyldigt.", +"Set an admin username." => "Angiv et admin brugernavn.", +"Set an admin password." => "Angiv et admin kodeord.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Din webserver er endnu ikke sat op til at tillade fil synkronisering fordi WebDAV grænsefladen virker ødelagt.", +"Please double check the <a href='%s'>installation guides</a>." => "Dobbelttjek venligst <a href='%s'>installations vejledningerne</a>.", +"seconds ago" => "sekunder siden", +"_%n minute ago_::_%n minutes ago_" => array("%n minut siden","%n minutter siden"), +"_%n hour ago_::_%n hours ago_" => array("%n time siden","%n timer siden"), +"today" => "i dag", +"yesterday" => "i går", +"_%n day go_::_%n days ago_" => array("%n dag siden","%n dage siden"), +"last month" => "sidste måned", +"_%n month ago_::_%n months ago_" => array("%n måned siden","%n måneder siden"), +"last year" => "sidste år", +"years ago" => "år siden", +"Caused by:" => "Forårsaget af:", +"Could not find category \"%s\"" => "Kunne ikke finde kategorien \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/de.php b/lib/private/l10n/de.php new file mode 100644 index 00000000000..87e7a67b47b --- /dev/null +++ b/lib/private/l10n/de.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Applikation \"%s\" kann nicht installiert werden, da sie mit dieser ownCloud Version nicht kompatibel ist.", +"No app name specified" => "Es wurde kein Applikation-Name angegeben", +"Help" => "Hilfe", +"Personal" => "Persönlich", +"Settings" => "Einstellungen", +"Users" => "Benutzer", +"Admin" => "Administration", +"Failed to upgrade \"%s\"." => "Konnte \"%s\" nicht aktualisieren.", +"Custom profile pictures don't work with encryption yet" => "Individuelle Profilbilder werden noch nicht von der Verschlüsselung unterstützt", +"Unknown filetype" => "Unbekannter Dateityp", +"Invalid image" => "Ungültiges Bild", +"web services under your control" => "Web-Services unter Deiner Kontrolle", +"cannot open \"%s\"" => "Öffnen von \"%s\" fehlgeschlagen", +"ZIP download is turned off." => "Der ZIP-Download ist deaktiviert.", +"Files need to be downloaded one by one." => "Die Dateien müssen einzeln heruntergeladen werden.", +"Back to Files" => "Zurück zu \"Dateien\"", +"Selected files too large to generate zip file." => "Die gewählten Dateien sind zu groß, um eine ZIP-Datei zu erstellen.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Lade die Dateien in kleineren, separaten, Stücken herunter oder bitte deinen Administrator.", +"No source specified when installing app" => "Für die Installation der Applikation wurde keine Quelle angegeben", +"No href specified when installing app from http" => "Der Link (href) wurde nicht angegeben um die Applikation per http zu installieren", +"No path specified when installing app from local file" => "Bei der Installation der Applikation aus einer lokalen Datei wurde kein Pfad angegeben", +"Archives of type %s are not supported" => "Archive vom Typ %s werden nicht unterstützt", +"Failed to open archive when installing app" => "Das Archiv konnte bei der Installation der Applikation nicht geöffnet werden", +"App does not provide an info.xml file" => "Die Applikation enthält keine info,xml Datei", +"App can't be installed because of not allowed code in the App" => "Die Applikation kann auf Grund von unerlaubten Code nicht installiert werden", +"App can't be installed because it is not compatible with this version of ownCloud" => "Die Anwendung konnte nicht installiert werden, weil Sie nicht mit dieser Version von ownCloud kompatibel ist.", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "Die Applikation konnte nicht installiert werden, da diese das <shipped>true</shipped> Tag beinhaltet und dieses, bei nicht mitausgelieferten Applikationen, nicht erlaubt ist ist", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "Die Applikation konnte nicht installiert werden, da die Version in der info.xml nicht die gleiche Version wie im App-Store ist", +"App directory already exists" => "Das Applikationsverzeichnis existiert bereits", +"Can't create app folder. Please fix permissions. %s" => "Es kann kein Applikationsordner erstellt werden. Bitte passen sie die Berechtigungen an. %s", +"Application is not enabled" => "Die Anwendung ist nicht aktiviert", +"Authentication error" => "Fehler bei der Anmeldung", +"Token expired. Please reload page." => "Token abgelaufen. Bitte lade die Seite neu.", +"Files" => "Dateien", +"Text" => "Text", +"Images" => "Bilder", +"%s enter the database username." => "%s gib den Datenbank-Benutzernamen an.", +"%s enter the database name." => "%s gib den Datenbank-Namen an.", +"%s you may not use dots in the database name" => "%s Der Datenbank-Name darf keine Punkte enthalten", +"MS SQL username and/or password not valid: %s" => "MS SQL Benutzername und/oder Password ungültig: %s", +"You need to enter either an existing account or the administrator." => "Du musst entweder ein existierendes Benutzerkonto oder das Administratoren-Konto angeben.", +"MySQL username and/or password not valid" => "MySQL Benutzername und/oder Passwort ungültig", +"DB Error: \"%s\"" => "DB Fehler: \"%s\"", +"Offending command was: \"%s\"" => "Fehlerhafter Befehl war: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL Benutzer '%s'@'localhost' existiert bereits.", +"Drop this user from MySQL" => "Lösche diesen Benutzer von MySQL", +"MySQL user '%s'@'%%' already exists" => "MySQL Benutzer '%s'@'%%' existiert bereits", +"Drop this user from MySQL." => "Lösche diesen Benutzer aus MySQL.", +"Oracle connection could not be established" => "Es konnte keine Verbindung zur Oracle-Datenbank hergestellt werden", +"Oracle username and/or password not valid" => "Oracle Benutzername und/oder Passwort ungültig", +"Offending command was: \"%s\", name: %s, password: %s" => "Fehlerhafter Befehl war: \"%s\", Name: %s, Passwort: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL Benutzername und/oder Passwort ungültig", +"Set an admin username." => "Setze Administrator Benutzername.", +"Set an admin password." => "Setze Administrator Passwort", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Dein Web-Server ist noch nicht für Datei-Synchronisation bereit, weil die WebDAV-Schnittstelle vermutlich defekt ist.", +"Please double check the <a href='%s'>installation guides</a>." => "Bitte prüfe die <a href='%s'>Installationsanleitungen</a>.", +"seconds ago" => "Gerade eben", +"_%n minute ago_::_%n minutes ago_" => array("","Vor %n Minuten"), +"_%n hour ago_::_%n hours ago_" => array("","Vor %n Stunden"), +"today" => "Heute", +"yesterday" => "Gestern", +"_%n day go_::_%n days ago_" => array("","Vor %n Tagen"), +"last month" => "Letzten Monat", +"_%n month ago_::_%n months ago_" => array("","Vor %n Monaten"), +"last year" => "Letztes Jahr", +"years ago" => "Vor Jahren", +"Caused by:" => "Verursacht durch:", +"Could not find category \"%s\"" => "Die Kategorie \"%s\" konnte nicht gefunden werden." +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/de_AT.php b/lib/private/l10n/de_AT.php new file mode 100644 index 00000000000..15f78e0bce6 --- /dev/null +++ b/lib/private/l10n/de_AT.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/de_CH.php b/lib/private/l10n/de_CH.php new file mode 100644 index 00000000000..33f3446a693 --- /dev/null +++ b/lib/private/l10n/de_CH.php @@ -0,0 +1,59 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Anwendung \"%s\" kann nicht installiert werden, da sie mit dieser Version von ownCloud nicht kompatibel ist.", +"No app name specified" => "Kein App-Name spezifiziert", +"Help" => "Hilfe", +"Personal" => "Persönlich", +"Settings" => "Einstellungen", +"Users" => "Benutzer", +"Admin" => "Administrator", +"Failed to upgrade \"%s\"." => "Konnte \"%s\" nicht aktualisieren.", +"web services under your control" => "Web-Services unter Ihrer Kontrolle", +"cannot open \"%s\"" => "Öffnen von \"%s\" fehlgeschlagen", +"ZIP download is turned off." => "Der ZIP-Download ist deaktiviert.", +"Files need to be downloaded one by one." => "Die Dateien müssen einzeln heruntergeladen werden.", +"Back to Files" => "Zurück zu \"Dateien\"", +"Selected files too large to generate zip file." => "Die gewählten Dateien sind zu gross, um eine ZIP-Datei zu erstellen.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Laden Sie die Dateien in kleineren, separaten, Stücken herunter oder bitten Sie Ihren Administrator.", +"App can't be installed because of not allowed code in the App" => "Anwendung kann wegen nicht erlaubten Codes nicht installiert werden", +"App directory already exists" => "Anwendungsverzeichnis existiert bereits", +"Application is not enabled" => "Die Anwendung ist nicht aktiviert", +"Authentication error" => "Authentifizierungs-Fehler", +"Token expired. Please reload page." => "Token abgelaufen. Bitte laden Sie die Seite neu.", +"Files" => "Dateien", +"Text" => "Text", +"Images" => "Bilder", +"%s enter the database username." => "%s geben Sie den Datenbank-Benutzernamen an.", +"%s enter the database name." => "%s geben Sie den Datenbank-Namen an.", +"%s you may not use dots in the database name" => "%s Der Datenbank-Name darf keine Punkte enthalten", +"MS SQL username and/or password not valid: %s" => "MS SQL Benutzername und/oder Passwort ungültig: %s", +"You need to enter either an existing account or the administrator." => "Sie müssen entweder ein existierendes Benutzerkonto oder das Administratoren-Konto angeben.", +"MySQL username and/or password not valid" => "MySQL Benutzername und/oder Passwort ungültig", +"DB Error: \"%s\"" => "DB Fehler: \"%s\"", +"Offending command was: \"%s\"" => "Fehlerhafter Befehl war: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL Benutzer '%s'@'localhost' existiert bereits.", +"Drop this user from MySQL" => "Lösche diesen Benutzer aus MySQL", +"MySQL user '%s'@'%%' already exists" => "MySQL Benutzer '%s'@'%%' existiert bereits", +"Drop this user from MySQL." => "Lösche diesen Benutzer aus MySQL.", +"Oracle connection could not be established" => "Die Oracle-Verbindung konnte nicht aufgebaut werden.", +"Oracle username and/or password not valid" => "Oracle Benutzername und/oder Passwort ungültig", +"Offending command was: \"%s\", name: %s, password: %s" => "Fehlerhafter Befehl war: \"%s\", Name: %s, Passwort: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL Benutzername und/oder Passwort ungültig", +"Set an admin username." => "Setze Administrator Benutzername.", +"Set an admin password." => "Setze Administrator Passwort", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Ihr Web-Server ist noch nicht für eine Datei-Synchronisation konfiguriert, weil die WebDAV-Schnittstelle vermutlich defekt ist.", +"Please double check the <a href='%s'>installation guides</a>." => "Bitte prüfen Sie die <a href='%s'>Installationsanleitungen</a>.", +"seconds ago" => "Gerade eben", +"_%n minute ago_::_%n minutes ago_" => array("","Vor %n Minuten"), +"_%n hour ago_::_%n hours ago_" => array("","Vor %n Stunden"), +"today" => "Heute", +"yesterday" => "Gestern", +"_%n day go_::_%n days ago_" => array("","Vor %n Tagen"), +"last month" => "Letzten Monat", +"_%n month ago_::_%n months ago_" => array("","Vor %n Monaten"), +"last year" => "Letztes Jahr", +"years ago" => "Vor Jahren", +"Caused by:" => "Verursacht durch:", +"Could not find category \"%s\"" => "Die Kategorie «%s» konnte nicht gefunden werden." +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/de_DE.php b/lib/private/l10n/de_DE.php new file mode 100644 index 00000000000..09be0eea22d --- /dev/null +++ b/lib/private/l10n/de_DE.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Applikation \"%s\" kann nicht installiert werden, da sie mit dieser ownCloud Version nicht kompatibel ist.", +"No app name specified" => "Es wurde kein Applikation-Name angegeben", +"Help" => "Hilfe", +"Personal" => "Persönlich", +"Settings" => "Einstellungen", +"Users" => "Benutzer", +"Admin" => "Administrator", +"Failed to upgrade \"%s\"." => "Konnte \"%s\" nicht aktualisieren.", +"Custom profile pictures don't work with encryption yet" => "Individuelle Profilbilder werden noch nicht von der Verschlüsselung unterstützt", +"Unknown filetype" => "Unbekannter Dateityp", +"Invalid image" => "Ungültiges Bild", +"web services under your control" => "Web-Services unter Ihrer Kontrolle", +"cannot open \"%s\"" => "Öffnen von \"%s\" fehlgeschlagen", +"ZIP download is turned off." => "Der ZIP-Download ist deaktiviert.", +"Files need to be downloaded one by one." => "Die Dateien müssen einzeln heruntergeladen werden.", +"Back to Files" => "Zurück zu \"Dateien\"", +"Selected files too large to generate zip file." => "Die gewählten Dateien sind zu groß, um eine ZIP-Datei zu erstellen.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Laden Sie die Dateien in kleineren, separaten, Stücken herunter oder bitten Sie Ihren Administrator.", +"No source specified when installing app" => "Für die Installation der Applikation wurde keine Quelle angegeben", +"No href specified when installing app from http" => "Der Link (href) wurde nicht angegeben um die Applikation per http zu installieren", +"No path specified when installing app from local file" => "Bei der Installation der Applikation aus einer lokalen Datei wurde kein Pfad angegeben", +"Archives of type %s are not supported" => "Archive des Typs %s werden nicht unterstützt.", +"Failed to open archive when installing app" => "Das Archiv konnte bei der Installation der Applikation nicht geöffnet werden", +"App does not provide an info.xml file" => "Die Applikation enthält keine info,xml Datei", +"App can't be installed because of not allowed code in the App" => "Die Applikation kann auf Grund von unerlaubten Code nicht installiert werden", +"App can't be installed because it is not compatible with this version of ownCloud" => "Die Anwendung konnte nicht installiert werden, weil Sie nicht mit dieser Version von ownCloud kompatibel ist.", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "Die Applikation konnte nicht installiert werden, da diese das <shipped>true</shipped> Tag beinhaltet und dieses, bei nicht mitausgelieferten Applikationen, nicht erlaubt ist ist", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "Die Applikation konnte nicht installiert werden, da die Version in der info.xml nicht die gleiche Version wie im App-Store ist", +"App directory already exists" => "Der Ordner für die Anwendung existiert bereits.", +"Can't create app folder. Please fix permissions. %s" => "Der Ordner für die Anwendung konnte nicht angelegt werden. Bitte überprüfen Sie die Ordner- und Dateirechte und passen Sie diese entsprechend an. %s", +"Application is not enabled" => "Die Anwendung ist nicht aktiviert", +"Authentication error" => "Authentifizierungs-Fehler", +"Token expired. Please reload page." => "Token abgelaufen. Bitte laden Sie die Seite neu.", +"Files" => "Dateien", +"Text" => "Text", +"Images" => "Bilder", +"%s enter the database username." => "%s geben Sie den Datenbank-Benutzernamen an.", +"%s enter the database name." => "%s geben Sie den Datenbank-Namen an.", +"%s you may not use dots in the database name" => "%s Der Datenbank-Name darf keine Punkte enthalten", +"MS SQL username and/or password not valid: %s" => "MS SQL Benutzername und/oder Passwort ungültig: %s", +"You need to enter either an existing account or the administrator." => "Sie müssen entweder ein existierendes Benutzerkonto oder das Administratoren-Konto angeben.", +"MySQL username and/or password not valid" => "MySQL Benutzername und/oder Passwort ungültig", +"DB Error: \"%s\"" => "DB Fehler: \"%s\"", +"Offending command was: \"%s\"" => "Fehlerhafter Befehl war: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL Benutzer '%s'@'localhost' existiert bereits.", +"Drop this user from MySQL" => "Lösche diesen Benutzer aus MySQL", +"MySQL user '%s'@'%%' already exists" => "MySQL Benutzer '%s'@'%%' existiert bereits", +"Drop this user from MySQL." => "Lösche diesen Benutzer aus MySQL.", +"Oracle connection could not be established" => "Die Oracle-Verbindung konnte nicht aufgebaut werden.", +"Oracle username and/or password not valid" => "Oracle Benutzername und/oder Passwort ungültig", +"Offending command was: \"%s\", name: %s, password: %s" => "Fehlerhafter Befehl war: \"%s\", Name: %s, Passwort: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL Benutzername und/oder Passwort ungültig", +"Set an admin username." => "Setze Administrator Benutzername.", +"Set an admin password." => "Setze Administrator Passwort", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Ihr Web-Server ist noch nicht für eine Datei-Synchronisation konfiguriert, weil die WebDAV-Schnittstelle vermutlich defekt ist.", +"Please double check the <a href='%s'>installation guides</a>." => "Bitte prüfen Sie die <a href='%s'>Installationsanleitungen</a>.", +"seconds ago" => "Gerade eben", +"_%n minute ago_::_%n minutes ago_" => array("Vor %n Minute","Vor %n Minuten"), +"_%n hour ago_::_%n hours ago_" => array("Vor %n Stunde","Vor %n Stunden"), +"today" => "Heute", +"yesterday" => "Gestern", +"_%n day go_::_%n days ago_" => array("Vor %n Tag","Vor %n Tagen"), +"last month" => "Letzten Monat", +"_%n month ago_::_%n months ago_" => array("Vor %n Monat","Vor %n Monaten"), +"last year" => "Letztes Jahr", +"years ago" => "Vor Jahren", +"Caused by:" => "Verursacht durch:", +"Could not find category \"%s\"" => "Die Kategorie \"%s\" konnte nicht gefunden werden." +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/el.php b/lib/private/l10n/el.php new file mode 100644 index 00000000000..dcbf82d4a4b --- /dev/null +++ b/lib/private/l10n/el.php @@ -0,0 +1,55 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Βοήθεια", +"Personal" => "Προσωπικά", +"Settings" => "Ρυθμίσεις", +"Users" => "Χρήστες", +"Admin" => "Διαχειριστής", +"Failed to upgrade \"%s\"." => "Αποτυχία αναβάθμισης του \"%s\".", +"web services under your control" => "υπηρεσίες δικτύου υπό τον έλεγχό σας", +"cannot open \"%s\"" => "αδυναμία ανοίγματος \"%s\"", +"ZIP download is turned off." => "Η λήψη ZIP απενεργοποιήθηκε.", +"Files need to be downloaded one by one." => "Τα αρχεία πρέπει να ληφθούν ένα-ένα.", +"Back to Files" => "Πίσω στα Αρχεία", +"Selected files too large to generate zip file." => "Τα επιλεγμένα αρχεία είναι μεγάλα ώστε να δημιουργηθεί αρχείο zip.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Λήψη των αρχείων σε μικρότερα κομμάτια, χωριστά ή ρωτήστε τον διαχειριστή σας.", +"Application is not enabled" => "Δεν ενεργοποιήθηκε η εφαρμογή", +"Authentication error" => "Σφάλμα πιστοποίησης", +"Token expired. Please reload page." => "Το αναγνωριστικό έληξε. Παρακαλώ φορτώστε ξανά την σελίδα.", +"Files" => "Αρχεία", +"Text" => "Κείμενο", +"Images" => "Εικόνες", +"%s enter the database username." => "%s εισάγετε το όνομα χρήστη της βάσης δεδομένων.", +"%s enter the database name." => "%s εισάγετε το όνομα της βάσης δεδομένων.", +"%s you may not use dots in the database name" => "%s μάλλον δεν χρησιμοποιείτε τελείες στο όνομα της βάσης δεδομένων", +"MS SQL username and/or password not valid: %s" => "Το όνομα χρήστη και/ή ο κωδικός της MS SQL δεν είναι έγκυρα: %s", +"You need to enter either an existing account or the administrator." => "Χρειάζεται να εισάγετε είτε έναν υπάρχον λογαριασμό ή του διαχειριστή.", +"MySQL username and/or password not valid" => "Μη έγκυρος χρήστης και/ή συνθηματικό της MySQL", +"DB Error: \"%s\"" => "Σφάλμα Βάσης Δεδομένων: \"%s\"", +"Offending command was: \"%s\"" => "Η εντολη παραβατικοτητας ηταν: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "Υπάρχει ήδη ο χρήστης '%s'@'localhost' της MySQL.", +"Drop this user from MySQL" => "Απόρριψη αυτού του χρήστη από την MySQL", +"MySQL user '%s'@'%%' already exists" => "Ο χρήστης '%s'@'%%' της MySQL υπάρχει ήδη", +"Drop this user from MySQL." => "Απόρριψη αυτού του χρήστη από την MySQL", +"Oracle connection could not be established" => "Αδυναμία σύνδεσης Oracle", +"Oracle username and/or password not valid" => "Μη έγκυρος χρήστης και/ή συνθηματικό της Oracle", +"Offending command was: \"%s\", name: %s, password: %s" => "Η εντολη παραβατικοτητας ηταν: \"%s\", ονομα: %s, κωδικος: %s", +"PostgreSQL username and/or password not valid" => "Μη έγκυρος χρήστης και/ή συνθηματικό της PostgreSQL", +"Set an admin username." => "Εισάγετε όνομα χρήστη διαχειριστή.", +"Set an admin password." => "Εισάγετε συνθηματικό διαχειριστή.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Ο διακομιστής σας δεν έχει ρυθμιστεί κατάλληλα ώστε να επιτρέπει τον συγχρονισμό αρχείων γιατί η διεπαφή WebDAV πιθανόν να είναι κατεστραμμένη.", +"Please double check the <a href='%s'>installation guides</a>." => "Ελέγξτε ξανά τις <a href='%s'>οδηγίες εγκατάστασης</a>.", +"seconds ago" => "δευτερόλεπτα πριν", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "σήμερα", +"yesterday" => "χτες", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "τελευταίο μήνα", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "τελευταίο χρόνο", +"years ago" => "χρόνια πριν", +"Caused by:" => "Προκλήθηκε από:", +"Could not find category \"%s\"" => "Αδυναμία εύρεσης κατηγορίας \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/en@pirate.php b/lib/private/l10n/en@pirate.php new file mode 100644 index 00000000000..a8175b1400f --- /dev/null +++ b/lib/private/l10n/en@pirate.php @@ -0,0 +1,9 @@ +<?php +$TRANSLATIONS = array( +"web services under your control" => "web services under your control", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/en_GB.php b/lib/private/l10n/en_GB.php new file mode 100644 index 00000000000..d02f553eda8 --- /dev/null +++ b/lib/private/l10n/en_GB.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "App \"%s\" can't be installed because it is not compatible with this version of ownCloud.", +"No app name specified" => "No app name specified", +"Help" => "Help", +"Personal" => "Personal", +"Settings" => "Settings", +"Users" => "Users", +"Admin" => "Admin", +"Failed to upgrade \"%s\"." => "Failed to upgrade \"%s\".", +"Custom profile pictures don't work with encryption yet" => "Custom profile pictures don't work with encryption yet", +"Unknown filetype" => "Unknown filetype", +"Invalid image" => "Invalid image", +"web services under your control" => "web services under your control", +"cannot open \"%s\"" => "cannot open \"%s\"", +"ZIP download is turned off." => "ZIP download is turned off.", +"Files need to be downloaded one by one." => "Files need to be downloaded one by one.", +"Back to Files" => "Back to Files", +"Selected files too large to generate zip file." => "Selected files too large to generate zip file.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Download the files in smaller chunks, seperately or kindly ask your administrator.", +"No source specified when installing app" => "No source specified when installing app", +"No href specified when installing app from http" => "No href specified when installing app from http", +"No path specified when installing app from local file" => "No path specified when installing app from local file", +"Archives of type %s are not supported" => "Archives of type %s are not supported", +"Failed to open archive when installing app" => "Failed to open archive when installing app", +"App does not provide an info.xml file" => "App does not provide an info.xml file", +"App can't be installed because of not allowed code in the App" => "App can't be installed because of unallowed code in the App", +"App can't be installed because it is not compatible with this version of ownCloud" => "App can't be installed because it is not compatible with this version of ownCloud", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "App can't be installed because the version in info.xml/version is not the same as the version reported from the app store", +"App directory already exists" => "App directory already exists", +"Can't create app folder. Please fix permissions. %s" => "Can't create app folder. Please fix permissions. %s", +"Application is not enabled" => "Application is not enabled", +"Authentication error" => "Authentication error", +"Token expired. Please reload page." => "Token expired. Please reload page.", +"Files" => "Files", +"Text" => "Text", +"Images" => "Images", +"%s enter the database username." => "%s enter the database username.", +"%s enter the database name." => "%s enter the database name.", +"%s you may not use dots in the database name" => "%s you may not use dots in the database name", +"MS SQL username and/or password not valid: %s" => "MS SQL username and/or password not valid: %s", +"You need to enter either an existing account or the administrator." => "You need to enter either an existing account or the administrator.", +"MySQL username and/or password not valid" => "MySQL username and/or password not valid", +"DB Error: \"%s\"" => "DB Error: \"%s\"", +"Offending command was: \"%s\"" => "Offending command was: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL user '%s'@'localhost' exists already.", +"Drop this user from MySQL" => "Drop this user from MySQL", +"MySQL user '%s'@'%%' already exists" => "MySQL user '%s'@'%%' already exists", +"Drop this user from MySQL." => "Drop this user from MySQL.", +"Oracle connection could not be established" => "Oracle connection could not be established", +"Oracle username and/or password not valid" => "Oracle username and/or password not valid", +"Offending command was: \"%s\", name: %s, password: %s" => "Offending command was: \"%s\", name: %s, password: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL username and/or password not valid", +"Set an admin username." => "Set an admin username.", +"Set an admin password." => "Set an admin password.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Your web server is not yet properly setup to allow files synchronisation because the WebDAV interface seems to be broken.", +"Please double check the <a href='%s'>installation guides</a>." => "Please double check the <a href='%s'>installation guides</a>.", +"seconds ago" => "seconds ago", +"_%n minute ago_::_%n minutes ago_" => array("%n minute ago","%n minutes ago"), +"_%n hour ago_::_%n hours ago_" => array("%n hour ago","%n hours ago"), +"today" => "today", +"yesterday" => "yesterday", +"_%n day go_::_%n days ago_" => array("%n day go","%n days ago"), +"last month" => "last month", +"_%n month ago_::_%n months ago_" => array("%n month ago","%n months ago"), +"last year" => "last year", +"years ago" => "years ago", +"Caused by:" => "Caused by:", +"Could not find category \"%s\"" => "Could not find category \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/eo.php b/lib/private/l10n/eo.php new file mode 100644 index 00000000000..5311dd6eb15 --- /dev/null +++ b/lib/private/l10n/eo.php @@ -0,0 +1,48 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Helpo", +"Personal" => "Persona", +"Settings" => "Agordo", +"Users" => "Uzantoj", +"Admin" => "Administranto", +"web services under your control" => "TTT-servoj regataj de vi", +"ZIP download is turned off." => "ZIP-elŝuto estas malkapabligita.", +"Files need to be downloaded one by one." => "Dosieroj devas elŝutiĝi unuope.", +"Back to Files" => "Reen al la dosieroj", +"Selected files too large to generate zip file." => "La elektitaj dosieroj tro grandas por genero de ZIP-dosiero.", +"Application is not enabled" => "La aplikaĵo ne estas kapabligita", +"Authentication error" => "Aŭtentiga eraro", +"Token expired. Please reload page." => "Ĵetono eksvalidiĝis. Bonvolu reŝargi la paĝon.", +"Files" => "Dosieroj", +"Text" => "Teksto", +"Images" => "Bildoj", +"%s enter the database username." => "%s enigu la uzantonomon de la datumbazo.", +"%s enter the database name." => "%s enigu la nomon de la datumbazo.", +"%s you may not use dots in the database name" => "%s vi ne povas uzi punktojn en la nomo de la datumbazo", +"MS SQL username and/or password not valid: %s" => "La uzantonomo de MS SQL aŭ la pasvorto ne validas: %s", +"MySQL username and/or password not valid" => "La uzantonomo de MySQL aŭ la pasvorto ne validas", +"DB Error: \"%s\"" => "Datumbaza eraro: “%s”", +"MySQL user '%s'@'localhost' exists already." => "La uzanto de MySQL “%s”@“localhost” jam ekzistas.", +"Drop this user from MySQL" => "Forigi ĉi tiun uzanton el MySQL", +"MySQL user '%s'@'%%' already exists" => "La uzanto de MySQL “%s”@“%%” jam ekzistas", +"Drop this user from MySQL." => "Forigi ĉi tiun uzanton el MySQL.", +"Oracle connection could not be established" => "Konekto al Oracle ne povas stariĝi", +"Oracle username and/or password not valid" => "La uzantonomo de Oracle aŭ la pasvorto ne validas", +"PostgreSQL username and/or password not valid" => "La uzantonomo de PostgreSQL aŭ la pasvorto ne validas", +"Set an admin username." => "Starigi administran uzantonomon.", +"Set an admin password." => "Starigi administran pasvorton.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Via TTT-servilo ankoraŭ ne ĝuste agordiĝis por permesi sinkronigi dosierojn ĉar la WebDAV-interfaco ŝajnas rompita.", +"Please double check the <a href='%s'>installation guides</a>." => "Bonvolu duoble kontroli la <a href='%s'>gvidilon por instalo</a>.", +"seconds ago" => "sekundoj antaŭe", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "hodiaŭ", +"yesterday" => "hieraŭ", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "lastamonate", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "lastajare", +"years ago" => "jaroj antaŭe", +"Could not find category \"%s\"" => "Ne troviĝis kategorio “%s”" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/es.php b/lib/private/l10n/es.php new file mode 100644 index 00000000000..047d5d955bb --- /dev/null +++ b/lib/private/l10n/es.php @@ -0,0 +1,69 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "La aplicación \"%s\" no puede ser instalada porque no es compatible con esta versión de ownCloud", +"No app name specified" => "No se ha especificado nombre de la aplicación", +"Help" => "Ayuda", +"Personal" => "Personal", +"Settings" => "Ajustes", +"Users" => "Usuarios", +"Admin" => "Administración", +"Failed to upgrade \"%s\"." => "Falló la actualización \"%s\".", +"web services under your control" => "Servicios web bajo su control", +"cannot open \"%s\"" => "No se puede abrir \"%s\"", +"ZIP download is turned off." => "La descarga en ZIP está desactivada.", +"Files need to be downloaded one by one." => "Los archivos deben ser descargados uno por uno.", +"Back to Files" => "Volver a Archivos", +"Selected files too large to generate zip file." => "Los archivos seleccionados son demasiado grandes para generar el archivo zip.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Descargue los archivos en trozos más pequeños, por separado o solicítelos amablemente su administrador.", +"No source specified when installing app" => "No se ha especificado origen cuando se ha instalado la aplicación", +"No href specified when installing app from http" => "No href especificado cuando se ha instalado la aplicación", +"No path specified when installing app from local file" => "Sin path especificado cuando se ha instalado la aplicación desde el fichero local", +"Archives of type %s are not supported" => "Ficheros de tipo %s no son soportados", +"Failed to open archive when installing app" => "Fallo de apertura de fichero mientras se instala la aplicación", +"App does not provide an info.xml file" => "La aplicación no suministra un fichero info.xml", +"App can't be installed because of not allowed code in the App" => "La aplicación no puede ser instalada por tener código no autorizado en la aplicación", +"App can't be installed because it is not compatible with this version of ownCloud" => "La aplicación no se puede instalar porque no es compatible con esta versión de ownCloud", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "La aplicación no se puede instalar porque contiene la etiqueta\n<shipped>\ntrue\n</shipped>\nque no está permitida para aplicaciones no distribuidas", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "La aplicación no puede ser instalada por que la versión en info.xml/version no es la misma que la establecida en la app store", +"App directory already exists" => "El directorio de la aplicación ya existe", +"Can't create app folder. Please fix permissions. %s" => "No se puede crear la carpeta de la aplicación. Corrija los permisos. %s", +"Application is not enabled" => "La aplicación no está habilitada", +"Authentication error" => "Error de autenticación", +"Token expired. Please reload page." => "Token expirado. Por favor, recarga la página.", +"Files" => "Archivos", +"Text" => "Texto", +"Images" => "Imágenes", +"%s enter the database username." => "%s ingresar el usuario de la base de datos.", +"%s enter the database name." => "%s ingresar el nombre de la base de datos", +"%s you may not use dots in the database name" => "%s puede utilizar puntos en el nombre de la base de datos", +"MS SQL username and/or password not valid: %s" => "Usuario y/o contraseña de MS SQL no válidos: %s", +"You need to enter either an existing account or the administrator." => "Tiene que ingresar una cuenta existente o la del administrador.", +"MySQL username and/or password not valid" => "Usuario y/o contraseña de MySQL no válidos", +"DB Error: \"%s\"" => "Error BD: \"%s\"", +"Offending command was: \"%s\"" => "Comando infractor: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "Usuario MySQL '%s'@'localhost' ya existe.", +"Drop this user from MySQL" => "Eliminar este usuario de MySQL", +"MySQL user '%s'@'%%' already exists" => "Usuario MySQL '%s'@'%%' ya existe", +"Drop this user from MySQL." => "Eliminar este usuario de MySQL.", +"Oracle connection could not be established" => "No se pudo establecer la conexión a Oracle", +"Oracle username and/or password not valid" => "Usuario y/o contraseña de Oracle no válidos", +"Offending command was: \"%s\", name: %s, password: %s" => "Comando infractor: \"%s\", nombre: %s, contraseña: %s", +"PostgreSQL username and/or password not valid" => "Usuario y/o contraseña de PostgreSQL no válidos", +"Set an admin username." => "Configurar un nombre de usuario del administrador", +"Set an admin password." => "Configurar la contraseña del administrador.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Su servidor web aún no está configurado adecuadamente para permitir sincronización de archivos ya que la interfaz WebDAV parece no estar funcionando.", +"Please double check the <a href='%s'>installation guides</a>." => "Por favor, vuelva a comprobar las <a href='%s'>guías de instalación</a>.", +"seconds ago" => "hace segundos", +"_%n minute ago_::_%n minutes ago_" => array("Hace %n minuto","Hace %n minutos"), +"_%n hour ago_::_%n hours ago_" => array("Hace %n hora","Hace %n horas"), +"today" => "hoy", +"yesterday" => "ayer", +"_%n day go_::_%n days ago_" => array("Hace %n día","Hace %n días"), +"last month" => "mes pasado", +"_%n month ago_::_%n months ago_" => array("Hace %n mes","Hace %n meses"), +"last year" => "año pasado", +"years ago" => "hace años", +"Caused by:" => "Causado por:", +"Could not find category \"%s\"" => "No puede encontrar la categoria \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/es_AR.php b/lib/private/l10n/es_AR.php new file mode 100644 index 00000000000..f637eb403ed --- /dev/null +++ b/lib/private/l10n/es_AR.php @@ -0,0 +1,69 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "La app \"%s\" no puede ser instalada porque no es compatible con esta versión de ownCloud", +"No app name specified" => "No fue especificado el nombre de la app", +"Help" => "Ayuda", +"Personal" => "Personal", +"Settings" => "Configuración", +"Users" => "Usuarios", +"Admin" => "Administración", +"Failed to upgrade \"%s\"." => "No se pudo actualizar \"%s\".", +"web services under your control" => "servicios web sobre los que tenés control", +"cannot open \"%s\"" => "no se puede abrir \"%s\"", +"ZIP download is turned off." => "La descarga en ZIP está desactivada.", +"Files need to be downloaded one by one." => "Los archivos deben ser descargados de a uno.", +"Back to Files" => "Volver a Archivos", +"Selected files too large to generate zip file." => "Los archivos seleccionados son demasiado grandes para generar el archivo zip.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Descargá los archivos en partes más chicas, de forma separada, o pedíselos al administrador", +"No source specified when installing app" => "No se especificó el origen al instalar la app", +"No href specified when installing app from http" => "No se especificó href al instalar la app", +"No path specified when installing app from local file" => "No se especificó PATH al instalar la app desde el archivo local", +"Archives of type %s are not supported" => "No hay soporte para archivos de tipo %s", +"Failed to open archive when installing app" => "Error al abrir archivo mientras se instalaba la app", +"App does not provide an info.xml file" => "La app no suministra un archivo info.xml", +"App can't be installed because of not allowed code in the App" => "No puede ser instalada la app por tener código no autorizado", +"App can't be installed because it is not compatible with this version of ownCloud" => "No se puede instalar la app porque no es compatible con esta versión de ownCloud", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "La app no se puede instalar porque contiene la etiqueta <shipped>true</shipped> que no está permitida para apps no distribuidas", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "La app no puede ser instalada porque la versión en info.xml/version no es la misma que la establecida en el app store", +"App directory already exists" => "El directorio de la app ya existe", +"Can't create app folder. Please fix permissions. %s" => "No se puede crear el directorio para la app. Corregí los permisos. %s", +"Application is not enabled" => "La aplicación no está habilitada", +"Authentication error" => "Error al autenticar", +"Token expired. Please reload page." => "Token expirado. Por favor, recargá la página.", +"Files" => "Archivos", +"Text" => "Texto", +"Images" => "Imágenes", +"%s enter the database username." => "%s Entrá el usuario de la base de datos", +"%s enter the database name." => "%s Entrá el nombre de la base de datos.", +"%s you may not use dots in the database name" => "%s no podés usar puntos en el nombre de la base de datos", +"MS SQL username and/or password not valid: %s" => "Nombre de usuario y contraseña de MS SQL no son válidas: %s", +"You need to enter either an existing account or the administrator." => "Tenés que ingresar una cuenta existente o el administrador.", +"MySQL username and/or password not valid" => "Usuario y/o contraseña MySQL no válido", +"DB Error: \"%s\"" => "Error DB: \"%s\"", +"Offending command was: \"%s\"" => "El comando no comprendido es: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "Usuario MySQL '%s'@'localhost' ya existe.", +"Drop this user from MySQL" => "Borrar este usuario de MySQL", +"MySQL user '%s'@'%%' already exists" => "Usuario MySQL '%s'@'%%' ya existe", +"Drop this user from MySQL." => "Borrar este usuario de MySQL", +"Oracle connection could not be established" => "No fue posible establecer la conexión a Oracle", +"Oracle username and/or password not valid" => "El nombre de usuario y/o contraseña no son válidos", +"Offending command was: \"%s\", name: %s, password: %s" => "El comando no comprendido es: \"%s\", nombre: \"%s\", contraseña: \"%s\"", +"PostgreSQL username and/or password not valid" => "Nombre de usuario o contraseña PostgradeSQL inválido.", +"Set an admin username." => "Configurar un nombre de administrador.", +"Set an admin password." => "Configurar una contraseña de administrador.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Tu servidor web no está configurado todavía para permitir sincronización de archivos porque la interfaz WebDAV parece no funcionar.", +"Please double check the <a href='%s'>installation guides</a>." => "Por favor, comprobá nuevamente la <a href='%s'>guía de instalación</a>.", +"seconds ago" => "segundos atrás", +"_%n minute ago_::_%n minutes ago_" => array("Hace %n minuto","Hace %n minutos"), +"_%n hour ago_::_%n hours ago_" => array("Hace %n hora","Hace %n horas"), +"today" => "hoy", +"yesterday" => "ayer", +"_%n day go_::_%n days ago_" => array("Hace %n día","Hace %n días"), +"last month" => "el mes pasado", +"_%n month ago_::_%n months ago_" => array("Hace %n mes","Hace %n meses"), +"last year" => "el año pasado", +"years ago" => "años atrás", +"Caused by:" => "Provocado por:", +"Could not find category \"%s\"" => "No fue posible encontrar la categoría \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/es_MX.php b/lib/private/l10n/es_MX.php new file mode 100644 index 00000000000..15f78e0bce6 --- /dev/null +++ b/lib/private/l10n/es_MX.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/et_EE.php b/lib/private/l10n/et_EE.php new file mode 100644 index 00000000000..85dfaeb52d5 --- /dev/null +++ b/lib/private/l10n/et_EE.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Rakendit \"%s\" ei saa paigaldada, kuna see pole ühilduv selle ownCloud versiooniga.", +"No app name specified" => "Ühegi rakendi nime pole määratletud", +"Help" => "Abiinfo", +"Personal" => "Isiklik", +"Settings" => "Seaded", +"Users" => "Kasutajad", +"Admin" => "Admin", +"Failed to upgrade \"%s\"." => "Ebaõnnestunud uuendus \"%s\".", +"Custom profile pictures don't work with encryption yet" => "Kohandatud profiili pildid ei toimi veel koos krüpteeringuga", +"Unknown filetype" => "Tundmatu failitüüp", +"Invalid image" => "Vigane pilt", +"web services under your control" => "veebitenused sinu kontrolli all", +"cannot open \"%s\"" => "ei suuda avada \"%s\"", +"ZIP download is turned off." => "ZIP-ina allalaadimine on välja lülitatud.", +"Files need to be downloaded one by one." => "Failid tuleb alla laadida ükshaaval.", +"Back to Files" => "Tagasi failide juurde", +"Selected files too large to generate zip file." => "Valitud failid on ZIP-faili loomiseks liiga suured.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Laadi failid alla eraldi väiksemate osadena või küsi nõu oma süsteemiadminstraatorilt.", +"No source specified when installing app" => "Ühegi lähteallikat pole rakendi paigalduseks määratletud", +"No href specified when installing app from http" => "Ühtegi aadressi pole määratletud rakendi paigalduseks veebist", +"No path specified when installing app from local file" => "Ühtegi teed pole määratletud paigaldamaks rakendit kohalikust failist", +"Archives of type %s are not supported" => "%s tüüpi arhiivid pole toetatud", +"Failed to open archive when installing app" => "Arhiivi avamine ebaõnnestus rakendi paigalduse käigus", +"App does not provide an info.xml file" => "Rakend ei paku ühtegi info.xml faili", +"App can't be installed because of not allowed code in the App" => "Rakendit ei saa paigaldada, kuna sisaldab lubamatud koodi", +"App can't be installed because it is not compatible with this version of ownCloud" => "Rakendit ei saa paigaldada, kuna see pole ühilduv selle ownCloud versiooniga.", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "Rakendit ei saa paigaldada, kuna see sisaldab \n<shipped>\n\ntrue\n</shipped>\nmärgendit, mis pole lubatud mitte veetud (non shipped) rakendites", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "Rakendit ei saa paigaldada, kuna selle versioon info.xml/version pole sama, mis on märgitud rakendite laos.", +"App directory already exists" => "Rakendi kataloog on juba olemas", +"Can't create app folder. Please fix permissions. %s" => "Ei saa luua rakendi kataloogi. Palun korrigeeri õigusi. %s", +"Application is not enabled" => "Rakendus pole sisse lülitatud", +"Authentication error" => "Autentimise viga", +"Token expired. Please reload page." => "Kontrollkood aegus. Paelun lae leht uuesti.", +"Files" => "Failid", +"Text" => "Tekst", +"Images" => "Pildid", +"%s enter the database username." => "%s sisesta andmebaasi kasutajatunnus.", +"%s enter the database name." => "%s sisesta andmebaasi nimi.", +"%s you may not use dots in the database name" => "%s punktide kasutamine andmebaasi nimes pole lubatud", +"MS SQL username and/or password not valid: %s" => "MS SQL kasutajatunnus ja/või parool pole õiged: %s", +"You need to enter either an existing account or the administrator." => "Sisesta kas juba olemasolev konto või administrator.", +"MySQL username and/or password not valid" => "MySQL kasutajatunnus ja/või parool pole õiged", +"DB Error: \"%s\"" => "Andmebaasi viga: \"%s\"", +"Offending command was: \"%s\"" => "Tõrkuv käsk oli: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL kasutaja '%s'@'localhost' on juba olemas.", +"Drop this user from MySQL" => "Kustuta see kasutaja MySQL-ist", +"MySQL user '%s'@'%%' already exists" => "MySQL kasutaja '%s'@'%%' on juba olemas", +"Drop this user from MySQL." => "Kustuta see kasutaja MySQL-ist.", +"Oracle connection could not be established" => "Ei suuda luua ühendust Oracle baasiga", +"Oracle username and/or password not valid" => "Oracle kasutajatunnus ja/või parool pole õiged", +"Offending command was: \"%s\", name: %s, password: %s" => "Tõrkuv käsk oli: \"%s\", nimi: %s, parool: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL kasutajatunnus ja/või parool pole õiged", +"Set an admin username." => "Määra admin kasutajanimi.", +"Set an admin password." => "Määra admini parool.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Veebiserveri ei ole veel korralikult seadistatud võimaldamaks failide sünkroniseerimist, kuna WebDAV liides näib olevat mittetoimiv.", +"Please double check the <a href='%s'>installation guides</a>." => "Palun tutvu veelkord <a href='%s'>paigalduse juhenditega</a>.", +"seconds ago" => "sekundit tagasi", +"_%n minute ago_::_%n minutes ago_" => array("","%n minutit tagasi"), +"_%n hour ago_::_%n hours ago_" => array("","%n tundi tagasi"), +"today" => "täna", +"yesterday" => "eile", +"_%n day go_::_%n days ago_" => array("","%n päeva tagasi"), +"last month" => "viimasel kuul", +"_%n month ago_::_%n months ago_" => array("","%n kuud tagasi"), +"last year" => "viimasel aastal", +"years ago" => "aastat tagasi", +"Caused by:" => "Põhjustaja:", +"Could not find category \"%s\"" => "Ei leia kategooriat \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/eu.php b/lib/private/l10n/eu.php new file mode 100644 index 00000000000..413819f4f94 --- /dev/null +++ b/lib/private/l10n/eu.php @@ -0,0 +1,55 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Laguntza", +"Personal" => "Pertsonala", +"Settings" => "Ezarpenak", +"Users" => "Erabiltzaileak", +"Admin" => "Admin", +"Failed to upgrade \"%s\"." => "Ezin izan da \"%s\" eguneratu.", +"web services under your control" => "web zerbitzuak zure kontrolpean", +"cannot open \"%s\"" => "ezin da \"%s\" ireki", +"ZIP download is turned off." => "ZIP deskarga ez dago gaituta.", +"Files need to be downloaded one by one." => "Fitxategiak banan-banan deskargatu behar dira.", +"Back to Files" => "Itzuli fitxategietara", +"Selected files too large to generate zip file." => "Hautatuko fitxategiak oso handiak dira zip fitxategia sortzeko.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Deskargatu fitzategiak zati txikiagoetan, banan-banan edo eskatu mesedez zure administradoreari", +"Application is not enabled" => "Aplikazioa ez dago gaituta", +"Authentication error" => "Autentifikazio errorea", +"Token expired. Please reload page." => "Tokena iraungitu da. Mesedez birkargatu orria.", +"Files" => "Fitxategiak", +"Text" => "Testua", +"Images" => "Irudiak", +"%s enter the database username." => "%s sartu datu basearen erabiltzaile izena.", +"%s enter the database name." => "%s sartu datu basearen izena.", +"%s you may not use dots in the database name" => "%s ezin duzu punturik erabili datu basearen izenean.", +"MS SQL username and/or password not valid: %s" => "MS SQL erabiltzaile izena edota pasahitza ez dira egokiak: %s", +"You need to enter either an existing account or the administrator." => "Existitzen den kontu bat edo administradorearena jarri behar duzu.", +"MySQL username and/or password not valid" => "MySQL erabiltzaile edota pasahitza ez dira egokiak.", +"DB Error: \"%s\"" => "DB errorea: \"%s\"", +"Offending command was: \"%s\"" => "Errorea komando honek sortu du: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL '%s'@'localhost' erabiltzailea dagoeneko existitzen da.", +"Drop this user from MySQL" => "Ezabatu erabiltzaile hau MySQLtik", +"MySQL user '%s'@'%%' already exists" => "MySQL '%s'@'%%' erabiltzailea dagoeneko existitzen da", +"Drop this user from MySQL." => "Ezabatu erabiltzaile hau MySQLtik.", +"Oracle connection could not be established" => "Ezin da Oracle konexioa sortu", +"Oracle username and/or password not valid" => "Oracle erabiltzaile edota pasahitza ez dira egokiak.", +"Offending command was: \"%s\", name: %s, password: %s" => "Errorea komando honek sortu du: \"%s\", izena: %s, pasahitza: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL erabiltzaile edota pasahitza ez dira egokiak.", +"Set an admin username." => "Ezarri administraziorako erabiltzaile izena.", +"Set an admin password." => "Ezarri administraziorako pasahitza.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Zure web zerbitzaria ez dago oraindik ongi konfiguratuta fitxategien sinkronizazioa egiteko, WebDAV interfazea ongi ez dagoela dirudi.", +"Please double check the <a href='%s'>installation guides</a>." => "Mesedez begiratu <a href='%s'>instalazio gidak</a>.", +"seconds ago" => "segundu", +"_%n minute ago_::_%n minutes ago_" => array("orain dela minutu %n","orain dela %n minutu"), +"_%n hour ago_::_%n hours ago_" => array("orain dela ordu %n","orain dela %n ordu"), +"today" => "gaur", +"yesterday" => "atzo", +"_%n day go_::_%n days ago_" => array("orain dela egun %n","orain dela %n egun"), +"last month" => "joan den hilabetean", +"_%n month ago_::_%n months ago_" => array("orain dela hilabete %n","orain dela %n hilabete"), +"last year" => "joan den urtean", +"years ago" => "urte", +"Caused by:" => "Honek eraginda:", +"Could not find category \"%s\"" => "Ezin da \"%s\" kategoria aurkitu" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/fa.php b/lib/private/l10n/fa.php new file mode 100644 index 00000000000..e9cb695bade --- /dev/null +++ b/lib/private/l10n/fa.php @@ -0,0 +1,51 @@ +<?php +$TRANSLATIONS = array( +"Help" => "راهنما", +"Personal" => "شخصی", +"Settings" => "تنظیمات", +"Users" => "کاربران", +"Admin" => "مدیر", +"web services under your control" => "سرویس های تحت وب در کنترل شما", +"ZIP download is turned off." => "دانلود به صورت فشرده غیر فعال است", +"Files need to be downloaded one by one." => "فایل ها باید به صورت یکی یکی دانلود شوند", +"Back to Files" => "بازگشت به فایل ها", +"Selected files too large to generate zip file." => "فایل های انتخاب شده بزرگتر از آن هستند که بتوان یک فایل فشرده تولید کرد", +"Application is not enabled" => "برنامه فعال نشده است", +"Authentication error" => "خطا در اعتبار سنجی", +"Token expired. Please reload page." => "رمز منقضی شده است. لطفا دوباره صفحه را بارگذاری نمایید.", +"Files" => "پروندهها", +"Text" => "متن", +"Images" => "تصاویر", +"%s enter the database username." => "%s نام کاربری پایگاه داده را وارد نمایید.", +"%s enter the database name." => "%s نام پایگاه داده را وارد نمایید.", +"%s you may not use dots in the database name" => "%s شما نباید از نقطه در نام پایگاه داده استفاده نمایید.", +"MS SQL username and/or password not valid: %s" => "نام کاربری و / یا رمزعبور MS SQL معتبر نیست: %s", +"You need to enter either an existing account or the administrator." => "شما نیاز به وارد کردن یک حساب کاربری موجود یا حساب مدیریتی دارید.", +"MySQL username and/or password not valid" => "نام کاربری و / یا رمزعبور MySQL معتبر نیست.", +"DB Error: \"%s\"" => "خطای پایگاه داده: \"%s\"", +"Offending command was: \"%s\"" => "دستور متخلف عبارت است از: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "کاربرMySQL '%s'@'localhost' درحال حاضر موجود است.", +"Drop this user from MySQL" => "این کاربر را از MySQL حذف نمایید.", +"MySQL user '%s'@'%%' already exists" => "کاربر'%s'@'%%' MySQL در حال حاضر موجود است.", +"Drop this user from MySQL." => "این کاربر را از MySQL حذف نمایید.", +"Oracle connection could not be established" => "ارتباط اراکل نمیتواند برقرار باشد.", +"Oracle username and/or password not valid" => "نام کاربری و / یا رمزعبور اراکل معتبر نیست.", +"Offending command was: \"%s\", name: %s, password: %s" => "دستور متخلف عبارت است از: \"%s\"، نام: \"%s\"، رمزعبور:\"%s\"", +"PostgreSQL username and/or password not valid" => "PostgreSQL نام کاربری و / یا رمزعبور معتبر نیست.", +"Set an admin username." => "یک نام کاربری برای مدیر تنظیم نمایید.", +"Set an admin password." => "یک رمزعبور برای مدیر تنظیم نمایید.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "احتمالاً وب سرور شما طوری تنظیم نشده است که اجازه ی همگام سازی فایلها را بدهد زیرا به نظر میرسد رابط WebDAV از کار افتاده است.", +"Please double check the <a href='%s'>installation guides</a>." => "لطفاً دوباره <a href='%s'>راهنمای نصب</a>را بررسی کنید.", +"seconds ago" => "ثانیهها پیش", +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"today" => "امروز", +"yesterday" => "دیروز", +"_%n day go_::_%n days ago_" => array(""), +"last month" => "ماه قبل", +"_%n month ago_::_%n months ago_" => array(""), +"last year" => "سال قبل", +"years ago" => "سالهای قبل", +"Could not find category \"%s\"" => "دسته بندی %s یافت نشد" +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/fi.php b/lib/private/l10n/fi.php new file mode 100644 index 00000000000..ac1f80a8f73 --- /dev/null +++ b/lib/private/l10n/fi.php @@ -0,0 +1,5 @@ +<?php +$TRANSLATIONS = array( +"Settings" => "asetukset" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/fi_FI.php b/lib/private/l10n/fi_FI.php new file mode 100644 index 00000000000..1d2bdab749c --- /dev/null +++ b/lib/private/l10n/fi_FI.php @@ -0,0 +1,62 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Sovellusta \"%s\" ei voi asentaa, koska se ei ole yhteensopiva käytössä olevan ownCloud-version kanssa.", +"No app name specified" => "Sovelluksen nimeä ei määritelty", +"Help" => "Ohje", +"Personal" => "Henkilökohtainen", +"Settings" => "Asetukset", +"Users" => "Käyttäjät", +"Admin" => "Ylläpitäjä", +"Failed to upgrade \"%s\"." => "Kohteen \"%s\" päivitys epäonnistui.", +"Custom profile pictures don't work with encryption yet" => "Omavalintaiset profiilikuvat eivät toimi salauksen kanssa vielä", +"Unknown filetype" => "Tuntematon tiedostotyyppi", +"Invalid image" => "Virheellinen kuva", +"web services under your control" => "verkkopalvelut hallinnassasi", +"ZIP download is turned off." => "ZIP-lataus on poistettu käytöstä.", +"Files need to be downloaded one by one." => "Tiedostot on ladattava yksittäin.", +"Back to Files" => "Takaisin tiedostoihin", +"Selected files too large to generate zip file." => "Valitut tiedostot ovat liian suurikokoisia mahtuakseen zip-tiedostoon.", +"No source specified when installing app" => "Lähdettä ei määritelty sovellusta asennettaessa", +"No path specified when installing app from local file" => "Polkua ei määritelty sovellusta asennettaessa paikallisesta tiedostosta", +"Archives of type %s are not supported" => "Tyypin %s arkistot eivät ole tuettuja", +"App does not provide an info.xml file" => "Sovellus ei sisällä info.xml-tiedostoa", +"App can't be installed because of not allowed code in the App" => "Sovellusta ei voi asentaa, koska sovellus sisältää kiellettyä koodia", +"App can't be installed because it is not compatible with this version of ownCloud" => "Sovellusta ei voi asentaa, koska se ei ole yhteensopiva käytössä olevan ownCloud-version kanssa", +"App directory already exists" => "Sovelluskansio on jo olemassa", +"Can't create app folder. Please fix permissions. %s" => "Sovelluskansion luominen ei onnistu. Korjaa käyttöoikeudet. %s", +"Application is not enabled" => "Sovellusta ei ole otettu käyttöön", +"Authentication error" => "Tunnistautumisvirhe", +"Token expired. Please reload page." => "Valtuutus vanheni. Lataa sivu uudelleen.", +"Files" => "Tiedostot", +"Text" => "Teksti", +"Images" => "Kuvat", +"%s enter the database username." => "%s anna tietokannan käyttäjätunnus.", +"%s enter the database name." => "%s anna tietokannan nimi.", +"%s you may not use dots in the database name" => "%s et voi käyttää pisteitä tietokannan nimessä", +"MS SQL username and/or password not valid: %s" => "MS SQL -käyttäjätunnus ja/tai -salasana on väärin: %s", +"MySQL username and/or password not valid" => "MySQL:n käyttäjätunnus ja/tai salasana on väärin", +"DB Error: \"%s\"" => "Tietokantavirhe: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL-käyttäjä '%s'@'localhost' on jo olemassa.", +"Drop this user from MySQL" => "Pudota tämä käyttäjä MySQL:stä", +"MySQL user '%s'@'%%' already exists" => "MySQL-käyttäjä '%s'@'%%' on jo olemassa", +"Drop this user from MySQL." => "Pudota tämä käyttäjä MySQL:stä.", +"Oracle connection could not be established" => "Oracle-yhteyttä ei voitu muodostaa", +"Oracle username and/or password not valid" => "Oraclen käyttäjätunnus ja/tai salasana on väärin", +"PostgreSQL username and/or password not valid" => "PostgreSQL:n käyttäjätunnus ja/tai salasana on väärin", +"Set an admin username." => "Aseta ylläpitäjän käyttäjätunnus.", +"Set an admin password." => "Aseta ylläpitäjän salasana.", +"Please double check the <a href='%s'>installation guides</a>." => "Lue tarkasti <a href='%s'>asennusohjeet</a>.", +"seconds ago" => "sekuntia sitten", +"_%n minute ago_::_%n minutes ago_" => array("%n minuutti sitten","%n minuuttia sitten"), +"_%n hour ago_::_%n hours ago_" => array("%n tunti sitten","%n tuntia sitten"), +"today" => "tänään", +"yesterday" => "eilen", +"_%n day go_::_%n days ago_" => array("%n päivä sitten","%n päivää sitten"), +"last month" => "viime kuussa", +"_%n month ago_::_%n months ago_" => array("%n kuukausi sitten","%n kuukautta sitten"), +"last year" => "viime vuonna", +"years ago" => "vuotta sitten", +"Caused by:" => "Aiheuttaja:", +"Could not find category \"%s\"" => "Luokkaa \"%s\" ei löytynyt" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/fr.php b/lib/private/l10n/fr.php new file mode 100644 index 00000000000..ab3d618849e --- /dev/null +++ b/lib/private/l10n/fr.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "L'application \"%s\" ne peut être installée car elle n'est pas compatible avec cette version de ownCloud.", +"No app name specified" => "Aucun nom d'application spécifié", +"Help" => "Aide", +"Personal" => "Personnel", +"Settings" => "Paramètres", +"Users" => "Utilisateurs", +"Admin" => "Administration", +"Failed to upgrade \"%s\"." => "Echec de la mise à niveau \"%s\".", +"Custom profile pictures don't work with encryption yet" => "Les images de profil personnalisées ne fonctionnent pas encore avec le système de chiffrement.", +"Unknown filetype" => "Type de fichier inconnu", +"Invalid image" => "Image invalide", +"web services under your control" => "services web sous votre contrôle", +"cannot open \"%s\"" => "impossible d'ouvrir \"%s\"", +"ZIP download is turned off." => "Téléchargement ZIP désactivé.", +"Files need to be downloaded one by one." => "Les fichiers nécessitent d'être téléchargés un par un.", +"Back to Files" => "Retour aux Fichiers", +"Selected files too large to generate zip file." => "Les fichiers sélectionnés sont trop volumineux pour être compressés.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Télécharger les fichiers en parties plus petites, séparément ou demander avec bienveillance à votre administrateur.", +"No source specified when installing app" => "Aucune source spécifiée pour installer l'application", +"No href specified when installing app from http" => "Aucun href spécifié pour installer l'application par http", +"No path specified when installing app from local file" => "Aucun chemin spécifié pour installer l'application depuis un fichier local", +"Archives of type %s are not supported" => "Les archives de type %s ne sont pas supportées", +"Failed to open archive when installing app" => "Échec de l'ouverture de l'archive lors de l'installation de l'application", +"App does not provide an info.xml file" => "L'application ne fournit pas de fichier info.xml", +"App can't be installed because of not allowed code in the App" => "L'application ne peut être installée car elle contient du code non-autorisé", +"App can't be installed because it is not compatible with this version of ownCloud" => "L'application ne peut être installée car elle n'est pas compatible avec cette version de ownCloud", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "L'application ne peut être installée car elle contient la balise <shipped>true</shipped> qui n'est pas autorisée pour les applications non-diffusées", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "L'application ne peut être installée car la version de info.xml/version n'est identique à celle indiquée sur l'app store", +"App directory already exists" => "Le dossier de l'application existe déjà", +"Can't create app folder. Please fix permissions. %s" => "Impossible de créer le dossier de l'application. Corrigez les droits d'accès. %s", +"Application is not enabled" => "L'application n'est pas activée", +"Authentication error" => "Erreur d'authentification", +"Token expired. Please reload page." => "La session a expiré. Veuillez recharger la page.", +"Files" => "Fichiers", +"Text" => "Texte", +"Images" => "Images", +"%s enter the database username." => "%s entrez le nom d'utilisateur de la base de données.", +"%s enter the database name." => "%s entrez le nom de la base de données.", +"%s you may not use dots in the database name" => "%s vous nez pouvez pas utiliser de points dans le nom de la base de données", +"MS SQL username and/or password not valid: %s" => "Le nom d'utilisateur et/ou le mot de passe de la base MS SQL est invalide : %s", +"You need to enter either an existing account or the administrator." => "Vous devez spécifier soit le nom d'un compte existant, soit celui de l'administrateur.", +"MySQL username and/or password not valid" => "Nom d'utilisateur et/ou mot de passe de la base MySQL invalide", +"DB Error: \"%s\"" => "Erreur de la base de données : \"%s\"", +"Offending command was: \"%s\"" => "La requête en cause est : \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "L'utilisateur MySQL '%s'@'localhost' existe déjà.", +"Drop this user from MySQL" => "Retirer cet utilisateur de la base MySQL", +"MySQL user '%s'@'%%' already exists" => "L'utilisateur MySQL '%s'@'%%' existe déjà", +"Drop this user from MySQL." => "Retirer cet utilisateur de la base MySQL.", +"Oracle connection could not be established" => "La connexion Oracle ne peut pas être établie", +"Oracle username and/or password not valid" => "Nom d'utilisateur et/ou mot de passe de la base Oracle invalide", +"Offending command was: \"%s\", name: %s, password: %s" => "La requête en cause est : \"%s\", nom : %s, mot de passe : %s", +"PostgreSQL username and/or password not valid" => "Nom d'utilisateur et/ou mot de passe de la base PostgreSQL invalide", +"Set an admin username." => "Spécifiez un nom d'utilisateur pour l'administrateur.", +"Set an admin password." => "Spécifiez un mot de passe administrateur.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Votre serveur web, n'est pas correctement configuré pour permettre la synchronisation des fichiers, car l'interface WebDav ne fonctionne pas comme il faut.", +"Please double check the <a href='%s'>installation guides</a>." => "Veuillez vous référer au <a href='%s'>guide d'installation</a>.", +"seconds ago" => "il y a quelques secondes", +"_%n minute ago_::_%n minutes ago_" => array("","il y a %n minutes"), +"_%n hour ago_::_%n hours ago_" => array("","Il y a %n heures"), +"today" => "aujourd'hui", +"yesterday" => "hier", +"_%n day go_::_%n days ago_" => array("","il y a %n jours"), +"last month" => "le mois dernier", +"_%n month ago_::_%n months ago_" => array("","Il y a %n mois"), +"last year" => "l'année dernière", +"years ago" => "il y a plusieurs années", +"Caused by:" => "Causé par :", +"Could not find category \"%s\"" => "Impossible de trouver la catégorie \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n > 1);"; diff --git a/lib/private/l10n/gl.php b/lib/private/l10n/gl.php new file mode 100644 index 00000000000..406272d690f --- /dev/null +++ b/lib/private/l10n/gl.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Non é posíbel instalar o aplicativo «%s» por non seren compatíbel con esta versión do ownCloud.", +"No app name specified" => "Non se especificou o nome do aplicativo", +"Help" => "Axuda", +"Personal" => "Persoal", +"Settings" => "Axustes", +"Users" => "Usuarios", +"Admin" => "Administración", +"Failed to upgrade \"%s\"." => "Non foi posíbel anovar «%s».", +"Custom profile pictures don't work with encryption yet" => "As imaxes personalizadas de perfil aínda non funcionan co cifrado", +"Unknown filetype" => "Tipo de ficheiro descoñecido", +"Invalid image" => "Imaxe incorrecta", +"web services under your control" => "servizos web baixo o seu control", +"cannot open \"%s\"" => "non foi posíbel abrir «%s»", +"ZIP download is turned off." => "As descargas ZIP están desactivadas.", +"Files need to be downloaded one by one." => "Os ficheiros necesitan seren descargados dun en un.", +"Back to Files" => "Volver aos ficheiros", +"Selected files too large to generate zip file." => "Os ficheiros seleccionados son demasiado grandes como para xerar un ficheiro zip.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Descargue os ficheiros en cachos máis pequenos e por separado, ou pídallos amabelmente ao seu administrador.", +"No source specified when installing app" => "Non foi especificada ningunha orixe ao instalar aplicativos", +"No href specified when installing app from http" => "Non foi especificada ningunha href ao instalar aplicativos", +"No path specified when installing app from local file" => "Non foi especificada ningunha ruta ao instalar aplicativos desde un ficheiro local", +"Archives of type %s are not supported" => "Os arquivos do tipo %s non están admitidos", +"Failed to open archive when installing app" => "Non foi posíbel abrir o arquivo ao instalar aplicativos", +"App does not provide an info.xml file" => "O aplicativo non fornece un ficheiro info.xml", +"App can't be installed because of not allowed code in the App" => "Non é posíbel instalar o aplicativo por mor de conter código non permitido", +"App can't be installed because it is not compatible with this version of ownCloud" => "Non é posíbel instalar o aplicativo por non seren compatíbel con esta versión do ownCloud.", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "Non é posíbel instalar o aplicativo por conter a etiqueta\n<shipped>\n\ntrue\n</shipped>\nque non está permitida para os aplicativos non enviados", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "Non é posíbel instalar o aplicativo xa que a versión en info.xml/version non é a mesma que a versión informada desde a App Store", +"App directory already exists" => "Xa existe o directorio do aplicativo", +"Can't create app folder. Please fix permissions. %s" => "Non é posíbel crear o cartafol de aplicativos. Corrixa os permisos. %s", +"Application is not enabled" => "O aplicativo non está activado", +"Authentication error" => "Produciuse un erro de autenticación", +"Token expired. Please reload page." => "Testemuña caducada. Recargue a páxina.", +"Files" => "Ficheiros", +"Text" => "Texto", +"Images" => "Imaxes", +"%s enter the database username." => "%s introduza o nome de usuario da base de datos", +"%s enter the database name." => "%s introduza o nome da base de datos", +"%s you may not use dots in the database name" => "%s non se poden empregar puntos na base de datos", +"MS SQL username and/or password not valid: %s" => "Nome de usuario e/ou contrasinal de MS SQL incorrecto: %s", +"You need to enter either an existing account or the administrator." => "Deberá introducir unha conta existente ou o administrador.", +"MySQL username and/or password not valid" => "Nome de usuario e/ou contrasinal de MySQL incorrecto", +"DB Error: \"%s\"" => "Produciuse un erro na base de datos: «%s»", +"Offending command was: \"%s\"" => "A orde ofensiva foi: «%s»", +"MySQL user '%s'@'localhost' exists already." => "O usuario MySQL '%s'@'localhost' xa existe.", +"Drop this user from MySQL" => "Omitir este usuario de MySQL", +"MySQL user '%s'@'%%' already exists" => "O usuario MySQL «%s»@«%%» xa existe.", +"Drop this user from MySQL." => "Omitir este usuario de MySQL.", +"Oracle connection could not be established" => "Non foi posíbel estabelecer a conexión con Oracle", +"Oracle username and/or password not valid" => "Nome de usuario e/ou contrasinal de Oracle incorrecto", +"Offending command was: \"%s\", name: %s, password: %s" => "A orde ofensiva foi: «%s», nome: %s, contrasinal: %s", +"PostgreSQL username and/or password not valid" => "Nome de usuario e/ou contrasinal de PostgreSQL incorrecto", +"Set an admin username." => "Estabeleza un nome de usuario administrador", +"Set an admin password." => "Estabeleza un contrasinal de administrador", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "O seu servidor web non está aínda configurado adecuadamente para permitir a sincronización de ficheiros xa que semella que a interface WebDAV non está a funcionar.", +"Please double check the <a href='%s'>installation guides</a>." => "Volva comprobar as <a href='%s'>guías de instalación</a>", +"seconds ago" => "segundos atrás", +"_%n minute ago_::_%n minutes ago_" => array("hai %n minuto","hai %n minutos"), +"_%n hour ago_::_%n hours ago_" => array("hai %n hora","hai %n horas"), +"today" => "hoxe", +"yesterday" => "onte", +"_%n day go_::_%n days ago_" => array("hai %n día","hai %n días"), +"last month" => "último mes", +"_%n month ago_::_%n months ago_" => array("hai %n mes","hai %n meses"), +"last year" => "último ano", +"years ago" => "anos atrás", +"Caused by:" => "Causado por:", +"Could not find category \"%s\"" => "Non foi posíbel atopar a categoría «%s»" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/he.php b/lib/private/l10n/he.php new file mode 100644 index 00000000000..ced6244ee91 --- /dev/null +++ b/lib/private/l10n/he.php @@ -0,0 +1,33 @@ +<?php +$TRANSLATIONS = array( +"Help" => "עזרה", +"Personal" => "אישי", +"Settings" => "הגדרות", +"Users" => "משתמשים", +"Admin" => "מנהל", +"web services under your control" => "שירותי רשת תחת השליטה שלך", +"ZIP download is turned off." => "הורדת ZIP כבויה", +"Files need to be downloaded one by one." => "יש להוריד את הקבצים אחד אחרי השני.", +"Back to Files" => "חזרה לקבצים", +"Selected files too large to generate zip file." => "הקבצים הנבחרים גדולים מידי ליצירת קובץ zip.", +"Application is not enabled" => "יישומים אינם מופעלים", +"Authentication error" => "שגיאת הזדהות", +"Token expired. Please reload page." => "פג תוקף. נא לטעון שוב את הדף.", +"Files" => "קבצים", +"Text" => "טקסט", +"Images" => "תמונות", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "שרת האינטרנט שלך אינו מוגדר לצורכי סנכרון קבצים עדיין כיוון שמנשק ה־WebDAV כנראה אינו תקין.", +"Please double check the <a href='%s'>installation guides</a>." => "נא לעיין שוב ב<a href='%s'>מדריכי ההתקנה</a>.", +"seconds ago" => "שניות", +"_%n minute ago_::_%n minutes ago_" => array("","לפני %n דקות"), +"_%n hour ago_::_%n hours ago_" => array("","לפני %n שעות"), +"today" => "היום", +"yesterday" => "אתמול", +"_%n day go_::_%n days ago_" => array("","לפני %n ימים"), +"last month" => "חודש שעבר", +"_%n month ago_::_%n months ago_" => array("","לפני %n חודשים"), +"last year" => "שנה שעברה", +"years ago" => "שנים", +"Could not find category \"%s\"" => "לא ניתן למצוא את הקטגוריה „%s“" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/hi.php b/lib/private/l10n/hi.php new file mode 100644 index 00000000000..039dfa4465d --- /dev/null +++ b/lib/private/l10n/hi.php @@ -0,0 +1,12 @@ +<?php +$TRANSLATIONS = array( +"Help" => "सहयोग", +"Personal" => "यक्तिगत", +"Settings" => "सेटिंग्स", +"Users" => "उपयोगकर्ता", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/hr.php b/lib/private/l10n/hr.php new file mode 100644 index 00000000000..d217f924099 --- /dev/null +++ b/lib/private/l10n/hr.php @@ -0,0 +1,23 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Pomoć", +"Personal" => "Osobno", +"Settings" => "Postavke", +"Users" => "Korisnici", +"Admin" => "Administrator", +"web services under your control" => "web usluge pod vašom kontrolom", +"Authentication error" => "Greška kod autorizacije", +"Files" => "Datoteke", +"Text" => "Tekst", +"seconds ago" => "sekundi prije", +"_%n minute ago_::_%n minutes ago_" => array("","",""), +"_%n hour ago_::_%n hours ago_" => array("","",""), +"today" => "danas", +"yesterday" => "jučer", +"_%n day go_::_%n days ago_" => array("","",""), +"last month" => "prošli mjesec", +"_%n month ago_::_%n months ago_" => array("","",""), +"last year" => "prošlu godinu", +"years ago" => "godina" +); +$PLURAL_FORMS = "nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;"; diff --git a/lib/private/l10n/hu_HU.php b/lib/private/l10n/hu_HU.php new file mode 100644 index 00000000000..e944291caee --- /dev/null +++ b/lib/private/l10n/hu_HU.php @@ -0,0 +1,62 @@ +<?php +$TRANSLATIONS = array( +"No app name specified" => "Nincs az alkalmazás név megadva.", +"Help" => "Súgó", +"Personal" => "Személyes", +"Settings" => "Beállítások", +"Users" => "Felhasználók", +"Admin" => "Adminsztráció", +"Failed to upgrade \"%s\"." => "Sikertelen Frissítés \"%s\".", +"Unknown filetype" => "Ismeretlen file tipús", +"Invalid image" => "Hibás kép", +"web services under your control" => "webszolgáltatások saját kézben", +"cannot open \"%s\"" => "nem sikerült megnyitni \"%s\"", +"ZIP download is turned off." => "A ZIP-letöltés nincs engedélyezve.", +"Files need to be downloaded one by one." => "A fájlokat egyenként kell letölteni.", +"Back to Files" => "Vissza a Fájlokhoz", +"Selected files too large to generate zip file." => "A kiválasztott fájlok túl nagyok a zip tömörítéshez.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Tölts le a fileokat kisebb chunkokban, kölün vagy kérj segitséget a rendszergazdádtól.", +"App does not provide an info.xml file" => "Az alkalmazás nem szolgáltatott info.xml file-t", +"App can't be installed because it is not compatible with this version of ownCloud" => "Az alalmazás nem telepíthető, mert nem kompatibilis az ownClod ezzel a verziójával.", +"App directory already exists" => "Az alkalmazás mappája már létezik", +"Can't create app folder. Please fix permissions. %s" => "Nem lehetett létrehozni az alkalmzás mappáját. Kérlek ellenőrizd a jogosultásgokat. %s", +"Application is not enabled" => "Az alkalmazás nincs engedélyezve", +"Authentication error" => "Azonosítási hiba", +"Token expired. Please reload page." => "A token lejárt. Frissítse az oldalt.", +"Files" => "Fájlok", +"Text" => "Szöveg", +"Images" => "Képek", +"%s enter the database username." => "%s adja meg az adatbázist elérő felhasználó login nevét.", +"%s enter the database name." => "%s adja meg az adatbázis nevét.", +"%s you may not use dots in the database name" => "%s az adatbázis neve nem tartalmazhat pontot", +"MS SQL username and/or password not valid: %s" => "Az MS SQL felhasználónév és/vagy jelszó érvénytelen: %s", +"You need to enter either an existing account or the administrator." => "Vagy egy létező felhasználó vagy az adminisztrátor bejelentkezési nevét kell megadnia", +"MySQL username and/or password not valid" => "A MySQL felhasználói név és/vagy jelszó érvénytelen", +"DB Error: \"%s\"" => "Adatbázis hiba: \"%s\"", +"Offending command was: \"%s\"" => "A hibát ez a parancs okozta: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "A '%s'@'localhost' MySQL felhasználó már létezik.", +"Drop this user from MySQL" => "Törölje ezt a felhasználót a MySQL-ből", +"MySQL user '%s'@'%%' already exists" => "A '%s'@'%%' MySQL felhasználó már létezik", +"Drop this user from MySQL." => "Törölje ezt a felhasználót a MySQL-ből.", +"Oracle connection could not be established" => "Az Oracle kapcsolat nem hozható létre", +"Oracle username and/or password not valid" => "Az Oracle felhasználói név és/vagy jelszó érvénytelen", +"Offending command was: \"%s\", name: %s, password: %s" => "A hibát okozó parancs ez volt: \"%s\", login név: %s, jelszó: %s", +"PostgreSQL username and/or password not valid" => "A PostgreSQL felhasználói név és/vagy jelszó érvénytelen", +"Set an admin username." => "Állítson be egy felhasználói nevet az adminisztrációhoz.", +"Set an admin password." => "Állítson be egy jelszót az adminisztrációhoz.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Az Ön webkiszolgálója nincs megfelelően beállítva az állományok szinkronizálásához, mert a WebDAV-elérés úgy tűnik, nem működik.", +"Please double check the <a href='%s'>installation guides</a>." => "Kérjük tüzetesen tanulmányozza át a <a href='%s'>telepítési útmutatót</a>.", +"Could not find category \"%s\"" => "Ez a kategória nem található: \"%s\"", +"seconds ago" => "pár másodperce", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "ma", +"yesterday" => "tegnap", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "múlt hónapban", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "tavaly", +"years ago" => "több éve", +"Caused by:" => "Okozta:" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/hy.php b/lib/private/l10n/hy.php new file mode 100644 index 00000000000..15f78e0bce6 --- /dev/null +++ b/lib/private/l10n/hy.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/ia.php b/lib/private/l10n/ia.php new file mode 100644 index 00000000000..34f43bc424a --- /dev/null +++ b/lib/private/l10n/ia.php @@ -0,0 +1,16 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Adjuta", +"Personal" => "Personal", +"Settings" => "Configurationes", +"Users" => "Usatores", +"Admin" => "Administration", +"web services under your control" => "servicios web sub tu controlo", +"Files" => "Files", +"Text" => "Texto", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/id.php b/lib/private/l10n/id.php new file mode 100644 index 00000000000..080faddb321 --- /dev/null +++ b/lib/private/l10n/id.php @@ -0,0 +1,50 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Bantuan", +"Personal" => "Pribadi", +"Settings" => "Setelan", +"Users" => "Pengguna", +"Admin" => "Admin", +"web services under your control" => "layanan web dalam kontrol Anda", +"ZIP download is turned off." => "Pengunduhan ZIP dimatikan.", +"Files need to be downloaded one by one." => "Berkas harus diunduh satu persatu.", +"Back to Files" => "Kembali ke Daftar Berkas", +"Selected files too large to generate zip file." => "Berkas yang dipilih terlalu besar untuk dibuat berkas zip-nya.", +"Application is not enabled" => "Aplikasi tidak diaktifkan", +"Authentication error" => "Galat saat autentikasi", +"Token expired. Please reload page." => "Token kedaluwarsa. Silakan muat ulang halaman.", +"Files" => "Berkas", +"Text" => "Teks", +"Images" => "Gambar", +"%s enter the database username." => "%s masukkan nama pengguna basis data.", +"%s enter the database name." => "%s masukkan nama basis data.", +"%s you may not use dots in the database name" => "%sAnda tidak boleh menggunakan karakter titik pada nama basis data", +"MS SQL username and/or password not valid: %s" => "Nama pengguna dan/atau sandi MySQL tidak valid: %s", +"You need to enter either an existing account or the administrator." => "Anda harus memasukkan akun yang sudah ada atau administrator.", +"MySQL username and/or password not valid" => "Nama pengguna dan/atau sandi MySQL tidak valid", +"DB Error: \"%s\"" => "Galat Basis Data: \"%s\"", +"Offending command was: \"%s\"" => "Perintah yang bermasalah: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "Pengguna MySQL '%s'@'localhost' sudah ada.", +"Drop this user from MySQL" => "Hapus pengguna ini dari MySQL", +"MySQL user '%s'@'%%' already exists" => "Pengguna MySQL '%s'@'%%' sudah ada.", +"Drop this user from MySQL." => "Hapus pengguna ini dari MySQL.", +"Oracle username and/or password not valid" => "Nama pengguna dan/atau sandi Oracle tidak valid", +"Offending command was: \"%s\", name: %s, password: %s" => "Perintah yang bermasalah: \"%s\", nama pengguna: %s, sandi: %s", +"PostgreSQL username and/or password not valid" => "Nama pengguna dan/atau sandi PostgreSQL tidak valid", +"Set an admin username." => "Setel nama pengguna admin.", +"Set an admin password." => "Setel sandi admin.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Web server Anda belum dikonfigurasikan dengan baik untuk mengizinkan sinkronisasi berkas karena tampaknya antarmuka WebDAV rusak.", +"Please double check the <a href='%s'>installation guides</a>." => "Silakan periksa ulang <a href='%s'>panduan instalasi</a>.", +"seconds ago" => "beberapa detik yang lalu", +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"today" => "hari ini", +"yesterday" => "kemarin", +"_%n day go_::_%n days ago_" => array(""), +"last month" => "bulan kemarin", +"_%n month ago_::_%n months ago_" => array(""), +"last year" => "tahun kemarin", +"years ago" => "beberapa tahun lalu", +"Could not find category \"%s\"" => "Tidak dapat menemukan kategori \"%s\"" +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/is.php b/lib/private/l10n/is.php new file mode 100644 index 00000000000..7512d278fb8 --- /dev/null +++ b/lib/private/l10n/is.php @@ -0,0 +1,31 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Hjálp", +"Personal" => "Um mig", +"Settings" => "Stillingar", +"Users" => "Notendur", +"Admin" => "Stjórnun", +"web services under your control" => "vefþjónusta undir þinni stjórn", +"ZIP download is turned off." => "Slökkt á ZIP niðurhali.", +"Files need to be downloaded one by one." => "Skrárnar verður að sækja eina og eina", +"Back to Files" => "Aftur í skrár", +"Selected files too large to generate zip file." => "Valdar skrár eru of stórar til að búa til ZIP skrá.", +"Application is not enabled" => "Forrit ekki virkt", +"Authentication error" => "Villa við auðkenningu", +"Token expired. Please reload page." => "Auðkenning útrunnin. Vinsamlegast skráðu þig aftur inn.", +"Files" => "Skrár", +"Text" => "Texti", +"Images" => "Myndir", +"seconds ago" => "sek.", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "í dag", +"yesterday" => "í gær", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "síðasta mánuði", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "síðasta ári", +"years ago" => "einhverjum árum", +"Could not find category \"%s\"" => "Fann ekki flokkinn \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/it.php b/lib/private/l10n/it.php new file mode 100644 index 00000000000..b00789bc86f --- /dev/null +++ b/lib/private/l10n/it.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "L'applicazione \"%s\" non può essere installata poiché non è compatibile con questa versione di ownCloud.", +"No app name specified" => "Il nome dell'applicazione non è specificato", +"Help" => "Aiuto", +"Personal" => "Personale", +"Settings" => "Impostazioni", +"Users" => "Utenti", +"Admin" => "Admin", +"Failed to upgrade \"%s\"." => "Aggiornamento non riuscito \"%s\".", +"Custom profile pictures don't work with encryption yet" => "Le immagini personalizzate del profilo non funzionano ancora con la cifratura", +"Unknown filetype" => "Tipo di file sconosciuto", +"Invalid image" => "Immagine non valida", +"web services under your control" => "servizi web nelle tue mani", +"cannot open \"%s\"" => "impossibile aprire \"%s\"", +"ZIP download is turned off." => "Lo scaricamento in formato ZIP è stato disabilitato.", +"Files need to be downloaded one by one." => "I file devono essere scaricati uno alla volta.", +"Back to Files" => "Torna ai file", +"Selected files too large to generate zip file." => "I file selezionati sono troppo grandi per generare un file zip.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Scarica i file in blocchi più piccoli, separatamente o chiedi al tuo amministratore.", +"No source specified when installing app" => "Nessuna fonte specificata durante l'installazione dell'applicazione", +"No href specified when installing app from http" => "Nessun href specificato durante l'installazione dell'applicazione da http", +"No path specified when installing app from local file" => "Nessun percorso specificato durante l'installazione dell'applicazione da file locale", +"Archives of type %s are not supported" => "Gli archivi di tipo %s non sono supportati", +"Failed to open archive when installing app" => "Apertura archivio non riuscita durante l'installazione dell'applicazione", +"App does not provide an info.xml file" => "L'applicazione non fornisce un file info.xml", +"App can't be installed because of not allowed code in the App" => "L'applicazione non può essere installata a causa di codice non consentito al suo interno", +"App can't be installed because it is not compatible with this version of ownCloud" => "L'applicazione non può essere installata poiché non è compatibile con questa versione di ownCloud", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "L'applicazione non può essere installata poiché contiene il tag <shipped>true<shipped> che non è permesso alle applicazioni non shipped", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "L'applicazione non può essere installata poiché la versione in info.xml/version non è la stessa riportata dall'app store", +"App directory already exists" => "La cartella dell'applicazione esiste già", +"Can't create app folder. Please fix permissions. %s" => "Impossibile creare la cartella dell'applicazione. Correggi i permessi. %s", +"Application is not enabled" => "L'applicazione non è abilitata", +"Authentication error" => "Errore di autenticazione", +"Token expired. Please reload page." => "Token scaduto. Ricarica la pagina.", +"Files" => "File", +"Text" => "Testo", +"Images" => "Immagini", +"%s enter the database username." => "%s digita il nome utente del database.", +"%s enter the database name." => "%s digita il nome del database.", +"%s you may not use dots in the database name" => "%s non dovresti utilizzare punti nel nome del database", +"MS SQL username and/or password not valid: %s" => "Nome utente e/o password MS SQL non validi: %s", +"You need to enter either an existing account or the administrator." => "È necessario inserire un account esistente o l'amministratore.", +"MySQL username and/or password not valid" => "Nome utente e/o password di MySQL non validi", +"DB Error: \"%s\"" => "Errore DB: \"%s\"", +"Offending command was: \"%s\"" => "Il comando non consentito era: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "L'utente MySQL '%s'@'localhost' esiste già.", +"Drop this user from MySQL" => "Elimina questo utente da MySQL", +"MySQL user '%s'@'%%' already exists" => "L'utente MySQL '%s'@'%%' esiste già", +"Drop this user from MySQL." => "Elimina questo utente da MySQL.", +"Oracle connection could not be established" => "La connessione a Oracle non può essere stabilita", +"Oracle username and/or password not valid" => "Nome utente e/o password di Oracle non validi", +"Offending command was: \"%s\", name: %s, password: %s" => "Il comando non consentito era: \"%s\", nome: %s, password: %s", +"PostgreSQL username and/or password not valid" => "Nome utente e/o password di PostgreSQL non validi", +"Set an admin username." => "Imposta un nome utente di amministrazione.", +"Set an admin password." => "Imposta una password di amministrazione.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Il tuo server web non è configurato correttamente per consentire la sincronizzazione dei file poiché l'interfaccia WebDAV sembra essere danneggiata.", +"Please double check the <a href='%s'>installation guides</a>." => "Leggi attentamente le <a href='%s'>guide d'installazione</a>.", +"seconds ago" => "secondi fa", +"_%n minute ago_::_%n minutes ago_" => array("%n minuto fa","%n minuti fa"), +"_%n hour ago_::_%n hours ago_" => array("%n ora fa","%n ore fa"), +"today" => "oggi", +"yesterday" => "ieri", +"_%n day go_::_%n days ago_" => array("%n giorno fa","%n giorni fa"), +"last month" => "mese scorso", +"_%n month ago_::_%n months ago_" => array("%n mese fa","%n mesi fa"), +"last year" => "anno scorso", +"years ago" => "anni fa", +"Caused by:" => "Causato da:", +"Could not find category \"%s\"" => "Impossibile trovare la categoria \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/ja_JP.php b/lib/private/l10n/ja_JP.php new file mode 100644 index 00000000000..b9e6a0e6924 --- /dev/null +++ b/lib/private/l10n/ja_JP.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => " \"%s\" アプリは、このバージョンのownCloudと互換性がない為、インストールできません。", +"No app name specified" => "アプリ名が未指定", +"Help" => "ヘルプ", +"Personal" => "個人", +"Settings" => "設定", +"Users" => "ユーザ", +"Admin" => "管理", +"Failed to upgrade \"%s\"." => "\"%s\" へのアップグレードに失敗しました。", +"Custom profile pictures don't work with encryption yet" => "暗号無しでは利用不可なカスタムプロフィール画像", +"Unknown filetype" => "不明なファイルタイプ", +"Invalid image" => "無効な画像", +"web services under your control" => "管理下のウェブサービス", +"cannot open \"%s\"" => "\"%s\" が開けません", +"ZIP download is turned off." => "ZIPダウンロードは無効です。", +"Files need to be downloaded one by one." => "ファイルは1つずつダウンロードする必要があります。", +"Back to Files" => "ファイルに戻る", +"Selected files too large to generate zip file." => "選択したファイルはZIPファイルの生成には大きすぎます。", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "ファイルは、小さいファイルに分割されてダウンロードされます。もしくは、管理者にお尋ねください。", +"No source specified when installing app" => "アプリインストール時のソースが未指定", +"No href specified when installing app from http" => "アプリインストール時のhttpの URL が未指定", +"No path specified when installing app from local file" => "アプリインストール時のローカルファイルのパスが未指定", +"Archives of type %s are not supported" => "\"%s\"タイプのアーカイブ形式は未サポート", +"Failed to open archive when installing app" => "アプリをインストール中にアーカイブファイルを開けませんでした。", +"App does not provide an info.xml file" => "アプリにinfo.xmlファイルが入っていません", +"App can't be installed because of not allowed code in the App" => "アプリで許可されないコードが入っているのが原因でアプリがインストールできません", +"App can't be installed because it is not compatible with this version of ownCloud" => "アプリは、このバージョンのownCloudと互換性がない為、インストールできません。", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "非shippedアプリには許可されない<shipped>true</shipped>タグが含まれているためにアプリをインストール出来ません。", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "info.xml/versionのバージョンがアプリストアのバージョンと合っていない為、アプリはインストールされません", +"App directory already exists" => "アプリディレクトリは既に存在します", +"Can't create app folder. Please fix permissions. %s" => "アプリフォルダを作成出来ませんでした。%s のパーミッションを修正してください。", +"Application is not enabled" => "アプリケーションは無効です", +"Authentication error" => "認証エラー", +"Token expired. Please reload page." => "トークンが無効になりました。ページを再読込してください。", +"Files" => "ファイル", +"Text" => "TTY TDD", +"Images" => "画像", +"%s enter the database username." => "%s のデータベースのユーザ名を入力してください。", +"%s enter the database name." => "%s のデータベース名を入力してください。", +"%s you may not use dots in the database name" => "%s ではデータベース名にドットを利用できないかもしれません。", +"MS SQL username and/or password not valid: %s" => "MS SQL サーバーのユーザー名/パスワードが正しくありません: %s", +"You need to enter either an existing account or the administrator." => "既存のアカウントもしくは管理者のどちらかを入力する必要があります。", +"MySQL username and/or password not valid" => "MySQLのユーザ名もしくはパスワードは有効ではありません", +"DB Error: \"%s\"" => "DBエラー: \"%s\"", +"Offending command was: \"%s\"" => "違反コマンド: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQLのユーザ '%s'@'localhost' はすでに存在します。", +"Drop this user from MySQL" => "MySQLからこのユーザを削除", +"MySQL user '%s'@'%%' already exists" => "MySQLのユーザ '%s'@'%%' はすでに存在します。", +"Drop this user from MySQL." => "MySQLからこのユーザを削除する。", +"Oracle connection could not be established" => "Oracleへの接続が確立できませんでした。", +"Oracle username and/or password not valid" => "Oracleのユーザ名もしくはパスワードは有効ではありません", +"Offending command was: \"%s\", name: %s, password: %s" => "違反コマンド: \"%s\"、名前: %s、パスワード: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQLのユーザ名もしくはパスワードは有効ではありません", +"Set an admin username." => "管理者のユーザ名を設定。", +"Set an admin password." => "管理者のパスワードを設定。", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "WebDAVインタフェースが動作していないと考えられるため、あなたのWEBサーバはまだファイルの同期を許可するように適切な設定がされていません。", +"Please double check the <a href='%s'>installation guides</a>." => "<a href='%s'>インストールガイド</a>をよく確認してください。", +"seconds ago" => "数秒前", +"_%n minute ago_::_%n minutes ago_" => array("%n 分前"), +"_%n hour ago_::_%n hours ago_" => array("%n 時間後"), +"today" => "今日", +"yesterday" => "昨日", +"_%n day go_::_%n days ago_" => array("%n 日後"), +"last month" => "一月前", +"_%n month ago_::_%n months ago_" => array("%n カ月後"), +"last year" => "一年前", +"years ago" => "年前", +"Caused by:" => "原因は以下:", +"Could not find category \"%s\"" => "カテゴリ \"%s\" が見つかりませんでした" +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/ka.php b/lib/private/l10n/ka.php new file mode 100644 index 00000000000..04fefe8bdf1 --- /dev/null +++ b/lib/private/l10n/ka.php @@ -0,0 +1,17 @@ +<?php +$TRANSLATIONS = array( +"Help" => "შველა", +"Personal" => "პერსონა", +"Users" => "მომხმარებლები", +"Admin" => "ადმინისტრატორი", +"ZIP download is turned off." => "ZIP გადმოწერა გამორთულია", +"Files" => "ფაილები", +"seconds ago" => "წამის წინ", +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"today" => "დღეს", +"yesterday" => "გუშინ", +"_%n day go_::_%n days ago_" => array(""), +"_%n month ago_::_%n months ago_" => array("") +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/ka_GE.php b/lib/private/l10n/ka_GE.php new file mode 100644 index 00000000000..8fbe34e6786 --- /dev/null +++ b/lib/private/l10n/ka_GE.php @@ -0,0 +1,50 @@ +<?php +$TRANSLATIONS = array( +"Help" => "დახმარება", +"Personal" => "პირადი", +"Settings" => "პარამეტრები", +"Users" => "მომხმარებელი", +"Admin" => "ადმინისტრატორი", +"web services under your control" => "web services under your control", +"ZIP download is turned off." => "ZIP download–ი გათიშულია", +"Files need to be downloaded one by one." => "ფაილები უნდა გადმოიტვირთოს სათითაოდ.", +"Back to Files" => "უკან ფაილებში", +"Selected files too large to generate zip file." => "არჩეული ფაილები ძალიან დიდია zip ფაილის გენერაციისთვის.", +"Application is not enabled" => "აპლიკაცია არ არის აქტიური", +"Authentication error" => "ავთენტიფიკაციის შეცდომა", +"Token expired. Please reload page." => "Token–ს ვადა გაუვიდა. გთხოვთ განაახლოთ გვერდი.", +"Files" => "ფაილები", +"Text" => "ტექსტი", +"Images" => "სურათები", +"%s enter the database username." => "%s შეიყვანეთ ბაზის იუზერნეიმი.", +"%s enter the database name." => "%s შეიყვანეთ ბაზის სახელი.", +"%s you may not use dots in the database name" => "%s არ მიუთითოთ წერტილი ბაზის სახელში", +"MS SQL username and/or password not valid: %s" => "MS SQL მომხმარებელი და/ან პაროლი არ არის მართებული: %s", +"You need to enter either an existing account or the administrator." => "თქვენ უნდა შეიყვანოთ არსებული მომხმარებელის სახელი ან ადმინისტრატორი.", +"MySQL username and/or password not valid" => "MySQL იუზერნეიმი და/ან პაროლი არ არის სწორი", +"DB Error: \"%s\"" => "DB შეცდომა: \"%s\"", +"Offending command was: \"%s\"" => "Offending ბრძანება იყო: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL მომხმარებელი '%s'@'localhost' უკვე არსებობს.", +"Drop this user from MySQL" => "წაშალე ეს მომხამრებელი MySQL–იდან", +"MySQL user '%s'@'%%' already exists" => "MySQL მომხმარებელი '%s'@'%%' უკვე არსებობს", +"Drop this user from MySQL." => "წაშალე ეს მომხამრებელი MySQL–იდან", +"Oracle username and/or password not valid" => "Oracle იუზერნეიმი და/ან პაროლი არ არის სწორი", +"Offending command was: \"%s\", name: %s, password: %s" => "Offending ბრძანება იყო: \"%s\", სახელი: %s, პაროლი: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL იუზერნეიმი და/ან პაროლი არ არის სწორი", +"Set an admin username." => "დააყენეთ ადმინისტრატორის სახელი.", +"Set an admin password." => "დააყენეთ ადმინისტრატორის პაროლი.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "თქვენი web სერვერი არ არის კონფიგურირებული ფაილ სინქრონიზაციისთვის, რადგან WebDAV ინტერფეისი შეიძლება იყოს გატეხილი.", +"Please double check the <a href='%s'>installation guides</a>." => "გთხოვთ გადაათვალიეროთ <a href='%s'>ინსტალაციის გზამკვლევი</a>.", +"seconds ago" => "წამის წინ", +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"today" => "დღეს", +"yesterday" => "გუშინ", +"_%n day go_::_%n days ago_" => array(""), +"last month" => "გასულ თვეში", +"_%n month ago_::_%n months ago_" => array(""), +"last year" => "ბოლო წელს", +"years ago" => "წლის წინ", +"Could not find category \"%s\"" => "\"%s\" კატეგორიის მოძებნა ვერ მოხერხდა" +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/km.php b/lib/private/l10n/km.php new file mode 100644 index 00000000000..e7b09649a24 --- /dev/null +++ b/lib/private/l10n/km.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"_%n day go_::_%n days ago_" => array(""), +"_%n month ago_::_%n months ago_" => array("") +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/kn.php b/lib/private/l10n/kn.php new file mode 100644 index 00000000000..e7b09649a24 --- /dev/null +++ b/lib/private/l10n/kn.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"_%n day go_::_%n days ago_" => array(""), +"_%n month ago_::_%n months ago_" => array("") +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/ko.php b/lib/private/l10n/ko.php new file mode 100644 index 00000000000..3ef39fefa60 --- /dev/null +++ b/lib/private/l10n/ko.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "현재 ownCloud 버전과 호환되지 않기 때문에 \"%s\" 앱을 설치할 수 없습니다.", +"No app name specified" => "앱 이름이 지정되지 않았습니다.", +"Help" => "도움말", +"Personal" => "개인", +"Settings" => "설정", +"Users" => "사용자", +"Admin" => "관리자", +"Failed to upgrade \"%s\"." => "\"%s\" 업그레이드에 실패했습니다.", +"Custom profile pictures don't work with encryption yet" => "개개인의 프로필 사진은 아직은 암호화 되지 않습니다", +"Unknown filetype" => "알수없는 파일형식", +"Invalid image" => "잘못된 그림", +"web services under your control" => "내가 관리하는 웹 서비스", +"cannot open \"%s\"" => "\"%s\"을(를) 열 수 없습니다.", +"ZIP download is turned off." => "ZIP 다운로드가 비활성화되었습니다.", +"Files need to be downloaded one by one." => "파일을 개별적으로 다운로드해야 합니다.", +"Back to Files" => "파일로 돌아가기", +"Selected files too large to generate zip file." => "선택한 파일들은 ZIP 파일을 생성하기에 너무 큽니다.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "작은 조각들 안에 들어있는 파일들을 받고자 하신다면, 나누어서 받으시거나 혹은 시스템 관리자에게 정중하게 물어보십시오", +"No source specified when installing app" => "앱을 설치할 때 소스가 지정되지 않았습니다.", +"No href specified when installing app from http" => "http에서 앱을 설치할 대 href가 지정되지 않았습니다.", +"No path specified when installing app from local file" => "로컬 파일에서 앱을 설치할 때 경로가 지정되지 않았습니다.", +"Archives of type %s are not supported" => "%s 타입 아카이브는 지원되지 않습니다.", +"Failed to open archive when installing app" => "앱을 설치할 때 아카이브를 열지 못했습니다.", +"App does not provide an info.xml file" => "앱에서 info.xml 파일이 제공되지 않았습니다.", +"App can't be installed because of not allowed code in the App" => "앱에 허용되지 않는 코드가 있어서 앱을 설치할 수 없습니다. ", +"App can't be installed because it is not compatible with this version of ownCloud" => "현재 ownCloud 버전과 호환되지 않기 때문에 앱을 설치할 수 없습니다.", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "출하되지 않은 앱에 허용되지 않는 <shipped>true</shipped> 태그를 포함하고 있기 때문에 앱을 설치할 수 없습니다.", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "info.xml/version에 포함된 버전과 앱 스토어에 보고된 버전이 같지 않아서 앱을 설치할 수 없습니다. ", +"App directory already exists" => "앱 디렉토리가 이미 존재합니다. ", +"Can't create app folder. Please fix permissions. %s" => "앱 폴더를 만들 수 없습니다. 권한을 수정하십시오. %s ", +"Application is not enabled" => "앱이 활성화되지 않았습니다", +"Authentication error" => "인증 오류", +"Token expired. Please reload page." => "토큰이 만료되었습니다. 페이지를 새로 고치십시오.", +"Files" => "파일", +"Text" => "텍스트", +"Images" => "그림", +"%s enter the database username." => "데이터베이스 사용자 명을 %s 에 입력해주십시오", +"%s enter the database name." => "데이터베이스 명을 %s 에 입력해주십시오", +"%s you may not use dots in the database name" => "%s 에 적으신 데이터베이스 이름에는 점을 사용할수 없습니다", +"MS SQL username and/or password not valid: %s" => "MS SQL 사용자 이름이나 암호가 잘못되었습니다: %s", +"You need to enter either an existing account or the administrator." => "기존 계정이나 administrator(관리자)를 입력해야 합니다.", +"MySQL username and/or password not valid" => "MySQL 사용자 이름이나 암호가 잘못되었습니다.", +"DB Error: \"%s\"" => "DB 오류: \"%s\"", +"Offending command was: \"%s\"" => "잘못된 명령: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL 사용자 '%s'@'localhost'이(가) 이미 존재합니다.", +"Drop this user from MySQL" => "이 사용자를 MySQL에서 뺍니다.", +"MySQL user '%s'@'%%' already exists" => "MySQL 사용자 '%s'@'%%'이(가) 이미 존재합니다. ", +"Drop this user from MySQL." => "이 사용자를 MySQL에서 뺍니다.", +"Oracle connection could not be established" => "Oracle 연결을 수립할 수 없습니다.", +"Oracle username and/or password not valid" => "Oracle 사용자 이름이나 암호가 잘못되었습니다.", +"Offending command was: \"%s\", name: %s, password: %s" => "잘못된 명령: \"%s\", 이름: %s, 암호: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL의 사용자 명 혹은 비밀번호가 잘못되었습니다", +"Set an admin username." => "관리자 이름 설정", +"Set an admin password." => "관리자 비밀번호 설정", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "WebDAV 인터페이스가 제대로 작동하지 않습니다. 웹 서버에서 파일 동기화를 사용할 수 있도록 설정이 제대로 되지 않은 것 같습니다.", +"Please double check the <a href='%s'>installation guides</a>." => "<a href='%s'>설치 가이드</a>를 다시 한 번 확인하십시오.", +"Could not find category \"%s\"" => "분류 \"%s\"을(를) 찾을 수 없습니다.", +"seconds ago" => "초 전", +"_%n minute ago_::_%n minutes ago_" => array("%n분 전 "), +"_%n hour ago_::_%n hours ago_" => array("%n시간 전 "), +"today" => "오늘", +"yesterday" => "어제", +"_%n day go_::_%n days ago_" => array("%n일 전 "), +"last month" => "지난 달", +"_%n month ago_::_%n months ago_" => array("%n달 전 "), +"last year" => "작년", +"years ago" => "년 전", +"Caused by:" => "원인: " +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/ku_IQ.php b/lib/private/l10n/ku_IQ.php new file mode 100644 index 00000000000..c99f9dd2a12 --- /dev/null +++ b/lib/private/l10n/ku_IQ.php @@ -0,0 +1,13 @@ +<?php +$TRANSLATIONS = array( +"Help" => "یارمەتی", +"Settings" => "دهستكاری", +"Users" => "بهكارهێنهر", +"Admin" => "بهڕێوهبهری سهرهكی", +"web services under your control" => "ڕاژهی وێب لهژێر چاودێریت دایه", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/lb.php b/lib/private/l10n/lb.php new file mode 100644 index 00000000000..c25f5b55bd5 --- /dev/null +++ b/lib/private/l10n/lb.php @@ -0,0 +1,23 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Hëllef", +"Personal" => "Perséinlech", +"Settings" => "Astellungen", +"Users" => "Benotzer", +"Admin" => "Admin", +"web services under your control" => "Web-Servicer ënnert denger Kontroll", +"Authentication error" => "Authentifikatioun's Fehler", +"Files" => "Dateien", +"Text" => "SMS", +"seconds ago" => "Sekonnen hir", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "haut", +"yesterday" => "gëschter", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "Läschte Mount", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "Läscht Joer", +"years ago" => "Joren hier" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/lt_LT.php b/lib/private/l10n/lt_LT.php new file mode 100644 index 00000000000..db8d96c1018 --- /dev/null +++ b/lib/private/l10n/lt_LT.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Programa „%s“ negali būti įdiegta, nes yra nesuderinama su šia ownCloud versija.", +"No app name specified" => "Nenurodytas programos pavadinimas", +"Help" => "Pagalba", +"Personal" => "Asmeniniai", +"Settings" => "Nustatymai", +"Users" => "Vartotojai", +"Admin" => "Administravimas", +"Failed to upgrade \"%s\"." => "Nepavyko pakelti „%s“ versijos.", +"Custom profile pictures don't work with encryption yet" => "Saviti profilio paveiksliukai dar neveikia su šifravimu", +"Unknown filetype" => "Nežinomas failo tipas", +"Invalid image" => "Netinkamas paveikslėlis", +"web services under your control" => "jūsų valdomos web paslaugos", +"cannot open \"%s\"" => "nepavyksta atverti „%s“", +"ZIP download is turned off." => "ZIP atsisiuntimo galimybė yra išjungta.", +"Files need to be downloaded one by one." => "Failai turi būti parsiunčiami vienas po kito.", +"Back to Files" => "Atgal į Failus", +"Selected files too large to generate zip file." => "Pasirinkti failai per dideli archyvavimui į ZIP.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Atsisiųskite failus mažesnėmis dalimis atskirai, arba mandagiai prašykite savo administratoriaus.", +"No source specified when installing app" => "Nenurodytas šaltinis diegiant programą", +"No href specified when installing app from http" => "Nenurodytas href diegiant programą iš http", +"No path specified when installing app from local file" => "Nenurodytas kelias diegiant programą iš vietinio failo", +"Archives of type %s are not supported" => "%s tipo archyvai nepalaikomi", +"Failed to open archive when installing app" => "Nepavyko atverti archyvo diegiant programą", +"App does not provide an info.xml file" => "Programa nepateikia info.xml failo", +"App can't be installed because of not allowed code in the App" => "Programa negali būti įdiegta, nes turi neleistiną kodą", +"App can't be installed because it is not compatible with this version of ownCloud" => "Programa negali būti įdiegta, nes yra nesuderinama su šia ownCloud versija", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "Programa negali būti įdiegta, nes turi <shipped>true</shipped> žymę, kuri yra neleistina ne kartu platinamoms programoms", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "Programa negali būti įdiegta, nes versija pateikta info.xml/version nesutampa su versija deklaruota programų saugykloje", +"App directory already exists" => "Programos aplankas jau egzistuoja", +"Can't create app folder. Please fix permissions. %s" => "Nepavyksta sukurti aplanko. Prašome pataisyti leidimus. %s", +"Application is not enabled" => "Programa neįjungta", +"Authentication error" => "Autentikacijos klaida", +"Token expired. Please reload page." => "Sesija baigėsi. Prašome perkrauti puslapį.", +"Files" => "Failai", +"Text" => "Žinučių", +"Images" => "Paveikslėliai", +"%s enter the database username." => "%s įrašykite duombazės naudotojo vardą.", +"%s enter the database name." => "%s įrašykite duombazės pavadinimą.", +"%s you may not use dots in the database name" => "%s negalite naudoti taškų duombazės pavadinime", +"MS SQL username and/or password not valid: %s" => "MS SQL naudotojo vardas ir/arba slaptažodis netinka: %s", +"You need to enter either an existing account or the administrator." => "Turite prisijungti su egzistuojančia paskyra arba su administratoriumi.", +"MySQL username and/or password not valid" => "Neteisingas MySQL naudotojo vardas ir/arba slaptažodis", +"DB Error: \"%s\"" => "DB klaida: \"%s\"", +"Offending command was: \"%s\"" => "Vykdyta komanda buvo: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL naudotojas '%s'@'localhost' jau egzistuoja.", +"Drop this user from MySQL" => "Pašalinti šį naudotoją iš MySQL", +"MySQL user '%s'@'%%' already exists" => "MySQL naudotojas '%s'@'%%' jau egzistuoja", +"Drop this user from MySQL." => "Pašalinti šį naudotoją iš MySQL.", +"Oracle connection could not be established" => "Nepavyko sukurti Oracle ryšio", +"Oracle username and/or password not valid" => "Neteisingas Oracle naudotojo vardas ir/arba slaptažodis", +"Offending command was: \"%s\", name: %s, password: %s" => "Vykdyta komanda buvo: \"%s\", name: %s, password: %s", +"PostgreSQL username and/or password not valid" => "Neteisingas PostgreSQL naudotojo vardas ir/arba slaptažodis", +"Set an admin username." => "Nustatyti administratoriaus naudotojo vardą.", +"Set an admin password." => "Nustatyti administratoriaus slaptažodį.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Jūsų serveris nėra tvarkingai nustatytas leisti failų sinchronizaciją, nes WebDAV sąsaja panašu, kad yra sugadinta.", +"Please double check the <a href='%s'>installation guides</a>." => "Prašome pažiūrėkite dar kartą <a href='%s'>diegimo instrukcijas</a>.", +"seconds ago" => "prieš sekundę", +"_%n minute ago_::_%n minutes ago_" => array("prieš %n min.","Prieš % minutes","Prieš %n minučių"), +"_%n hour ago_::_%n hours ago_" => array("Prieš %n valandą","Prieš %n valandas","Prieš %n valandų"), +"today" => "šiandien", +"yesterday" => "vakar", +"_%n day go_::_%n days ago_" => array("Prieš %n dieną","Prieš %n dienas","Prieš %n dienų"), +"last month" => "praeitą mėnesį", +"_%n month ago_::_%n months ago_" => array("Prieš %n mėnesį","Prieš %n mėnesius","Prieš %n mėnesių"), +"last year" => "praeitais metais", +"years ago" => "prieš metus", +"Caused by:" => "Iššaukė:", +"Could not find category \"%s\"" => "Nepavyko rasti kategorijos „%s“" +); +$PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/lib/private/l10n/lv.php b/lib/private/l10n/lv.php new file mode 100644 index 00000000000..4090a36edcc --- /dev/null +++ b/lib/private/l10n/lv.php @@ -0,0 +1,55 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Palīdzība", +"Personal" => "Personīgi", +"Settings" => "Iestatījumi", +"Users" => "Lietotāji", +"Admin" => "Administratori", +"Failed to upgrade \"%s\"." => "Kļūda atjauninot \"%s\"", +"web services under your control" => "tīmekļa servisi tavā varā", +"cannot open \"%s\"" => "Nevar atvērt \"%s\"", +"ZIP download is turned off." => "ZIP lejupielādēšana ir izslēgta.", +"Files need to be downloaded one by one." => "Datnes var lejupielādēt tikai katru atsevišķi.", +"Back to Files" => "Atpakaļ pie datnēm", +"Selected files too large to generate zip file." => "Izvēlētās datnes ir pārāk lielas, lai izveidotu zip datni.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Lejupielādējiet savus failus mazākās daļās, atsevišķi vai palūdziet tos administratoram.", +"Application is not enabled" => "Lietotne nav aktivēta", +"Authentication error" => "Autentifikācijas kļūda", +"Token expired. Please reload page." => "Pilnvarai ir beidzies termiņš. Lūdzu, pārlādējiet lapu.", +"Files" => "Datnes", +"Text" => "Teksts", +"Images" => "Attēli", +"%s enter the database username." => "%s ievadiet datubāzes lietotājvārdu.", +"%s enter the database name." => "%s ievadiet datubāzes nosaukumu.", +"%s you may not use dots in the database name" => "%s datubāžu nosaukumos nedrīkst izmantot punktus", +"MS SQL username and/or password not valid: %s" => "Nav derīga MySQL parole un/vai lietotājvārds — %s", +"You need to enter either an existing account or the administrator." => "Jums jāievada vai nu esošs vai administratora konts.", +"MySQL username and/or password not valid" => "Nav derīga MySQL parole un/vai lietotājvārds", +"DB Error: \"%s\"" => "DB kļūda — “%s”", +"Offending command was: \"%s\"" => "Vainīgā komanda bija “%s”", +"MySQL user '%s'@'localhost' exists already." => "MySQL lietotājs %s'@'localhost' jau eksistē.", +"Drop this user from MySQL" => "Izmest šo lietotāju no MySQL", +"MySQL user '%s'@'%%' already exists" => "MySQL lietotājs '%s'@'%%' jau eksistē", +"Drop this user from MySQL." => "Izmest šo lietotāju no MySQL.", +"Oracle connection could not be established" => "Nevar izveidot savienojumu ar Oracle", +"Oracle username and/or password not valid" => "Nav derīga Oracle parole un/vai lietotājvārds", +"Offending command was: \"%s\", name: %s, password: %s" => "Vainīgā komanda bija \"%s\", vārds: %s, parole: %s", +"PostgreSQL username and/or password not valid" => "Nav derīga PostgreSQL parole un/vai lietotājvārds", +"Set an admin username." => "Iestatiet administratora lietotājvārdu.", +"Set an admin password." => "Iestatiet administratora paroli.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Jūsu serveris vēl nav pareizi iestatīts, lai ļautu sinhronizēt datnes, jo izskatās, ka WebDAV saskarne ir salauzta.", +"Please double check the <a href='%s'>installation guides</a>." => "Lūdzu, vēlreiz pārbaudiet <a href='%s'>instalēšanas palīdzību</a>.", +"seconds ago" => "sekundes atpakaļ", +"_%n minute ago_::_%n minutes ago_" => array("","","Pirms %n minūtēm"), +"_%n hour ago_::_%n hours ago_" => array("","","Pirms %n stundām"), +"today" => "šodien", +"yesterday" => "vakar", +"_%n day go_::_%n days ago_" => array("","","Pirms %n dienām"), +"last month" => "pagājušajā mēnesī", +"_%n month ago_::_%n months ago_" => array("","","Pirms %n mēnešiem"), +"last year" => "gājušajā gadā", +"years ago" => "gadus atpakaļ", +"Caused by:" => "Cēlonis:", +"Could not find category \"%s\"" => "Nevarēja atrast kategoriju “%s”" +); +$PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);"; diff --git a/lib/private/l10n/mk.php b/lib/private/l10n/mk.php new file mode 100644 index 00000000000..69d4a1cb694 --- /dev/null +++ b/lib/private/l10n/mk.php @@ -0,0 +1,31 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Помош", +"Personal" => "Лично", +"Settings" => "Подесувања", +"Users" => "Корисници", +"Admin" => "Админ", +"web services under your control" => "веб сервиси под Ваша контрола", +"ZIP download is turned off." => "Преземање во ZIP е исклучено", +"Files need to be downloaded one by one." => "Датотеките треба да се симнат една по една.", +"Back to Files" => "Назад кон датотеки", +"Selected files too large to generate zip file." => "Избраните датотеки се преголеми за да се генерира zip.", +"Application is not enabled" => "Апликацијата не е овозможена", +"Authentication error" => "Грешка во автентикација", +"Token expired. Please reload page." => "Жетонот е истечен. Ве молам превчитајте ја страницата.", +"Files" => "Датотеки", +"Text" => "Текст", +"Images" => "Слики", +"seconds ago" => "пред секунди", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "денеска", +"yesterday" => "вчера", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "минатиот месец", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "минатата година", +"years ago" => "пред години", +"Could not find category \"%s\"" => "Не можам да најдам категорија „%s“" +); +$PLURAL_FORMS = "nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;"; diff --git a/lib/private/l10n/ml_IN.php b/lib/private/l10n/ml_IN.php new file mode 100644 index 00000000000..15f78e0bce6 --- /dev/null +++ b/lib/private/l10n/ml_IN.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/ms_MY.php b/lib/private/l10n/ms_MY.php new file mode 100644 index 00000000000..17ef07f83dd --- /dev/null +++ b/lib/private/l10n/ms_MY.php @@ -0,0 +1,17 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Bantuan", +"Personal" => "Peribadi", +"Settings" => "Tetapan", +"Users" => "Pengguna", +"Admin" => "Admin", +"web services under your control" => "Perkhidmatan web di bawah kawalan anda", +"Authentication error" => "Ralat pengesahan", +"Files" => "Fail-fail", +"Text" => "Teks", +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"_%n day go_::_%n days ago_" => array(""), +"_%n month ago_::_%n months ago_" => array("") +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/my_MM.php b/lib/private/l10n/my_MM.php new file mode 100644 index 00000000000..5f4b6ddc820 --- /dev/null +++ b/lib/private/l10n/my_MM.php @@ -0,0 +1,27 @@ +<?php +$TRANSLATIONS = array( +"Help" => "အကူအညီ", +"Users" => "သုံးစွဲသူ", +"Admin" => "အက်ဒမင်", +"web services under your control" => "သင်၏ထိန်းချုပ်မှု့အောက်တွင်ရှိသော Web services", +"ZIP download is turned off." => "ZIP ဒေါင်းလုတ်ကိုပိတ်ထားသည်", +"Files need to be downloaded one by one." => "ဖိုင်များသည် တစ်ခုပြီး တစ်ခုဒေါင်းလုတ်ချရန်လိုအပ်သည်", +"Back to Files" => "ဖိုင်သို့ပြန်သွားမည်", +"Selected files too large to generate zip file." => "zip ဖိုင်အဖြစ်ပြုလုပ်ရန် ရွေးချယ်ထားသောဖိုင်များသည် အရမ်းကြီးလွန်းသည်", +"Authentication error" => "ခွင့်ပြုချက်မအောင်မြင်", +"Files" => "ဖိုင်များ", +"Text" => "စာသား", +"Images" => "ပုံရိပ်များ", +"seconds ago" => "စက္ကန့်အနည်းငယ်က", +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"today" => "ယနေ့", +"yesterday" => "မနေ့က", +"_%n day go_::_%n days ago_" => array(""), +"last month" => "ပြီးခဲ့သောလ", +"_%n month ago_::_%n months ago_" => array(""), +"last year" => "မနှစ်က", +"years ago" => "နှစ် အရင်က", +"Could not find category \"%s\"" => "\"%s\"ခေါင်းစဉ်ကို ရှာမတွေ့ပါ" +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/nb_NO.php b/lib/private/l10n/nb_NO.php new file mode 100644 index 00000000000..8e7d095d369 --- /dev/null +++ b/lib/private/l10n/nb_NO.php @@ -0,0 +1,33 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Hjelp", +"Personal" => "Personlig", +"Settings" => "Innstillinger", +"Users" => "Brukere", +"Admin" => "Admin", +"web services under your control" => "web tjenester du kontrollerer", +"ZIP download is turned off." => "ZIP-nedlasting av avslått", +"Files need to be downloaded one by one." => "Filene må lastes ned en om gangen", +"Back to Files" => "Tilbake til filer", +"Selected files too large to generate zip file." => "De valgte filene er for store til å kunne generere ZIP-fil", +"Application is not enabled" => "Applikasjon er ikke påslått", +"Authentication error" => "Autentikasjonsfeil", +"Token expired. Please reload page." => "Symbol utløpt. Vennligst last inn siden på nytt.", +"Files" => "Filer", +"Text" => "Tekst", +"Images" => "Bilder", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Din nettservev er ikke konfigurert korrekt for filsynkronisering. WebDAV ser ut til å ikke funkere.", +"Please double check the <a href='%s'>installation guides</a>." => "Vennligst dobbelsjekk <a href='%s'>installasjonsguiden</a>.", +"seconds ago" => "sekunder siden", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "i dag", +"yesterday" => "i går", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "forrige måned", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "forrige år", +"years ago" => "år siden", +"Could not find category \"%s\"" => "Kunne ikke finne kategori \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/ne.php b/lib/private/l10n/ne.php new file mode 100644 index 00000000000..15f78e0bce6 --- /dev/null +++ b/lib/private/l10n/ne.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/nl.php b/lib/private/l10n/nl.php new file mode 100644 index 00000000000..20374f1f0f8 --- /dev/null +++ b/lib/private/l10n/nl.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "App \"%s\" kan niet worden geïnstalleerd omdat die niet compatible is met deze versie van ownCloud.", +"No app name specified" => "De app naam is niet gespecificeerd.", +"Help" => "Help", +"Personal" => "Persoonlijk", +"Settings" => "Instellingen", +"Users" => "Gebruikers", +"Admin" => "Beheerder", +"Failed to upgrade \"%s\"." => "Upgrade \"%s\" mislukt.", +"Custom profile pictures don't work with encryption yet" => "Maatwerk profielafbeelding werkt nog niet met versleuteling", +"Unknown filetype" => "Onbekend bestandsformaat", +"Invalid image" => "Ongeldige afbeelding", +"web services under your control" => "Webdiensten in eigen beheer", +"cannot open \"%s\"" => "Kon \"%s\" niet openen", +"ZIP download is turned off." => "ZIP download is uitgeschakeld.", +"Files need to be downloaded one by one." => "Bestanden moeten één voor één worden gedownload.", +"Back to Files" => "Terug naar bestanden", +"Selected files too large to generate zip file." => "De geselecteerde bestanden zijn te groot om een zip bestand te maken.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Download de bestanden in kleinere brokken, appart of vraag uw administrator.", +"No source specified when installing app" => "Geen bron opgegeven bij installatie van de app", +"No href specified when installing app from http" => "Geen href opgegeven bij installeren van de app vanaf http", +"No path specified when installing app from local file" => "Geen pad opgegeven bij installeren van de app vanaf een lokaal bestand", +"Archives of type %s are not supported" => "Archiefbestanden van type %s niet ondersteund", +"Failed to open archive when installing app" => "Kon archiefbestand bij installatie van de app niet openen", +"App does not provide an info.xml file" => "De app heeft geen info.xml bestand", +"App can't be installed because of not allowed code in the App" => "De app kan niet worden geïnstalleerd wegens onjuiste code in de app", +"App can't be installed because it is not compatible with this version of ownCloud" => "De app kan niet worden geïnstalleerd omdat die niet compatible is met deze versie van ownCloud", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "De app kan niet worden geïnstallerd omdat het de <shipped>true</shipped> tag bevat die niet is toegestaan voor niet gepubliceerde apps", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "De app kan niet worden geïnstalleerd omdat de versie in info.xml/version niet dezelfde is als de versie zoals die in de app store staat vermeld", +"App directory already exists" => "App directory bestaat al", +"Can't create app folder. Please fix permissions. %s" => "Kan de app map niet aanmaken, Herstel de permissies. %s", +"Application is not enabled" => "De applicatie is niet actief", +"Authentication error" => "Authenticatie fout", +"Token expired. Please reload page." => "Token verlopen. Herlaad de pagina.", +"Files" => "Bestanden", +"Text" => "Tekst", +"Images" => "Afbeeldingen", +"%s enter the database username." => "%s opgeven database gebruikersnaam.", +"%s enter the database name." => "%s opgeven databasenaam.", +"%s you may not use dots in the database name" => "%s er mogen geen puntjes in de databasenaam voorkomen", +"MS SQL username and/or password not valid: %s" => "MS SQL gebruikersnaam en/of wachtwoord niet geldig: %s", +"You need to enter either an existing account or the administrator." => "Geef of een bestaand account op of het beheerdersaccount.", +"MySQL username and/or password not valid" => "MySQL gebruikersnaam en/of wachtwoord ongeldig", +"DB Error: \"%s\"" => "DB Fout: \"%s\"", +"Offending command was: \"%s\"" => "Onjuiste commande was: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL gebruiker '%s'@'localhost' bestaat al.", +"Drop this user from MySQL" => "Verwijder deze gebruiker uit MySQL", +"MySQL user '%s'@'%%' already exists" => "MySQL gebruiker '%s'@'%%' bestaat al", +"Drop this user from MySQL." => "Verwijder deze gebruiker uit MySQL.", +"Oracle connection could not be established" => "Er kon geen verbinding met Oracle worden bereikt", +"Oracle username and/or password not valid" => "Oracle gebruikersnaam en/of wachtwoord ongeldig", +"Offending command was: \"%s\", name: %s, password: %s" => "Onjuiste commando was: \"%s\", naam: %s, wachtwoord: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL gebruikersnaam en/of wachtwoord ongeldig", +"Set an admin username." => "Stel de gebruikersnaam van de beheerder in.", +"Set an admin password." => "Stel een beheerderswachtwoord in.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Uw webserver is nog niet goed ingesteld voor bestandssynchronisatie omdat de WebDAV interface verbroken lijkt.", +"Please double check the <a href='%s'>installation guides</a>." => "Controleer de <a href='%s'>installatiehandleiding</a> goed.", +"seconds ago" => "seconden geleden", +"_%n minute ago_::_%n minutes ago_" => array("%n minuut geleden","%n minuten geleden"), +"_%n hour ago_::_%n hours ago_" => array("%n uur geleden","%n uur geleden"), +"today" => "vandaag", +"yesterday" => "gisteren", +"_%n day go_::_%n days ago_" => array("%n dag terug","%n dagen geleden"), +"last month" => "vorige maand", +"_%n month ago_::_%n months ago_" => array("%n maand geleden","%n maanden geleden"), +"last year" => "vorig jaar", +"years ago" => "jaar geleden", +"Caused by:" => "Gekomen door:", +"Could not find category \"%s\"" => "Kon categorie \"%s\" niet vinden" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/nn_NO.php b/lib/private/l10n/nn_NO.php new file mode 100644 index 00000000000..e8bf8dfdef4 --- /dev/null +++ b/lib/private/l10n/nn_NO.php @@ -0,0 +1,27 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Hjelp", +"Personal" => "Personleg", +"Settings" => "Innstillingar", +"Users" => "Brukarar", +"Admin" => "Administrer", +"Unknown filetype" => "Ukjend filtype", +"Invalid image" => "Ugyldig bilete", +"web services under your control" => "Vev tjenester under din kontroll", +"Authentication error" => "Feil i autentisering", +"Files" => "Filer", +"Text" => "Tekst", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Tenaren din er ikkje enno rett innstilt til å tilby filsynkronisering sidan WebDAV-grensesnittet ser ut til å vera øydelagt.", +"Please double check the <a href='%s'>installation guides</a>." => "Ver venleg og dobbeltsjekk <a href='%s'>installasjonsrettleiinga</a>.", +"seconds ago" => "sekund sidan", +"_%n minute ago_::_%n minutes ago_" => array("","%n minutt sidan"), +"_%n hour ago_::_%n hours ago_" => array("","%n timar sidan"), +"today" => "i dag", +"yesterday" => "i går", +"_%n day go_::_%n days ago_" => array("","%n dagar sidan"), +"last month" => "førre månad", +"_%n month ago_::_%n months ago_" => array("","%n månadar sidan"), +"last year" => "i fjor", +"years ago" => "år sidan" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/nqo.php b/lib/private/l10n/nqo.php new file mode 100644 index 00000000000..e7b09649a24 --- /dev/null +++ b/lib/private/l10n/nqo.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"_%n day go_::_%n days ago_" => array(""), +"_%n month ago_::_%n months ago_" => array("") +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/oc.php b/lib/private/l10n/oc.php new file mode 100644 index 00000000000..40a527cc76c --- /dev/null +++ b/lib/private/l10n/oc.php @@ -0,0 +1,25 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Ajuda", +"Personal" => "Personal", +"Settings" => "Configuracion", +"Users" => "Usancièrs", +"Admin" => "Admin", +"web services under your control" => "Services web jos ton contraròtle", +"ZIP download is turned off." => "Avalcargar los ZIP es inactiu.", +"Files need to be downloaded one by one." => "Los fichièrs devan èsser avalcargats un per un.", +"Back to Files" => "Torna cap als fichièrs", +"Authentication error" => "Error d'autentificacion", +"Files" => "Fichièrs", +"seconds ago" => "segonda a", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "uèi", +"yesterday" => "ièr", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "mes passat", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "an passat", +"years ago" => "ans a" +); +$PLURAL_FORMS = "nplurals=2; plural=(n > 1);"; diff --git a/lib/private/l10n/pa.php b/lib/private/l10n/pa.php new file mode 100644 index 00000000000..069fea6e710 --- /dev/null +++ b/lib/private/l10n/pa.php @@ -0,0 +1,16 @@ +<?php +$TRANSLATIONS = array( +"Settings" => "ਸੈਟਿੰਗ", +"Files" => "ਫਾਇਲਾਂ", +"seconds ago" => "ਸਕਿੰਟ ਪਹਿਲਾਂ", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "ਅੱਜ", +"yesterday" => "ਕੱਲ੍ਹ", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "ਪਿਛਲੇ ਮਹੀਨੇ", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "ਪਿਛਲੇ ਸਾਲ", +"years ago" => "ਸਾਲਾਂ ਪਹਿਲਾਂ" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/pl.php b/lib/private/l10n/pl.php new file mode 100644 index 00000000000..270559b4e50 --- /dev/null +++ b/lib/private/l10n/pl.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Aplikacja \"%s\" nie może zostać zainstalowana, ponieważ nie jest zgodna z tą wersją ownCloud.", +"No app name specified" => "Nie określono nazwy aplikacji", +"Help" => "Pomoc", +"Personal" => "Osobiste", +"Settings" => "Ustawienia", +"Users" => "Użytkownicy", +"Admin" => "Administrator", +"Failed to upgrade \"%s\"." => "Błąd przy aktualizacji \"%s\".", +"Custom profile pictures don't work with encryption yet" => "Domyślny profil zdjęć nie działa z szyfrowaniem jeszcze", +"Unknown filetype" => "Nieznany typ pliku", +"Invalid image" => "Błędne zdjęcie", +"web services under your control" => "Kontrolowane serwisy", +"cannot open \"%s\"" => "Nie można otworzyć \"%s\"", +"ZIP download is turned off." => "Pobieranie ZIP jest wyłączone.", +"Files need to be downloaded one by one." => "Pliki muszą zostać pobrane pojedynczo.", +"Back to Files" => "Wróć do plików", +"Selected files too large to generate zip file." => "Wybrane pliki są zbyt duże, aby wygenerować plik zip.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Pobierz pliki w mniejszy kawałkach, oddzielnie lub poproś administratora o zwiększenie limitu.", +"No source specified when installing app" => "Nie określono źródła podczas instalacji aplikacji", +"No href specified when installing app from http" => "Nie określono linku skąd aplikacja ma być zainstalowana", +"No path specified when installing app from local file" => "Nie określono lokalnego pliku z którego miała być instalowana aplikacja", +"Archives of type %s are not supported" => "Typ archiwum %s nie jest obsługiwany", +"Failed to open archive when installing app" => "Nie udało się otworzyć archiwum podczas instalacji aplikacji", +"App does not provide an info.xml file" => "Aplikacja nie posiada pliku info.xml", +"App can't be installed because of not allowed code in the App" => "Aplikacja nie może być zainstalowany ponieważ nie dopuszcza kod w aplikacji", +"App can't be installed because it is not compatible with this version of ownCloud" => "Aplikacja nie może zostać zainstalowana ponieważ jest niekompatybilna z tą wersja ownCloud", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "Aplikacja nie może być zainstalowana ponieważ true tag nie jest <shipped>true</shipped> , co nie jest dozwolone dla aplikacji nie wysłanych", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "Nie można zainstalować aplikacji, ponieważ w wersji info.xml/version nie jest taka sama, jak wersja z app store", +"App directory already exists" => "Katalog aplikacji już isnieje", +"Can't create app folder. Please fix permissions. %s" => "Nie mogę utworzyć katalogu aplikacji. Proszę popraw uprawnienia. %s", +"Application is not enabled" => "Aplikacja nie jest włączona", +"Authentication error" => "Błąd uwierzytelniania", +"Token expired. Please reload page." => "Token wygasł. Proszę ponownie załadować stronę.", +"Files" => "Pliki", +"Text" => "Połączenie tekstowe", +"Images" => "Obrazy", +"%s enter the database username." => "%s wpisz nazwę użytkownika do bazy", +"%s enter the database name." => "%s wpisz nazwę bazy.", +"%s you may not use dots in the database name" => "%s nie można używać kropki w nazwie bazy danych", +"MS SQL username and/or password not valid: %s" => "Nazwa i/lub hasło serwera MS SQL jest niepoprawne: %s.", +"You need to enter either an existing account or the administrator." => "Należy wprowadzić istniejące konto użytkownika lub administratora.", +"MySQL username and/or password not valid" => "MySQL: Nazwa użytkownika i/lub hasło jest niepoprawne", +"DB Error: \"%s\"" => "Błąd DB: \"%s\"", +"Offending command was: \"%s\"" => "Niepoprawna komenda: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "Użytkownik MySQL '%s'@'localhost' już istnieje", +"Drop this user from MySQL" => "Usuń tego użytkownika z MySQL", +"MySQL user '%s'@'%%' already exists" => "Użytkownik MySQL '%s'@'%%t' już istnieje", +"Drop this user from MySQL." => "Usuń tego użytkownika z MySQL.", +"Oracle connection could not be established" => "Nie można ustanowić połączenia z bazą Oracle", +"Oracle username and/or password not valid" => "Oracle: Nazwa użytkownika i/lub hasło jest niepoprawne", +"Offending command was: \"%s\", name: %s, password: %s" => "Niepoprawne polecania: \"%s\", nazwa: %s, hasło: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL: Nazwa użytkownika i/lub hasło jest niepoprawne", +"Set an admin username." => "Ustaw nazwę administratora.", +"Set an admin password." => "Ustaw hasło administratora.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Serwer internetowy nie jest jeszcze poprawnie skonfigurowany, aby umożliwić synchronizację plików, ponieważ interfejs WebDAV wydaje się być uszkodzony.", +"Please double check the <a href='%s'>installation guides</a>." => "Sprawdź ponownie <a href='%s'>przewodniki instalacji</a>.", +"Could not find category \"%s\"" => "Nie można odnaleźć kategorii \"%s\"", +"seconds ago" => "sekund temu", +"_%n minute ago_::_%n minutes ago_" => array("%n minute temu","%n minut temu","%n minut temu"), +"_%n hour ago_::_%n hours ago_" => array("%n godzinę temu","%n godzin temu","%n godzin temu"), +"today" => "dziś", +"yesterday" => "wczoraj", +"_%n day go_::_%n days ago_" => array("%n dzień temu","%n dni temu","%n dni temu"), +"last month" => "w zeszłym miesiącu", +"_%n month ago_::_%n months ago_" => array("%n miesiąc temu","%n miesięcy temu","%n miesięcy temu"), +"last year" => "w zeszłym roku", +"years ago" => "lat temu", +"Caused by:" => "Spowodowane przez:" +); +$PLURAL_FORMS = "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/lib/private/l10n/pl_PL.php b/lib/private/l10n/pl_PL.php new file mode 100644 index 00000000000..5494e3dab25 --- /dev/null +++ b/lib/private/l10n/pl_PL.php @@ -0,0 +1,5 @@ +<?php +$TRANSLATIONS = array( +"Settings" => "Ustawienia" +); +$PLURAL_FORMS = "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/lib/private/l10n/pt_BR.php b/lib/private/l10n/pt_BR.php new file mode 100644 index 00000000000..7a580799701 --- /dev/null +++ b/lib/private/l10n/pt_BR.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "O aplicativo \"%s\" não pode ser instalado porque não é compatível com esta versão do ownCloud.", +"No app name specified" => "O nome do aplicativo não foi especificado.", +"Help" => "Ajuda", +"Personal" => "Pessoal", +"Settings" => "Ajustes", +"Users" => "Usuários", +"Admin" => "Admin", +"Failed to upgrade \"%s\"." => "Falha na atualização de \"%s\".", +"Custom profile pictures don't work with encryption yet" => "Fotos de perfil personalizados ainda não funcionam com criptografia", +"Unknown filetype" => "Tipo de arquivo desconhecido", +"Invalid image" => "Imagem inválida", +"web services under your control" => "serviços web sob seu controle", +"cannot open \"%s\"" => "não pode abrir \"%s\"", +"ZIP download is turned off." => "Download ZIP está desligado.", +"Files need to be downloaded one by one." => "Arquivos precisam ser baixados um de cada vez.", +"Back to Files" => "Voltar para Arquivos", +"Selected files too large to generate zip file." => "Arquivos selecionados são muito grandes para gerar arquivo zip.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Baixe os arquivos em pedaços menores, separadamente ou solicite educadamente ao seu administrador.", +"No source specified when installing app" => "Nenhuma fonte foi especificada enquanto instalava o aplicativo", +"No href specified when installing app from http" => "Nenhuma href foi especificada enquanto instalava o aplicativo de httml", +"No path specified when installing app from local file" => "Nenhum caminho foi especificado enquanto instalava o aplicativo do arquivo local", +"Archives of type %s are not supported" => "Arquivos do tipo %s não são suportados", +"Failed to open archive when installing app" => "Falha para abrir o arquivo enquanto instalava o aplicativo", +"App does not provide an info.xml file" => "O aplicativo não fornece um arquivo info.xml", +"App can't be installed because of not allowed code in the App" => "O aplicativo não pode ser instalado por causa do código não permitido no Aplivativo", +"App can't be installed because it is not compatible with this version of ownCloud" => "O aplicativo não pode ser instalado porque não é compatível com esta versão do ownCloud", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "O aplicativo não pode ser instalado porque ele contém a marca <shipped>verdadeiro</shipped> que não é permitido para aplicações não embarcadas", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "O aplicativo não pode ser instalado porque a versão em info.xml /versão não é a mesma que a versão relatada na App Store", +"App directory already exists" => "Diretório App já existe", +"Can't create app folder. Please fix permissions. %s" => "Não é possível criar pasta app. Corrija as permissões. %s", +"Application is not enabled" => "Aplicação não está habilitada", +"Authentication error" => "Erro de autenticação", +"Token expired. Please reload page." => "Token expirou. Por favor recarregue a página.", +"Files" => "Arquivos", +"Text" => "Texto", +"Images" => "Imagens", +"%s enter the database username." => "%s insira o nome de usuário do banco de dados.", +"%s enter the database name." => "%s insira o nome do banco de dados.", +"%s you may not use dots in the database name" => "%s você não pode usar pontos no nome do banco de dados", +"MS SQL username and/or password not valid: %s" => "Nome de usuário e/ou senha MS SQL inválido(s): %s", +"You need to enter either an existing account or the administrator." => "Você precisa inserir uma conta existente ou o administrador.", +"MySQL username and/or password not valid" => "Nome de usuário e/ou senha MySQL inválido(s)", +"DB Error: \"%s\"" => "Erro no BD: \"%s\"", +"Offending command was: \"%s\"" => "Comando ofensivo era: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "O usuário MySQL '%s'@'localhost' já existe.", +"Drop this user from MySQL" => "Derrubar este usuário do MySQL", +"MySQL user '%s'@'%%' already exists" => "Usuário MySQL '%s'@'%%' já existe", +"Drop this user from MySQL." => "Derrube este usuário do MySQL.", +"Oracle connection could not be established" => "Conexão Oracle não pode ser estabelecida", +"Oracle username and/or password not valid" => "Nome de usuário e/ou senha Oracle inválido(s)", +"Offending command was: \"%s\", name: %s, password: %s" => "Comando ofensivo era: \"%s\", nome: %s, senha: %s", +"PostgreSQL username and/or password not valid" => "Nome de usuário e/ou senha PostgreSQL inválido(s)", +"Set an admin username." => "Defina um nome de usuário de administrador.", +"Set an admin password." => "Defina uma senha de administrador.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Seu servidor web não está configurado corretamente para permitir sincronização de arquivos porque a interface WebDAV parece estar quebrada.", +"Please double check the <a href='%s'>installation guides</a>." => "Por favor, confira os <a href='%s'>guias de instalação</a>.", +"seconds ago" => "segundos atrás", +"_%n minute ago_::_%n minutes ago_" => array("","ha %n minutos"), +"_%n hour ago_::_%n hours ago_" => array("","ha %n horas"), +"today" => "hoje", +"yesterday" => "ontem", +"_%n day go_::_%n days ago_" => array("","ha %n dias"), +"last month" => "último mês", +"_%n month ago_::_%n months ago_" => array("","ha %n meses"), +"last year" => "último ano", +"years ago" => "anos atrás", +"Caused by:" => "Causados por:", +"Could not find category \"%s\"" => "Impossível localizar categoria \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n > 1);"; diff --git a/lib/private/l10n/pt_PT.php b/lib/private/l10n/pt_PT.php new file mode 100644 index 00000000000..6e2bcba7b10 --- /dev/null +++ b/lib/private/l10n/pt_PT.php @@ -0,0 +1,57 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Ajuda", +"Personal" => "Pessoal", +"Settings" => "Configurações", +"Users" => "Utilizadores", +"Admin" => "Admin", +"Failed to upgrade \"%s\"." => "A actualização \"%s\" falhou.", +"Unknown filetype" => "Ficheiro desconhecido", +"Invalid image" => "Imagem inválida", +"web services under your control" => "serviços web sob o seu controlo", +"cannot open \"%s\"" => "Não foi possível abrir \"%s\"", +"ZIP download is turned off." => "Descarregamento em ZIP está desligado.", +"Files need to be downloaded one by one." => "Os ficheiros precisam de ser descarregados um por um.", +"Back to Files" => "Voltar a Ficheiros", +"Selected files too large to generate zip file." => "Os ficheiros seleccionados são grandes demais para gerar um ficheiro zip.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Descarregue os ficheiros em partes menores, separados ou peça gentilmente ao seu administrador.", +"Application is not enabled" => "A aplicação não está activada", +"Authentication error" => "Erro na autenticação", +"Token expired. Please reload page." => "O token expirou. Por favor recarregue a página.", +"Files" => "Ficheiros", +"Text" => "Texto", +"Images" => "Imagens", +"%s enter the database username." => "%s introduza o nome de utilizador da base de dados", +"%s enter the database name." => "%s introduza o nome da base de dados", +"%s you may not use dots in the database name" => "%s não é permitido utilizar pontos (.) no nome da base de dados", +"MS SQL username and/or password not valid: %s" => "Nome de utilizador/password do MySQL é inválido: %s", +"You need to enter either an existing account or the administrator." => "Precisa de introduzir uma conta existente ou de administrador", +"MySQL username and/or password not valid" => "Nome de utilizador/password do MySQL inválida", +"DB Error: \"%s\"" => "Erro na BD: \"%s\"", +"Offending command was: \"%s\"" => "O comando gerador de erro foi: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "O utilizador '%s'@'localhost' do MySQL já existe.", +"Drop this user from MySQL" => "Eliminar este utilizador do MySQL", +"MySQL user '%s'@'%%' already exists" => "O utilizador '%s'@'%%' do MySQL já existe", +"Drop this user from MySQL." => "Eliminar este utilizador do MySQL", +"Oracle connection could not be established" => "Não foi possível estabelecer a ligação Oracle", +"Oracle username and/or password not valid" => "Nome de utilizador/password do Oracle inválida", +"Offending command was: \"%s\", name: %s, password: %s" => "O comando gerador de erro foi: \"%s\", nome: %s, password: %s", +"PostgreSQL username and/or password not valid" => "Nome de utilizador/password do PostgreSQL inválido", +"Set an admin username." => "Definir um nome de utilizador de administrador", +"Set an admin password." => "Definiar uma password de administrador", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "O seu servidor web não está configurado correctamente para autorizar sincronização de ficheiros, pois o interface WebDAV parece estar com problemas.", +"Please double check the <a href='%s'>installation guides</a>." => "Por favor verifique <a href='%s'>installation guides</a>.", +"seconds ago" => "Minutos atrás", +"_%n minute ago_::_%n minutes ago_" => array("","%n minutos atrás"), +"_%n hour ago_::_%n hours ago_" => array("","%n horas atrás"), +"today" => "hoje", +"yesterday" => "ontem", +"_%n day go_::_%n days ago_" => array("","%n dias atrás"), +"last month" => "ultímo mês", +"_%n month ago_::_%n months ago_" => array("","%n meses atrás"), +"last year" => "ano passado", +"years ago" => "anos atrás", +"Caused by:" => "Causado por:", +"Could not find category \"%s\"" => "Não foi encontrado a categoria \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/ro.php b/lib/private/l10n/ro.php new file mode 100644 index 00000000000..76dafcd03e0 --- /dev/null +++ b/lib/private/l10n/ro.php @@ -0,0 +1,35 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Ajutor", +"Personal" => "Personal", +"Settings" => "Setări", +"Users" => "Utilizatori", +"Admin" => "Admin", +"Unknown filetype" => "Tip fișier necunoscut", +"Invalid image" => "Imagine invalidă", +"web services under your control" => "servicii web controlate de tine", +"ZIP download is turned off." => "Descărcarea ZIP este dezactivată.", +"Files need to be downloaded one by one." => "Fișierele trebuie descărcate unul câte unul.", +"Back to Files" => "Înapoi la fișiere", +"Selected files too large to generate zip file." => "Fișierele selectate sunt prea mari pentru a genera un fișier zip.", +"Application is not enabled" => "Aplicația nu este activată", +"Authentication error" => "Eroare la autentificare", +"Token expired. Please reload page." => "Token expirat. Te rugăm să reîncarci pagina.", +"Files" => "Fișiere", +"Text" => "Text", +"Images" => "Imagini", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Serverul de web nu este încă setat corespunzător pentru a permite sincronizarea fișierelor deoarece interfața WebDAV pare a fi întreruptă.", +"Please double check the <a href='%s'>installation guides</a>." => "Vă rugăm să verificați <a href='%s'>ghiduri de instalare</a>.", +"seconds ago" => "secunde în urmă", +"_%n minute ago_::_%n minutes ago_" => array("","","acum %n minute"), +"_%n hour ago_::_%n hours ago_" => array("","","acum %n ore"), +"today" => "astăzi", +"yesterday" => "ieri", +"_%n day go_::_%n days ago_" => array("","","acum %n zile"), +"last month" => "ultima lună", +"_%n month ago_::_%n months ago_" => array("","",""), +"last year" => "ultimul an", +"years ago" => "ani în urmă", +"Could not find category \"%s\"" => "Cloud nu a gasit categoria \"%s\"" +); +$PLURAL_FORMS = "nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"; diff --git a/lib/private/l10n/ru.php b/lib/private/l10n/ru.php new file mode 100644 index 00000000000..501065f8b5f --- /dev/null +++ b/lib/private/l10n/ru.php @@ -0,0 +1,72 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Приложение \"%s\" нельзя установить, так как оно не совместимо с текущей версией ownCloud.", +"No app name specified" => "Не выбрано имя приложения", +"Help" => "Помощь", +"Personal" => "Личное", +"Settings" => "Конфигурация", +"Users" => "Пользователи", +"Admin" => "Admin", +"Failed to upgrade \"%s\"." => "Не смог обновить \"%s\".", +"Custom profile pictures don't work with encryption yet" => "Пользовательские картинки профиля ещё не поддерживают шифрование", +"Unknown filetype" => "Неизвестный тип файла", +"Invalid image" => "Изображение повреждено", +"web services under your control" => "веб-сервисы под вашим управлением", +"cannot open \"%s\"" => "не могу открыть \"%s\"", +"ZIP download is turned off." => "ZIP-скачивание отключено.", +"Files need to be downloaded one by one." => "Файлы должны быть загружены по одному.", +"Back to Files" => "Назад к файлам", +"Selected files too large to generate zip file." => "Выбранные файлы слишком велики, чтобы создать zip файл.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Загрузите файл маленьшими порциями, раздельно или вежливо попросите Вашего администратора.", +"No source specified when installing app" => "Не указан источник при установке приложения", +"No href specified when installing app from http" => "Не указан атрибут href при установке приложения через http", +"No path specified when installing app from local file" => "Не указан путь при установке приложения из локального файла", +"Archives of type %s are not supported" => "Архивы %s не поддерживаются", +"Failed to open archive when installing app" => "Не возможно открыть архив при установке приложения", +"App does not provide an info.xml file" => "Приложение не имеет файла info.xml", +"App can't be installed because of not allowed code in the App" => "Приложение невозможно установить. В нем содержится запрещенный код.", +"App can't be installed because it is not compatible with this version of ownCloud" => "Приложение невозможно установить. Не совместимо с текущей версией ownCloud.", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "Приложение невозможно установить. Оно содержит параметр <shipped>true</shipped> который не допустим для приложений, не входящих в поставку.", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "Приложение невозможно установить. Версия в info.xml/version не совпадает с версией заявленной в магазине приложений", +"App directory already exists" => "Папка приложения уже существует", +"Can't create app folder. Please fix permissions. %s" => "Не удалось создать директорию. Исправьте права доступа. %s", +"Application is not enabled" => "Приложение не разрешено", +"Authentication error" => "Ошибка аутентификации", +"Token expired. Please reload page." => "Токен просрочен. Перезагрузите страницу.", +"Files" => "Файлы", +"Text" => "Текст", +"Images" => "Изображения", +"%s enter the database username." => "%s введите имя пользователя базы данных.", +"%s enter the database name." => "%s введите имя базы данных.", +"%s you may not use dots in the database name" => "%s Вы не можете использовать точки в имени базы данных", +"MS SQL username and/or password not valid: %s" => "Имя пользователя и/или пароль MS SQL не подходит: %s", +"You need to enter either an existing account or the administrator." => "Вы должны войти или в существующий аккаунт или под администратором.", +"MySQL username and/or password not valid" => "Неверное имя пользователя и/или пароль MySQL", +"DB Error: \"%s\"" => "Ошибка БД: \"%s\"", +"Offending command was: \"%s\"" => "Вызываемая команда была: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "Пользователь MySQL '%s'@'localhost' уже существует.", +"Drop this user from MySQL" => "Удалить этого пользователя из MySQL", +"MySQL user '%s'@'%%' already exists" => "Пользователь MySQL '%s'@'%%' уже существует", +"Drop this user from MySQL." => "Удалить этого пользователя из MySQL.", +"Oracle connection could not be established" => "соединение с Oracle не может быть установлено", +"Oracle username and/or password not valid" => "Неверное имя пользователя и/или пароль Oracle", +"Offending command was: \"%s\", name: %s, password: %s" => "Вызываемая команда была: \"%s\", имя: %s, пароль: %s", +"PostgreSQL username and/or password not valid" => "Неверное имя пользователя и/или пароль PostgreSQL", +"Set an admin username." => "Установить имя пользователя для admin.", +"Set an admin password." => "становит пароль для admin.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Ваш веб сервер до сих пор не настроен правильно для возможности синхронизации файлов, похоже что проблема в неисправности интерфейса WebDAV.", +"Please double check the <a href='%s'>installation guides</a>." => "Пожалуйста, дважды просмотрите <a href='%s'>инструкции по установке</a>.", +"seconds ago" => "несколько секунд назад", +"_%n minute ago_::_%n minutes ago_" => array("%n минута назад","%n минуты назад","%n минут назад"), +"_%n hour ago_::_%n hours ago_" => array("%n час назад","%n часа назад","%n часов назад"), +"today" => "сегодня", +"yesterday" => "вчера", +"_%n day go_::_%n days ago_" => array("%n день назад","%n дня назад","%n дней назад"), +"last month" => "в прошлом месяце", +"_%n month ago_::_%n months ago_" => array("%n месяц назад","%n месяца назад","%n месяцев назад"), +"last year" => "в прошлом году", +"years ago" => "несколько лет назад", +"Caused by:" => "Вызвано:", +"Could not find category \"%s\"" => "Категория \"%s\" не найдена" +); +$PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/lib/private/l10n/si_LK.php b/lib/private/l10n/si_LK.php new file mode 100644 index 00000000000..d10804cae69 --- /dev/null +++ b/lib/private/l10n/si_LK.php @@ -0,0 +1,30 @@ +<?php +$TRANSLATIONS = array( +"Help" => "උදව්", +"Personal" => "පෞද්ගලික", +"Settings" => "සිටුවම්", +"Users" => "පරිශීලකයන්", +"Admin" => "පරිපාලක", +"web services under your control" => "ඔබට පාලනය කළ හැකි වෙබ් සේවාවන්", +"ZIP download is turned off." => "ZIP භාගත කිරීම් අක්රියයි", +"Files need to be downloaded one by one." => "ගොනු එකින් එක භාගත යුතුයි", +"Back to Files" => "ගොනු වෙතට නැවත යන්න", +"Selected files too large to generate zip file." => "තෝරාගත් ගොනු ZIP ගොනුවක් තැනීමට විශාල වැඩිය.", +"Application is not enabled" => "යෙදුම සක්රිය කර නොමැත", +"Authentication error" => "සත්යාපන දෝෂයක්", +"Token expired. Please reload page." => "ටෝකනය කල් ඉකුත් වී ඇත. පිටුව නැවුම් කරන්න", +"Files" => "ගොනු", +"Text" => "පෙළ", +"Images" => "අනු රූ", +"seconds ago" => "තත්පරයන්ට පෙර", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "අද", +"yesterday" => "ඊයේ", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "පෙර මාසයේ", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "පෙර අවුරුද්දේ", +"years ago" => "අවුරුදු කීපයකට පෙර" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/sk.php b/lib/private/l10n/sk.php new file mode 100644 index 00000000000..54812b15a6f --- /dev/null +++ b/lib/private/l10n/sk.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array("","",""), +"_%n hour ago_::_%n hours ago_" => array("","",""), +"_%n day go_::_%n days ago_" => array("","",""), +"_%n month ago_::_%n months ago_" => array("","","") +); +$PLURAL_FORMS = "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"; diff --git a/lib/private/l10n/sk_SK.php b/lib/private/l10n/sk_SK.php new file mode 100644 index 00000000000..13487b039d6 --- /dev/null +++ b/lib/private/l10n/sk_SK.php @@ -0,0 +1,69 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Aplikácia \"%s\" nemôže byť nainštalovaná kvôli nekompatibilite z danou verziou ownCloudu.", +"No app name specified" => "Nešpecifikované meno aplikácie", +"Help" => "Pomoc", +"Personal" => "Osobné", +"Settings" => "Nastavenia", +"Users" => "Používatelia", +"Admin" => "Administrátor", +"Failed to upgrade \"%s\"." => "Zlyhala aktualizácia \"%s\".", +"web services under your control" => "webové služby pod Vašou kontrolou", +"cannot open \"%s\"" => "nemožno otvoriť \"%s\"", +"ZIP download is turned off." => "Sťahovanie súborov ZIP je vypnuté.", +"Files need to be downloaded one by one." => "Súbory musia byť nahrávané jeden za druhým.", +"Back to Files" => "Späť na súbory", +"Selected files too large to generate zip file." => "Zvolené súbory sú príliš veľké na vygenerovanie zip súboru.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Stiahnite súbory po menších častiach, samostatne, alebo sa obráťte na správcu.", +"No source specified when installing app" => "Nešpecifikovaný zdroj pri inštalácii aplikácie", +"No href specified when installing app from http" => "Nešpecifikovaný atribút \"href\" pri inštalácii aplikácie pomocou protokolu \"http\"", +"No path specified when installing app from local file" => "Nešpecifikovaná cesta pri inštalácii aplikácie z lokálneho súboru", +"Archives of type %s are not supported" => "Typ archívu %s nie je podporovaný", +"Failed to open archive when installing app" => "Zlyhanie pri otváraní archívu počas inštalácie aplikácie", +"App does not provide an info.xml file" => "Aplikácia neposkytuje súbor info.xml", +"App can't be installed because of not allowed code in the App" => "Aplikácia nemôže byť inštalovaná pre nepovolený kód v aplikácii", +"App can't be installed because it is not compatible with this version of ownCloud" => "Aplikácia nemôže byť inštalovaná pre nekompatibilitu z danou verziou ownCloudu", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "Aplikácia nemôže byť inštalovaná pretože obsahuje <shipped>pravý</shipped> štítok, ktorý nie je povolený pre zaslané \"shipped\" aplikácie", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "Aplikácia nemôže byť inštalovaná pretože verzia v info.xml/version nezodpovedá verzii špecifikovanej v aplikačnom obchode", +"App directory already exists" => "Aplikačný adresár už existuje", +"Can't create app folder. Please fix permissions. %s" => "Nemožno vytvoriť aplikačný priečinok. Prosím upravte povolenia. %s", +"Application is not enabled" => "Aplikácia nie je zapnutá", +"Authentication error" => "Chyba autentifikácie", +"Token expired. Please reload page." => "Token vypršal. Obnovte, prosím, stránku.", +"Files" => "Súbory", +"Text" => "Text", +"Images" => "Obrázky", +"%s enter the database username." => "Zadajte používateľské meno %s databázy..", +"%s enter the database name." => "Zadajte názov databázy pre %s databázy.", +"%s you may not use dots in the database name" => "V názve databázy %s nemôžete používať bodky", +"MS SQL username and/or password not valid: %s" => "Používateľské meno, alebo heslo MS SQL nie je platné: %s", +"You need to enter either an existing account or the administrator." => "Musíte zadať jestvujúci účet alebo administrátora.", +"MySQL username and/or password not valid" => "Používateľské meno a/alebo heslo pre MySQL databázu je neplatné", +"DB Error: \"%s\"" => "Chyba DB: \"%s\"", +"Offending command was: \"%s\"" => "Podozrivý príkaz bol: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "Používateľ '%s'@'localhost' už v MySQL existuje.", +"Drop this user from MySQL" => "Zahodiť používateľa z MySQL.", +"MySQL user '%s'@'%%' already exists" => "Používateľ '%s'@'%%' už v MySQL existuje", +"Drop this user from MySQL." => "Zahodiť používateľa z MySQL.", +"Oracle connection could not be established" => "Nie je možné pripojiť sa k Oracle", +"Oracle username and/or password not valid" => "Používateľské meno a/alebo heslo pre Oracle databázu je neplatné", +"Offending command was: \"%s\", name: %s, password: %s" => "Podozrivý príkaz bol: \"%s\", meno: %s, heslo: %s", +"PostgreSQL username and/or password not valid" => "Používateľské meno a/alebo heslo pre PostgreSQL databázu je neplatné", +"Set an admin username." => "Zadajte používateľské meno administrátora.", +"Set an admin password." => "Zadajte heslo administrátora.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Váš webový server nie je správne nastavený na synchronizáciu, pretože rozhranie WebDAV je poškodené.", +"Please double check the <a href='%s'>installation guides</a>." => "Prosím skontrolujte <a href='%s'>inštalačnú príručku</a>.", +"seconds ago" => "pred sekundami", +"_%n minute ago_::_%n minutes ago_" => array("","","pred %n minútami"), +"_%n hour ago_::_%n hours ago_" => array("","","pred %n hodinami"), +"today" => "dnes", +"yesterday" => "včera", +"_%n day go_::_%n days ago_" => array("","","pred %n dňami"), +"last month" => "minulý mesiac", +"_%n month ago_::_%n months ago_" => array("","","pred %n mesiacmi"), +"last year" => "minulý rok", +"years ago" => "pred rokmi", +"Caused by:" => "Príčina:", +"Could not find category \"%s\"" => "Nemožno nájsť danú kategóriu \"%s\"" +); +$PLURAL_FORMS = "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"; diff --git a/lib/private/l10n/sl.php b/lib/private/l10n/sl.php new file mode 100644 index 00000000000..5722191aedf --- /dev/null +++ b/lib/private/l10n/sl.php @@ -0,0 +1,51 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Pomoč", +"Personal" => "Osebno", +"Settings" => "Nastavitve", +"Users" => "Uporabniki", +"Admin" => "Skrbništvo", +"web services under your control" => "spletne storitve pod vašim nadzorom", +"ZIP download is turned off." => "Prejemanje datotek v paketu ZIP je onemogočeno.", +"Files need to be downloaded one by one." => "Datoteke je mogoče prejeti le posamično.", +"Back to Files" => "Nazaj na datoteke", +"Selected files too large to generate zip file." => "Izbrane datoteke so prevelike za ustvarjanje datoteke arhiva zip.", +"Application is not enabled" => "Program ni omogočen", +"Authentication error" => "Napaka pri overjanju", +"Token expired. Please reload page." => "Žeton je potekel. Stran je treba ponovno naložiti.", +"Files" => "Datoteke", +"Text" => "Besedilo", +"Images" => "Slike", +"%s enter the database username." => "%s - vnos uporabniškega imena podatkovne zbirke.", +"%s enter the database name." => "%s - vnos imena podatkovne zbirke.", +"%s you may not use dots in the database name" => "%s - v imenu podatkovne zbirke ni dovoljeno uporabljati pik.", +"MS SQL username and/or password not valid: %s" => "Uporabniško ime ali geslo MS SQL ni veljavno: %s", +"You need to enter either an existing account or the administrator." => "Prijaviti se je treba v obstoječi ali pa skrbniški račun.", +"MySQL username and/or password not valid" => "Uporabniško ime ali geslo MySQL ni veljavno", +"DB Error: \"%s\"" => "Napaka podatkovne zbirke: \"%s\"", +"Offending command was: \"%s\"" => "Napačni ukaz je: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "Uporabnik MySQL '%s'@'localhost' že obstaja.", +"Drop this user from MySQL" => "Odstrani uporabnika s podatkovne zbirke MySQL", +"MySQL user '%s'@'%%' already exists" => "Uporabnik MySQL '%s'@'%%' že obstaja.", +"Drop this user from MySQL." => "Odstrani uporabnika s podatkovne zbirke MySQL", +"Oracle connection could not be established" => "Povezava z bazo Oracle ni uspela.", +"Oracle username and/or password not valid" => "Uporabniško ime ali geslo Oracle ni veljavno", +"Offending command was: \"%s\", name: %s, password: %s" => "Napačni ukaz je: \"%s\", ime: %s, geslo: %s", +"PostgreSQL username and/or password not valid" => "Uporabniško ime ali geslo PostgreSQL ni veljavno", +"Set an admin username." => "Nastavi uporabniško ime skrbnika.", +"Set an admin password." => "Nastavi geslo skrbnika.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Spletni stražnik še ni ustrezno nastavljen in ne omogoča usklajevanja, saj je nastavitev WebDAV okvarjena.", +"Please double check the <a href='%s'>installation guides</a>." => "Preverite <a href='%s'>navodila namestitve</a>.", +"seconds ago" => "pred nekaj sekundami", +"_%n minute ago_::_%n minutes ago_" => array("","","",""), +"_%n hour ago_::_%n hours ago_" => array("","","",""), +"today" => "danes", +"yesterday" => "včeraj", +"_%n day go_::_%n days ago_" => array("","","",""), +"last month" => "zadnji mesec", +"_%n month ago_::_%n months ago_" => array("","","",""), +"last year" => "lansko leto", +"years ago" => "let nazaj", +"Could not find category \"%s\"" => "Kategorije \"%s\" ni mogoče najti." +); +$PLURAL_FORMS = "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"; diff --git a/lib/private/l10n/sq.php b/lib/private/l10n/sq.php new file mode 100644 index 00000000000..edaa1df2b86 --- /dev/null +++ b/lib/private/l10n/sq.php @@ -0,0 +1,50 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Ndihmë", +"Personal" => "Personale", +"Settings" => "Parametra", +"Users" => "Përdoruesit", +"Admin" => "Admin", +"web services under your control" => "shërbime web nën kontrollin tënd", +"ZIP download is turned off." => "Shkarimi i skedarëve ZIP është i çaktivizuar.", +"Files need to be downloaded one by one." => "Skedarët duhet të shkarkohen një nga një.", +"Back to Files" => "Kthehu tek skedarët", +"Selected files too large to generate zip file." => "Skedarët e selektuar janë shumë të mëdhenj për të krijuar një skedar ZIP.", +"Application is not enabled" => "Programi nuk është i aktivizuar.", +"Authentication error" => "Veprim i gabuar gjatë vërtetimit të identitetit", +"Token expired. Please reload page." => "Përmbajtja ka skaduar. Ju lutemi ringarkoni faqen.", +"Files" => "Skedarët", +"Text" => "Tekst", +"Images" => "Foto", +"%s enter the database username." => "% shkruani përdoruesin e database-it.", +"%s enter the database name." => "%s shkruani emrin e database-it.", +"%s you may not use dots in the database name" => "%s nuk mund të përdorni pikat tek emri i database-it", +"MS SQL username and/or password not valid: %s" => "Përdoruesi dhe/apo kodi i MS SQL i pavlefshëm: %s", +"You need to enter either an existing account or the administrator." => "Duhet të përdorni një llogari ekzistuese ose llogarinë e administratorit.", +"MySQL username and/or password not valid" => "Përdoruesi dhe/apo kodi i MySQL-it i pavlefshëm.", +"DB Error: \"%s\"" => "Veprim i gabuar i DB-it: \"%s\"", +"Offending command was: \"%s\"" => "Komanda e gabuar ishte: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "Përdoruesi MySQL '%s'@'localhost' ekziston.", +"Drop this user from MySQL" => "Eliminoni këtë përdorues nga MySQL", +"MySQL user '%s'@'%%' already exists" => "Përdoruesi MySQL '%s'@'%%' ekziston", +"Drop this user from MySQL." => "Eliminoni këtë përdorues nga MySQL.", +"Oracle username and/or password not valid" => "Përdoruesi dhe/apo kodi i Oracle-it i pavlefshëm", +"Offending command was: \"%s\", name: %s, password: %s" => "Komanda e gabuar ishte: \"%s\", përdoruesi: %s, kodi: %s", +"PostgreSQL username and/or password not valid" => "Përdoruesi dhe/apo kodi i PostgreSQL i pavlefshëm", +"Set an admin username." => "Cakto emrin e administratorit.", +"Set an admin password." => "Cakto kodin e administratorit.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Serveri web i juaji nuk është konfiguruar akoma për të lejuar sinkronizimin e skedarëve sepse ndërfaqja WebDAV mund të jetë e dëmtuar.", +"Please double check the <a href='%s'>installation guides</a>." => "Ju lutemi kontrolloni mirë <a href='%s'>shoqëruesin e instalimit</a>.", +"seconds ago" => "sekonda më parë", +"_%n minute ago_::_%n minutes ago_" => array("","%n minuta më parë"), +"_%n hour ago_::_%n hours ago_" => array("","%n orë më parë"), +"today" => "sot", +"yesterday" => "dje", +"_%n day go_::_%n days ago_" => array("","%n ditë më parë"), +"last month" => "muajin e shkuar", +"_%n month ago_::_%n months ago_" => array("","%n muaj më parë"), +"last year" => "vitin e shkuar", +"years ago" => "vite më parë", +"Could not find category \"%s\"" => "Kategoria \"%s\" nuk u gjet" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/sr.php b/lib/private/l10n/sr.php new file mode 100644 index 00000000000..9441d0578fc --- /dev/null +++ b/lib/private/l10n/sr.php @@ -0,0 +1,33 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Помоћ", +"Personal" => "Лично", +"Settings" => "Поставке", +"Users" => "Корисници", +"Admin" => "Администратор", +"web services under your control" => "веб сервиси под контролом", +"ZIP download is turned off." => "Преузимање ZIP-а је искључено.", +"Files need to be downloaded one by one." => "Датотеке морате преузимати једну по једну.", +"Back to Files" => "Назад на датотеке", +"Selected files too large to generate zip file." => "Изабране датотеке су превелике да бисте направили ZIP датотеку.", +"Application is not enabled" => "Апликација није омогућена", +"Authentication error" => "Грешка при провери идентитета", +"Token expired. Please reload page." => "Жетон је истекао. Поново учитајте страницу.", +"Files" => "Датотеке", +"Text" => "Текст", +"Images" => "Слике", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Ваш веб сервер тренутно не подржава синхронизацију датотека јер се чини да је WebDAV сучеље неисправно.", +"Please double check the <a href='%s'>installation guides</a>." => "Погледајте <a href='%s'>водиче за инсталацију</a>.", +"seconds ago" => "пре неколико секунди", +"_%n minute ago_::_%n minutes ago_" => array("","",""), +"_%n hour ago_::_%n hours ago_" => array("","",""), +"today" => "данас", +"yesterday" => "јуче", +"_%n day go_::_%n days ago_" => array("","",""), +"last month" => "прошлог месеца", +"_%n month ago_::_%n months ago_" => array("","",""), +"last year" => "прошле године", +"years ago" => "година раније", +"Could not find category \"%s\"" => "Не могу да пронађем категорију „%s“." +); +$PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/lib/private/l10n/sr@latin.php b/lib/private/l10n/sr@latin.php new file mode 100644 index 00000000000..d8fa9289221 --- /dev/null +++ b/lib/private/l10n/sr@latin.php @@ -0,0 +1,22 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Pomoć", +"Personal" => "Lično", +"Settings" => "Podešavanja", +"Users" => "Korisnici", +"Admin" => "Adninistracija", +"Authentication error" => "Greška pri autentifikaciji", +"Files" => "Fajlovi", +"Text" => "Tekst", +"seconds ago" => "Pre par sekundi", +"_%n minute ago_::_%n minutes ago_" => array("","",""), +"_%n hour ago_::_%n hours ago_" => array("","",""), +"today" => "Danas", +"yesterday" => "juče", +"_%n day go_::_%n days ago_" => array("","",""), +"last month" => "prošlog meseca", +"_%n month ago_::_%n months ago_" => array("","",""), +"last year" => "prošle godine", +"years ago" => "pre nekoliko godina" +); +$PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/lib/private/l10n/string.php b/lib/private/l10n/string.php new file mode 100644 index 00000000000..88c85b32e70 --- /dev/null +++ b/lib/private/l10n/string.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_L10N_String{ + /** + * @var OC_L10N + */ + protected $l10n; + + /** + * @var string + */ + protected $text; + + /** + * @var array + */ + protected $parameters; + + /** + * @var integer + */ + protected $count; + + public function __construct($l10n, $text, $parameters, $count = 1) { + $this->l10n = $l10n; + $this->text = $text; + $this->parameters = $parameters; + $this->count = $count; + } + + public function __toString() { + $translations = $this->l10n->getTranslations(); + + $text = $this->text; + if(array_key_exists($this->text, $translations)) { + if(is_array($translations[$this->text])) { + $fn = $this->l10n->getPluralFormFunction(); + $id = $fn($this->count); + $text = $translations[$this->text][$id]; + } + else{ + $text = $translations[$this->text]; + } + } + + // Replace %n first (won't interfere with vsprintf) + $text = str_replace('%n', $this->count, $text); + return vsprintf($text, $this->parameters); + } +} diff --git a/lib/private/l10n/sv.php b/lib/private/l10n/sv.php new file mode 100644 index 00000000000..e7c3420a85b --- /dev/null +++ b/lib/private/l10n/sv.php @@ -0,0 +1,69 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Appen \"%s\" kan inte installeras eftersom att den inte är kompatibel med denna version av ownCloud.", +"No app name specified" => "Inget appnamn angivet", +"Help" => "Hjälp", +"Personal" => "Personligt", +"Settings" => "Inställningar", +"Users" => "Användare", +"Admin" => "Admin", +"Failed to upgrade \"%s\"." => "Misslyckades med att uppgradera \"%s\".", +"web services under your control" => "webbtjänster under din kontroll", +"cannot open \"%s\"" => "Kan inte öppna \"%s\"", +"ZIP download is turned off." => "Nerladdning av ZIP är avstängd.", +"Files need to be downloaded one by one." => "Filer laddas ner en åt gången.", +"Back to Files" => "Tillbaka till Filer", +"Selected files too large to generate zip file." => "Valda filer är för stora för att skapa zip-fil.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Ladda ner filerna i mindre bitar, separat eller fråga din administratör.", +"No source specified when installing app" => "Ingen källa angiven vid installation av app ", +"No href specified when installing app from http" => "Ingen href angiven vid installation av app från http", +"No path specified when installing app from local file" => "Ingen sökväg angiven vid installation av app från lokal fil", +"Archives of type %s are not supported" => "Arkiv av typen %s stöds ej", +"Failed to open archive when installing app" => "Kunde inte öppna arkivet när appen skulle installeras", +"App does not provide an info.xml file" => "Appen har ingen info.xml fil", +"App can't be installed because of not allowed code in the App" => "Appen kan inte installeras eftersom att den innehåller otillåten kod", +"App can't be installed because it is not compatible with this version of ownCloud" => "Appen kan inte installeras eftersom att den inte är kompatibel med denna version av ownCloud", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "Appen kan inte installeras eftersom att den innehåller etiketten <shipped>true</shipped> vilket inte är tillåtet för icke inkluderade appar", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "Appen kan inte installeras eftersom versionen i info.xml inte är samma som rapporteras från app store", +"App directory already exists" => "Appens mapp finns redan", +"Can't create app folder. Please fix permissions. %s" => "Kan inte skapa appens mapp. Var god åtgärda rättigheterna. %s", +"Application is not enabled" => "Applikationen är inte aktiverad", +"Authentication error" => "Fel vid autentisering", +"Token expired. Please reload page." => "Ogiltig token. Ladda om sidan.", +"Files" => "Filer", +"Text" => "Text", +"Images" => "Bilder", +"%s enter the database username." => "%s ange databasanvändare.", +"%s enter the database name." => "%s ange databasnamn", +"%s you may not use dots in the database name" => "%s du får inte använda punkter i databasnamnet", +"MS SQL username and/or password not valid: %s" => "MS SQL-användaren och/eller lösenordet var inte giltigt: %s", +"You need to enter either an existing account or the administrator." => "Du måste antingen ange ett befintligt konto eller administratör.", +"MySQL username and/or password not valid" => "MySQL-användarnamnet och/eller lösenordet är felaktigt", +"DB Error: \"%s\"" => "DB error: \"%s\"", +"Offending command was: \"%s\"" => "Det felaktiga kommandot var: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL-användaren '%s'@'localhost' existerar redan.", +"Drop this user from MySQL" => "Radera denna användare från MySQL", +"MySQL user '%s'@'%%' already exists" => "MySQl-användare '%s'@'%%' existerar redan", +"Drop this user from MySQL." => "Radera denna användare från MySQL.", +"Oracle connection could not be established" => "Oracle-anslutning kunde inte etableras", +"Oracle username and/or password not valid" => "Oracle-användarnamnet och/eller lösenordet är felaktigt", +"Offending command was: \"%s\", name: %s, password: %s" => "Det felande kommandot var: \"%s\", name: %s, password: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL-användarnamnet och/eller lösenordet är felaktigt", +"Set an admin username." => "Ange ett användarnamn för administratören.", +"Set an admin password." => "Ange ett administratörslösenord.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Din webbserver är inte korrekt konfigurerad för att tillåta filsynkronisering eftersom WebDAV inte verkar fungera.", +"Please double check the <a href='%s'>installation guides</a>." => "Var god kontrollera <a href='%s'>installationsguiden</a>.", +"seconds ago" => "sekunder sedan", +"_%n minute ago_::_%n minutes ago_" => array("%n minut sedan","%n minuter sedan"), +"_%n hour ago_::_%n hours ago_" => array("%n timme sedan","%n timmar sedan"), +"today" => "i dag", +"yesterday" => "i går", +"_%n day go_::_%n days ago_" => array("%n dag sedan","%n dagar sedan"), +"last month" => "förra månaden", +"_%n month ago_::_%n months ago_" => array("%n månad sedan","%n månader sedan"), +"last year" => "förra året", +"years ago" => "år sedan", +"Caused by:" => "Orsakad av:", +"Could not find category \"%s\"" => "Kunde inte hitta kategorin \"%s\"" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/sw_KE.php b/lib/private/l10n/sw_KE.php new file mode 100644 index 00000000000..15f78e0bce6 --- /dev/null +++ b/lib/private/l10n/sw_KE.php @@ -0,0 +1,8 @@ +<?php +$TRANSLATIONS = array( +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/ta_LK.php b/lib/private/l10n/ta_LK.php new file mode 100644 index 00000000000..e70e65845be --- /dev/null +++ b/lib/private/l10n/ta_LK.php @@ -0,0 +1,31 @@ +<?php +$TRANSLATIONS = array( +"Help" => "உதவி", +"Personal" => "தனிப்பட்ட", +"Settings" => "அமைப்புகள்", +"Users" => "பயனாளர்", +"Admin" => "நிர்வாகம்", +"web services under your control" => "வலைய சேவைகள் உங்களுடைய கட்டுப்பாட்டின் கீழ் உள்ளது", +"ZIP download is turned off." => "வீசொலிப் பூட்டு பதிவிறக்கம் நிறுத்தப்பட்டுள்ளது.", +"Files need to be downloaded one by one." => "கோப்புகள்ஒன்றன் பின் ஒன்றாக பதிவிறக்கப்படவேண்டும்.", +"Back to Files" => "கோப்புகளுக்கு செல்க", +"Selected files too large to generate zip file." => "வீ சொலிக் கோப்புகளை உருவாக்குவதற்கு தெரிவுசெய்யப்பட்ட கோப்புகள் மிகப்பெரியவை", +"Application is not enabled" => "செயலி இயலுமைப்படுத்தப்படவில்லை", +"Authentication error" => "அத்தாட்சிப்படுத்தலில் வழு", +"Token expired. Please reload page." => "அடையாளவில்லை காலாவதியாகிவிட்டது. தயவுசெய்து பக்கத்தை மீள் ஏற்றுக.", +"Files" => "கோப்புகள்", +"Text" => "உரை", +"Images" => "படங்கள்", +"seconds ago" => "செக்கன்களுக்கு முன்", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "இன்று", +"yesterday" => "நேற்று", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "கடந்த மாதம்", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "கடந்த வருடம்", +"years ago" => "வருடங்களுக்கு முன்", +"Could not find category \"%s\"" => "பிரிவு \"%s\" ஐ கண்டுப்பிடிக்க முடியவில்லை" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/te.php b/lib/private/l10n/te.php new file mode 100644 index 00000000000..524ea0c6024 --- /dev/null +++ b/lib/private/l10n/te.php @@ -0,0 +1,17 @@ +<?php +$TRANSLATIONS = array( +"Help" => "సహాయం", +"Settings" => "అమరికలు", +"Users" => "వాడుకరులు", +"seconds ago" => "క్షణాల క్రితం", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"today" => "ఈరోజు", +"yesterday" => "నిన్న", +"_%n day go_::_%n days ago_" => array("",""), +"last month" => "పోయిన నెల", +"_%n month ago_::_%n months ago_" => array("",""), +"last year" => "పోయిన సంవత్సరం", +"years ago" => "సంవత్సరాల క్రితం" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/th_TH.php b/lib/private/l10n/th_TH.php new file mode 100644 index 00000000000..3344d0bb18e --- /dev/null +++ b/lib/private/l10n/th_TH.php @@ -0,0 +1,31 @@ +<?php +$TRANSLATIONS = array( +"Help" => "ช่วยเหลือ", +"Personal" => "ส่วนตัว", +"Settings" => "ตั้งค่า", +"Users" => "ผู้ใช้งาน", +"Admin" => "ผู้ดูแล", +"web services under your control" => "เว็บเซอร์วิสที่คุณควบคุมการใช้งานได้", +"ZIP download is turned off." => "คุณสมบัติการดาวน์โหลด zip ถูกปิดการใช้งานไว้", +"Files need to be downloaded one by one." => "ไฟล์สามารถดาวน์โหลดได้ทีละครั้งเท่านั้น", +"Back to Files" => "กลับไปที่ไฟล์", +"Selected files too large to generate zip file." => "ไฟล์ที่เลือกมีขนาดใหญ่เกินกว่าที่จะสร้างเป็นไฟล์ zip", +"Application is not enabled" => "แอพพลิเคชั่นดังกล่าวยังไม่ได้เปิดใช้งาน", +"Authentication error" => "เกิดข้อผิดพลาดในสิทธิ์การเข้าใช้งาน", +"Token expired. Please reload page." => "รหัสยืนยันความถูกต้องหมดอายุแล้ว กรุณาโหลดหน้าเว็บใหม่อีกครั้ง", +"Files" => "ไฟล์", +"Text" => "ข้อความ", +"Images" => "รูปภาพ", +"seconds ago" => "วินาที ก่อนหน้านี้", +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"today" => "วันนี้", +"yesterday" => "เมื่อวานนี้", +"_%n day go_::_%n days ago_" => array(""), +"last month" => "เดือนที่แล้ว", +"_%n month ago_::_%n months ago_" => array(""), +"last year" => "ปีที่แล้ว", +"years ago" => "ปี ที่ผ่านมา", +"Could not find category \"%s\"" => "ไม่พบหมวดหมู่ \"%s\"" +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/tr.php b/lib/private/l10n/tr.php new file mode 100644 index 00000000000..b63c37c7240 --- /dev/null +++ b/lib/private/l10n/tr.php @@ -0,0 +1,69 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "Owncloud yazılımının bu sürümü ile uyumlu olmadığı için \"%s\" uygulaması kurulamaz.", +"No app name specified" => "Uygulama adı belirtimedli", +"Help" => "Yardım", +"Personal" => "Kişisel", +"Settings" => "Ayarlar", +"Users" => "Kullanıcılar", +"Admin" => "Yönetici", +"Failed to upgrade \"%s\"." => "\"%s\" yükseltme başarısız oldu.", +"web services under your control" => "Bilgileriniz güvenli ve şifreli", +"cannot open \"%s\"" => "\"%s\" açılamıyor", +"ZIP download is turned off." => "ZIP indirmeleri kapatılmıştır.", +"Files need to be downloaded one by one." => "Dosyaların birer birer indirilmesi gerekmektedir.", +"Back to Files" => "Dosyalara dön", +"Selected files too large to generate zip file." => "Seçilen dosyalar bir zip dosyası oluşturmak için fazla büyüktür.", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "Dosyaları ayrı ayrı, küçük parçalar halinde indirin ya da yöneticinizden yardım isteyin. ", +"No source specified when installing app" => "Uygulama kurulurken bir kaynak belirtilmedi", +"No href specified when installing app from http" => "Uygulama kuruluyorken http'de href belirtilmedi.", +"No path specified when installing app from local file" => "Uygulama yerel dosyadan kuruluyorken dosya yolu belirtilmedi", +"Archives of type %s are not supported" => "%s arşiv tipi desteklenmiyor", +"Failed to open archive when installing app" => "Uygulama kuruluyorken arşiv dosyası açılamadı", +"App does not provide an info.xml file" => "Uygulama info.xml dosyası sağlamıyor", +"App can't be installed because of not allowed code in the App" => "Uygulamada izin verilmeyeden kodlar olduğu için kurulamıyor.", +"App can't be installed because it is not compatible with this version of ownCloud" => "Owncloud versiyonunuz ile uyumsuz olduğu için uygulama kurulamıyor.", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "Uygulama kurulamıyor. Çünkü \"non shipped\" uygulamalar için <shipped>true</shipped> tag içermektedir.", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "Uygulama kurulamıyor çünkü info.xml/version ile uygulama marketde belirtilen sürüm aynı değil.", +"App directory already exists" => "App dizini zaten mevcut", +"Can't create app folder. Please fix permissions. %s" => "app dizini oluşturulamıyor. Lütfen izinleri düzeltin. %s", +"Application is not enabled" => "Uygulama etkinleştirilmedi", +"Authentication error" => "Kimlik doğrulama hatası", +"Token expired. Please reload page." => "Jetonun süresi geçti. Lütfen sayfayı yenileyin.", +"Files" => "Dosyalar", +"Text" => "Metin", +"Images" => "Resimler", +"%s enter the database username." => "%s veritabanı kullanıcı adını gir.", +"%s enter the database name." => "%s veritabanı adını gir.", +"%s you may not use dots in the database name" => "%s veritabanı adında nokta kullanamayabilirsiniz", +"MS SQL username and/or password not valid: %s" => "MS SQL kullanıcı adı ve/veya parolası geçersiz: %s", +"You need to enter either an existing account or the administrator." => "Bir konto veya kullanici birlemek ihtiyacin. ", +"MySQL username and/or password not valid" => "MySQL kullanıcı adı ve/veya parolası geçerli değil", +"DB Error: \"%s\"" => "DB Hata: ''%s''", +"Offending command was: \"%s\"" => "Komut rahasiz ''%s''. ", +"MySQL user '%s'@'localhost' exists already." => "MySQL kullanici '%s @local host zatan var. ", +"Drop this user from MySQL" => "Bu kullanici MySQLden list disari koymak. ", +"MySQL user '%s'@'%%' already exists" => "MySQL kullanici '%s @ % % zaten var (zaten yazili)", +"Drop this user from MySQL." => "Bu kulanıcıyı MySQL veritabanından kaldır", +"Oracle connection could not be established" => "Oracle bağlantısı kurulamadı", +"Oracle username and/or password not valid" => "Adi klullanici ve/veya parola Oracle mantikli değildir. ", +"Offending command was: \"%s\", name: %s, password: %s" => "Hatalı komut: \"%s\", ad: %s, parola: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL adi kullanici ve/veya parola yasal degildir. ", +"Set an admin username." => "Bir adi kullanici vermek. ", +"Set an admin password." => "Parola yonetici birlemek. ", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Web sunucunuz dosya transferi için düzgün bir şekilde yapılandırılmamış. WevDAV arabirimini sorunlu gözüküyor.", +"Please double check the <a href='%s'>installation guides</a>." => "Lütfen <a href='%s'>kurulum kılavuzlarını</a> iki kez kontrol edin.", +"seconds ago" => "saniye önce", +"_%n minute ago_::_%n minutes ago_" => array("","%n dakika önce"), +"_%n hour ago_::_%n hours ago_" => array("","%n saat önce"), +"today" => "bugün", +"yesterday" => "dün", +"_%n day go_::_%n days ago_" => array("","%n gün önce"), +"last month" => "geçen ay", +"_%n month ago_::_%n months ago_" => array("","%n ay önce"), +"last year" => "geçen yıl", +"years ago" => "yıl önce", +"Caused by:" => "Neden olan:", +"Could not find category \"%s\"" => "\"%s\" kategorisi bulunamadı" +); +$PLURAL_FORMS = "nplurals=2; plural=(n > 1);"; diff --git a/lib/private/l10n/ug.php b/lib/private/l10n/ug.php new file mode 100644 index 00000000000..e2cf38ecc8c --- /dev/null +++ b/lib/private/l10n/ug.php @@ -0,0 +1,19 @@ +<?php +$TRANSLATIONS = array( +"Help" => "ياردەم", +"Personal" => "شەخسىي", +"Settings" => "تەڭشەكلەر", +"Users" => "ئىشلەتكۈچىلەر", +"Authentication error" => "سالاھىيەت دەلىللەش خاتالىقى", +"Files" => "ھۆججەتلەر", +"Text" => "قىسقا ئۇچۇر", +"Images" => "سۈرەتلەر", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "سىزنىڭ تور مۇلازىمېتىرىڭىز ھۆججەت قەدەمداشلاشقا يول قويىدىغان قىلىپ توغرا تەڭشەلمەپتۇ، چۈنكى WebDAV نىڭ ئېغىزى بۇزۇلغاندەك تۇرىدۇ.", +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"today" => "بۈگۈن", +"yesterday" => "تۈنۈگۈن", +"_%n day go_::_%n days ago_" => array(""), +"_%n month ago_::_%n months ago_" => array("") +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/uk.php b/lib/private/l10n/uk.php new file mode 100644 index 00000000000..c1513c5bb79 --- /dev/null +++ b/lib/private/l10n/uk.php @@ -0,0 +1,50 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Допомога", +"Personal" => "Особисте", +"Settings" => "Налаштування", +"Users" => "Користувачі", +"Admin" => "Адмін", +"web services under your control" => "підконтрольні Вам веб-сервіси", +"ZIP download is turned off." => "ZIP завантаження вимкнено.", +"Files need to be downloaded one by one." => "Файли повинні бути завантаженні послідовно.", +"Back to Files" => "Повернутися до файлів", +"Selected files too large to generate zip file." => "Вибрані фали завеликі для генерування zip файлу.", +"Application is not enabled" => "Додаток не увімкнений", +"Authentication error" => "Помилка автентифікації", +"Token expired. Please reload page." => "Строк дії токена скінчився. Будь ласка, перезавантажте сторінку.", +"Files" => "Файли", +"Text" => "Текст", +"Images" => "Зображення", +"%s enter the database username." => "%s введіть ім'я користувача бази даних.", +"%s enter the database name." => "%s введіть назву бази даних.", +"%s you may not use dots in the database name" => "%s не можна використовувати крапки в назві бази даних", +"MS SQL username and/or password not valid: %s" => "MS SQL ім'я користувача та/або пароль не дійсні: %s", +"You need to enter either an existing account or the administrator." => "Вам потрібно ввести або існуючий обліковий запис або administrator.", +"MySQL username and/or password not valid" => "MySQL ім'я користувача та/або пароль не дійсні", +"DB Error: \"%s\"" => "Помилка БД: \"%s\"", +"Offending command was: \"%s\"" => "Команда, що викликала проблему: \"%s\"", +"MySQL user '%s'@'localhost' exists already." => "Користувач MySQL '%s'@'localhost' вже існує.", +"Drop this user from MySQL" => "Видалити цього користувача з MySQL", +"MySQL user '%s'@'%%' already exists" => "Користувач MySQL '%s'@'%%' вже існує", +"Drop this user from MySQL." => "Видалити цього користувача з MySQL.", +"Oracle username and/or password not valid" => "Oracle ім'я користувача та/або пароль не дійсні", +"Offending command was: \"%s\", name: %s, password: %s" => "Команда, що викликала проблему: \"%s\", ім'я: %s, пароль: %s", +"PostgreSQL username and/or password not valid" => "PostgreSQL ім'я користувача та/або пароль не дійсні", +"Set an admin username." => "Встановіть ім'я адміністратора.", +"Set an admin password." => "Встановіть пароль адміністратора.", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Ваш Web-сервер ще не налаштований належним чином для того, щоб дозволити синхронізацію файлів, через те що інтерфейс WebDAV, здається, зламаний.", +"Please double check the <a href='%s'>installation guides</a>." => "Будь ласка, перевірте <a href='%s'>інструкції по встановленню</a>.", +"seconds ago" => "секунди тому", +"_%n minute ago_::_%n minutes ago_" => array("","",""), +"_%n hour ago_::_%n hours ago_" => array("","",""), +"today" => "сьогодні", +"yesterday" => "вчора", +"_%n day go_::_%n days ago_" => array("","",""), +"last month" => "минулого місяця", +"_%n month ago_::_%n months ago_" => array("","",""), +"last year" => "минулого року", +"years ago" => "роки тому", +"Could not find category \"%s\"" => "Не вдалося знайти категорію \"%s\"" +); +$PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/lib/private/l10n/ur_PK.php b/lib/private/l10n/ur_PK.php new file mode 100644 index 00000000000..7dc967ccd93 --- /dev/null +++ b/lib/private/l10n/ur_PK.php @@ -0,0 +1,14 @@ +<?php +$TRANSLATIONS = array( +"Help" => "مدد", +"Personal" => "ذاتی", +"Settings" => "سیٹینگز", +"Users" => "یوزرز", +"Admin" => "ایڈمن", +"web services under your control" => "آپ کے اختیار میں ویب سروسیز", +"_%n minute ago_::_%n minutes ago_" => array("",""), +"_%n hour ago_::_%n hours ago_" => array("",""), +"_%n day go_::_%n days ago_" => array("",""), +"_%n month ago_::_%n months ago_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/l10n/vi.php b/lib/private/l10n/vi.php new file mode 100644 index 00000000000..dc0045c35ca --- /dev/null +++ b/lib/private/l10n/vi.php @@ -0,0 +1,31 @@ +<?php +$TRANSLATIONS = array( +"Help" => "Giúp đỡ", +"Personal" => "Cá nhân", +"Settings" => "Cài đặt", +"Users" => "Người dùng", +"Admin" => "Quản trị", +"web services under your control" => "dịch vụ web dưới sự kiểm soát của bạn", +"ZIP download is turned off." => "Tải về ZIP đã bị tắt.", +"Files need to be downloaded one by one." => "Tập tin cần phải được tải về từng người một.", +"Back to Files" => "Trở lại tập tin", +"Selected files too large to generate zip file." => "Tập tin được chọn quá lớn để tạo tập tin ZIP.", +"Application is not enabled" => "Ứng dụng không được BẬT", +"Authentication error" => "Lỗi xác thực", +"Token expired. Please reload page." => "Mã Token đã hết hạn. Hãy tải lại trang.", +"Files" => "Tập tin", +"Text" => "Văn bản", +"Images" => "Hình ảnh", +"seconds ago" => "vài giây trước", +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"today" => "hôm nay", +"yesterday" => "hôm qua", +"_%n day go_::_%n days ago_" => array(""), +"last month" => "tháng trước", +"_%n month ago_::_%n months ago_" => array(""), +"last year" => "năm trước", +"years ago" => "năm trước", +"Could not find category \"%s\"" => "không thể tìm thấy mục \"%s\"" +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/zh_CN.php b/lib/private/l10n/zh_CN.php new file mode 100644 index 00000000000..2c34356ea10 --- /dev/null +++ b/lib/private/l10n/zh_CN.php @@ -0,0 +1,52 @@ +<?php +$TRANSLATIONS = array( +"Help" => "帮助", +"Personal" => "个人", +"Settings" => "设置", +"Users" => "用户", +"Admin" => "管理", +"web services under your control" => "您控制的web服务", +"ZIP download is turned off." => "ZIP 下载已经关闭", +"Files need to be downloaded one by one." => "需要逐一下载文件", +"Back to Files" => "回到文件", +"Selected files too large to generate zip file." => "选择的文件太大,无法生成 zip 文件。", +"App does not provide an info.xml file" => "应用未提供 info.xml 文件", +"Application is not enabled" => "应用程序未启用", +"Authentication error" => "认证出错", +"Token expired. Please reload page." => "Token 过期,请刷新页面。", +"Files" => "文件", +"Text" => "文本", +"Images" => "图片", +"%s enter the database username." => "%s 输入数据库用户名。", +"%s enter the database name." => "%s 输入数据库名称。", +"%s you may not use dots in the database name" => "%s 您不能在数据库名称中使用英文句号。", +"MS SQL username and/or password not valid: %s" => "MS SQL 用户名和/或密码无效:%s", +"You need to enter either an existing account or the administrator." => "你需要输入一个数据库中已有的账户或管理员账户。", +"MySQL username and/or password not valid" => "MySQL 数据库用户名和/或密码无效", +"DB Error: \"%s\"" => "数据库错误:\"%s\"", +"Offending command was: \"%s\"" => "冲突命令为:\"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL 用户 '%s'@'localhost' 已存在。", +"Drop this user from MySQL" => "建议从 MySQL 数据库中丢弃 Drop 此用户", +"MySQL user '%s'@'%%' already exists" => "MySQL 用户 '%s'@'%%' 已存在", +"Drop this user from MySQL." => "建议从 MySQL 数据库中丢弃 Drop 此用户。", +"Oracle connection could not be established" => "不能建立甲骨文连接", +"Oracle username and/or password not valid" => "Oracle 数据库用户名和/或密码无效", +"Offending command was: \"%s\", name: %s, password: %s" => "冲突命令为:\"%s\",名称:%s,密码:%s", +"PostgreSQL username and/or password not valid" => "PostgreSQL 数据库用户名和/或密码无效", +"Set an admin username." => "请设置一个管理员用户名。", +"Set an admin password." => "请设置一个管理员密码。", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "您的Web服务器尚未正确设置以允许文件同步, 因为WebDAV的接口似乎已损坏.", +"Please double check the <a href='%s'>installation guides</a>." => "请认真检查<a href='%s'>安装指南</a>.", +"seconds ago" => "秒前", +"_%n minute ago_::_%n minutes ago_" => array("%n 分钟前"), +"_%n hour ago_::_%n hours ago_" => array("%n 小时前"), +"today" => "今天", +"yesterday" => "昨天", +"_%n day go_::_%n days ago_" => array("%n 天前"), +"last month" => "上月", +"_%n month ago_::_%n months ago_" => array("%n 月前"), +"last year" => "去年", +"years ago" => "年前", +"Could not find category \"%s\"" => "无法找到分类 \"%s\"" +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/zh_HK.php b/lib/private/l10n/zh_HK.php new file mode 100644 index 00000000000..ca3e6d504e7 --- /dev/null +++ b/lib/private/l10n/zh_HK.php @@ -0,0 +1,18 @@ +<?php +$TRANSLATIONS = array( +"Help" => "幫助", +"Personal" => "個人", +"Settings" => "設定", +"Users" => "用戶", +"Admin" => "管理", +"Files" => "文件", +"Text" => "文字", +"_%n minute ago_::_%n minutes ago_" => array(""), +"_%n hour ago_::_%n hours ago_" => array(""), +"today" => "今日", +"yesterday" => "昨日", +"_%n day go_::_%n days ago_" => array(""), +"last month" => "前一月", +"_%n month ago_::_%n months ago_" => array("") +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/l10n/zh_TW.php b/lib/private/l10n/zh_TW.php new file mode 100644 index 00000000000..210c766aa59 --- /dev/null +++ b/lib/private/l10n/zh_TW.php @@ -0,0 +1,69 @@ +<?php +$TRANSLATIONS = array( +"App \"%s\" can't be installed because it is not compatible with this version of ownCloud." => "無法安裝應用程式 %s 因為它和此版本的 ownCloud 不相容。", +"No app name specified" => "沒有指定應用程式名稱", +"Help" => "說明", +"Personal" => "個人", +"Settings" => "設定", +"Users" => "使用者", +"Admin" => "管理", +"Failed to upgrade \"%s\"." => "升級失敗:%s", +"web services under your control" => "由您控制的網路服務", +"cannot open \"%s\"" => "無法開啓 %s", +"ZIP download is turned off." => "ZIP 下載已關閉。", +"Files need to be downloaded one by one." => "檔案需要逐一下載。", +"Back to Files" => "回到檔案列表", +"Selected files too large to generate zip file." => "選擇的檔案太大以致於無法產生壓縮檔。", +"Download the files in smaller chunks, seperately or kindly ask your administrator." => "以小分割下載您的檔案,請詢問您的系統管理員。", +"No source specified when installing app" => "沒有指定應用程式安裝來源", +"No href specified when installing app from http" => "從 http 安裝應用程式,找不到 href 屬性", +"No path specified when installing app from local file" => "從本地檔案安裝應用程式時沒有指定路徑", +"Archives of type %s are not supported" => "不支援 %s 格式的壓縮檔", +"Failed to open archive when installing app" => "安裝應用程式時無法開啓壓縮檔", +"App does not provide an info.xml file" => "應用程式沒有提供 info.xml 檔案", +"App can't be installed because of not allowed code in the App" => "無法安裝應用程式因為在當中找到危險的代碼", +"App can't be installed because it is not compatible with this version of ownCloud" => "無法安裝應用程式因為它和此版本的 ownCloud 不相容。", +"App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" => "無法安裝應用程式,因為它包含了 <shipped>true</shipped> 標籤,在未發行的應用程式當中這是不允許的", +"App can't be installed because the version in info.xml/version is not the same as the version reported from the app store" => "無法安裝應用程式,因為它在 info.xml/version 宣告的版本與 app store 當中記載的版本不同", +"App directory already exists" => "應用程式目錄已經存在", +"Can't create app folder. Please fix permissions. %s" => "無法建立應用程式目錄,請檢查權限:%s", +"Application is not enabled" => "應用程式未啟用", +"Authentication error" => "認證錯誤", +"Token expired. Please reload page." => "Token 過期,請重新整理頁面。", +"Files" => "檔案", +"Text" => "文字", +"Images" => "圖片", +"%s enter the database username." => "%s 輸入資料庫使用者名稱。", +"%s enter the database name." => "%s 輸入資料庫名稱。", +"%s you may not use dots in the database name" => "%s 資料庫名稱不能包含小數點", +"MS SQL username and/or password not valid: %s" => "MS SQL 使用者和/或密碼無效:%s", +"You need to enter either an existing account or the administrator." => "您必須輸入一個現有的帳號或管理員帳號。", +"MySQL username and/or password not valid" => "MySQL 用戶名和/或密碼無效", +"DB Error: \"%s\"" => "資料庫錯誤:\"%s\"", +"Offending command was: \"%s\"" => "有問題的指令是:\"%s\"", +"MySQL user '%s'@'localhost' exists already." => "MySQL 使用者 '%s'@'localhost' 已經存在。", +"Drop this user from MySQL" => "在 MySQL 移除這個使用者", +"MySQL user '%s'@'%%' already exists" => "MySQL 使用者 '%s'@'%%' 已經存在", +"Drop this user from MySQL." => "在 MySQL 移除這個使用者。", +"Oracle connection could not be established" => "無法建立 Oracle 資料庫連線", +"Oracle username and/or password not valid" => "Oracle 用戶名和/或密碼無效", +"Offending command was: \"%s\", name: %s, password: %s" => "有問題的指令是:\"%s\" ,使用者:\"%s\",密碼:\"%s\"", +"PostgreSQL username and/or password not valid" => "PostgreSQL 用戶名和/或密碼無效", +"Set an admin username." => "設定管理員帳號。", +"Set an admin password." => "設定管理員密碼。", +"Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "您的網頁伺服器尚未被正確設定來進行檔案同步,因為您的 WebDAV 界面似乎無法使用。", +"Please double check the <a href='%s'>installation guides</a>." => "請參考<a href='%s'>安裝指南</a>。", +"seconds ago" => "幾秒前", +"_%n minute ago_::_%n minutes ago_" => array("%n 分鐘前"), +"_%n hour ago_::_%n hours ago_" => array("%n 小時前"), +"today" => "今天", +"yesterday" => "昨天", +"_%n day go_::_%n days ago_" => array("%n 天前"), +"last month" => "上個月", +"_%n month ago_::_%n months ago_" => array("%n 個月前"), +"last year" => "去年", +"years ago" => "幾年前", +"Caused by:" => "原因:", +"Could not find category \"%s\"" => "找不到分類:\"%s\"" +); +$PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/lib/private/legacy/cache.php b/lib/private/legacy/cache.php new file mode 100644 index 00000000000..f915eb516b1 --- /dev/null +++ b/lib/private/legacy/cache.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright (c) 2013 Thomas Tanghus (thomas@tanghus.net) + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Cache extends \OC\Cache { +}
\ No newline at end of file diff --git a/lib/private/legacy/config.php b/lib/private/legacy/config.php new file mode 100644 index 00000000000..7e498013737 --- /dev/null +++ b/lib/private/legacy/config.php @@ -0,0 +1,107 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @author Jakob Sack + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +/* + * + * An example of config.php + * + * <?php + * $CONFIG = array( + * "database" => "mysql", + * "firstrun" => false, + * "pi" => 3.14 + * ); + * ?> + * + */ + +/** + * This class is responsible for reading and writing config.php, the very basic + * configuration file of ownCloud. + */ +OC_Config::$object = new \OC\Config(OC::$SERVERROOT.'/config/'); +class OC_Config { + + /** + * @var \OC\Config + */ + public static $object; + + public static function getObject() { + return self::$object; + } + + /** + * @brief Lists all available config keys + * @return array with key names + * + * This function returns all keys saved in config.php. Please note that it + * does not return the values. + */ + public static function getKeys() { + return self::$object->getKeys(); + } + + /** + * @brief Gets a value from config.php + * @param string $key key + * @param string $default = null default value + * @return string the value or $default + * + * This function gets the value from config.php. If it does not exist, + * $default will be returned. + */ + public static function getValue($key, $default = null) { + return self::$object->getValue($key, $default); + } + + /** + * @brief Sets a value + * @param string $key key + * @param string $value value + * + * This function sets the value and writes the config.php. + * + */ + public static function setValue($key, $value) { + try { + self::$object->setValue($key, $value); + } catch (\OC\HintException $e) { + \OC_Template::printErrorPage($e->getMessage(), $e->getHint()); + } + } + + /** + * @brief Removes a key from the config + * @param string $key key + * + * This function removes a key from the config.php. + * + */ + public static function deleteKey($key) { + try { + self::$object->deleteKey($key); + } catch (\OC\HintException $e) { + \OC_Template::printErrorPage($e->getMessage(), $e->getHint()); + } + } +} diff --git a/lib/private/legacy/filesystem.php b/lib/private/legacy/filesystem.php new file mode 100644 index 00000000000..34f92b357ca --- /dev/null +++ b/lib/private/legacy/filesystem.php @@ -0,0 +1,415 @@ +<?php + +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * Class for abstraction of filesystem functions + * This class won't call any filesystem functions for itself but but will pass them to the correct OC_Filestorage object + * this class should also handle all the file permission related stuff + * + * Hooks provided: + * read(path) + * write(path, &run) + * post_write(path) + * create(path, &run) (when a file is created, both create and write will be emitted in that order) + * post_create(path) + * delete(path, &run) + * post_delete(path) + * rename(oldpath,newpath, &run) + * post_rename(oldpath,newpath) + * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emitted in that order) + * post_rename(oldpath,newpath) + * + * the &run parameter can be set to false to prevent the operation from occurring + */ + +/** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ +class OC_Filesystem { + /** + * get the mountpoint of the storage object for a path + * ( note: because a storage is not always mounted inside the fakeroot, the + * returned mountpoint is relative to the absolute root of the filesystem + * and doesn't take the chroot into account ) + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return string + */ + static public function getMountPoint($path) { + return \OC\Files\Filesystem::getMountPoint($path); + } + + /** + * resolve a path to a storage and internal path + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return array consisting of the storage and the internal path + */ + static public function resolvePath($path) { + return \OC\Files\Filesystem::resolvePath($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function init($user, $root) { + return \OC\Files\Filesystem::init($user, $root); + } + + /** + * get the default filesystem view + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @return \OC\Files\View + */ + static public function getView() { + return \OC\Files\Filesystem::getView(); + } + + /** + * tear down the filesystem, removing all storage providers + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function tearDown() { + \OC\Files\Filesystem::tearDown(); + } + + /** + * @brief get the relative path of the root data directory for the current user + * @return string + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * Returns path like /admin/files + */ + static public function getRoot() { + return \OC\Files\Filesystem::getRoot(); + } + + /** + * clear all mounts and storage backends + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + public static function clearMounts() { + \OC\Files\Filesystem::clearMounts(); + } + + /** + * mount an \OC\Files\Storage\Storage in our virtual filesystem + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param \OC\Files\Storage\Storage $class + * @param array $arguments + * @param string $mountpoint + */ + static public function mount($class, $arguments, $mountpoint) { + \OC\Files\Filesystem::mount($class, $arguments, $mountpoint); + } + + /** + * return the path to a local version of the file + * we need this because we can't know if a file is stored local or not from + * outside the filestorage and for some purposes a local file is needed + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return string + */ + static public function getLocalFile($path) { + return \OC\Files\Filesystem::getLocalFile($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return string + */ + static public function getLocalFolder($path) { + return \OC\Files\Filesystem::getLocalFolder($path); + } + + /** + * return path to file which reflects one visible in browser + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return string + */ + static public function getLocalPath($path) { + return \OC\Files\Filesystem::getLocalPath($path); + } + + /** + * check if the requested path is valid + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return bool + */ + static public function isValidPath($path) { + return \OC\Files\Filesystem::isValidPath($path); + } + + /** + * checks if a file is blacklisted for storage in the filesystem + * Listens to write and rename hooks + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param array $data from hook + */ + static public function isBlacklisted($data) { + \OC\Files\Filesystem::isBlacklisted($data); + } + + /** + * following functions are equivalent to their php builtin equivalents for arguments/return values. + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function mkdir($path) { + return \OC\Files\Filesystem::mkdir($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function rmdir($path) { + return \OC\Files\Filesystem::rmdir($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function opendir($path) { + return \OC\Files\Filesystem::opendir($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function readdir($path) { + return \OC\Files\Filesystem::readdir($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function is_dir($path) { + return \OC\Files\Filesystem::is_dir($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function is_file($path) { + return \OC\Files\Filesystem::is_file($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function stat($path) { + return \OC\Files\Filesystem::stat($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function filetype($path) { + return \OC\Files\Filesystem::filetype($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function filesize($path) { + return \OC\Files\Filesystem::filesize($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function readfile($path) { + return \OC\Files\Filesystem::readfile($path); + } + + /** + * @deprecated Replaced by isReadable() as part of CRUDS + */ + static public function is_readable($path) { + return \OC\Files\Filesystem::isReadable($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function isCreatable($path) { + return \OC\Files\Filesystem::isCreatable($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function isReadable($path) { + return \OC\Files\Filesystem::isReadable($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function isUpdatable($path) { + return \OC\Files\Filesystem::isUpdatable($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function isDeletable($path) { + return \OC\Files\Filesystem::isDeletable($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function isSharable($path) { + return \OC\Files\Filesystem::isSharable($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function file_exists($path) { + return \OC\Files\Filesystem::file_exists($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function filemtime($path) { + return \OC\Files\Filesystem::filemtime($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function touch($path, $mtime = null) { + return \OC\Files\Filesystem::touch($path, $mtime); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function file_get_contents($path) { + return \OC\Files\Filesystem::file_get_contents($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function file_put_contents($path, $data) { + return \OC\Files\Filesystem::file_put_contents($path, $data); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function unlink($path) { + return \OC\Files\Filesystem::unlink($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function rename($path1, $path2) { + return \OC\Files\Filesystem::rename($path1, $path2); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function copy($path1, $path2) { + return \OC\Files\Filesystem::copy($path1, $path2); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function fopen($path, $mode) { + return \OC\Files\Filesystem::fopen($path, $mode); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function toTmpFile($path) { + return \OC\Files\Filesystem::toTmpFile($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function fromTmpFile($tmpFile, $path) { + return \OC\Files\Filesystem::fromTmpFile($tmpFile, $path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function getMimeType($path) { + return \OC\Files\Filesystem::getMimeType($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function hash($type, $path, $raw = false) { + return \OC\Files\Filesystem::hash($type, $path, $raw); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function free_space($path = '/') { + return \OC\Files\Filesystem::free_space($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function search($query) { + return \OC\Files\Filesystem::search($query); + } + + /** + * check if a file or folder has been updated since $time + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @param int $time + * @return bool + */ + static public function hasUpdated($path, $time) { + return \OC\Files\Filesystem::hasUpdated($path, $time); + } + + /** + * normalize a path + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @param bool $stripTrailingSlash + * @return string + */ + public static function normalizePath($path, $stripTrailingSlash = true) { + return \OC\Files\Filesystem::normalizePath($path, $stripTrailingSlash); + } +} diff --git a/lib/private/legacy/filesystemview.php b/lib/private/legacy/filesystemview.php new file mode 100644 index 00000000000..d6bca62e06a --- /dev/null +++ b/lib/private/legacy/filesystemview.php @@ -0,0 +1,9 @@ +<?php + +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. */ + +class OC_FilesystemView extends \OC\Files\View {} diff --git a/lib/private/legacy/log.php b/lib/private/legacy/log.php new file mode 100644 index 00000000000..027cb89e97c --- /dev/null +++ b/lib/private/legacy/log.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * logging utilities + * + * Log is saved by default at data/owncloud.log using OC_Log_Owncloud. + * Selecting other backend is done with a config option 'log_type'. + */ + +OC_Log::$object = new \OC\Log(); +class OC_Log { + public static $object; + + const DEBUG=0; + const INFO=1; + const WARN=2; + const ERROR=3; + const FATAL=4; + + static private $level_funcs = array( + self::DEBUG => 'debug', + self::INFO => 'info', + self::WARN => 'warning', + self::ERROR => 'error', + self::FATAL => 'emergency', + ); + + static public $enabled = true; + static protected $class = null; + + /** + * write a message in the log + * @param string $app + * @param string $message + * @param int $level + */ + public static function write($app, $message, $level) { + if (self::$enabled) { + $context = array('app' => $app); + $func = array(self::$object, self::$level_funcs[$level]); + call_user_func($func, $message, $context); + } + } +} diff --git a/lib/private/legacy/preferences.php b/lib/private/legacy/preferences.php new file mode 100644 index 00000000000..a663db7598b --- /dev/null +++ b/lib/private/legacy/preferences.php @@ -0,0 +1,146 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @author Jakob Sack + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * This class provides an easy way for storing user preferences. + */ +OC_Preferences::$object = new \OC\Preferences(OC_DB::getConnection()); +class OC_Preferences{ + public static $object; + /** + * @brief Get all users using the preferences + * @return array with user ids + * + * This function returns a list of all users that have at least one entry + * in the preferences table. + */ + public static function getUsers() { + return self::$object->getUsers(); + } + + /** + * @brief Get all apps of a user + * @param string $user user + * @return array with app ids + * + * This function returns a list of all apps of the user that have at least + * one entry in the preferences table. + */ + public static function getApps( $user ) { + return self::$object->getApps( $user ); + } + + /** + * @brief Get the available keys for an app + * @param string $user user + * @param string $app the app we are looking for + * @return array with key names + * + * This function gets all keys of an app of an user. Please note that the + * values are not returned. + */ + public static function getKeys( $user, $app ) { + return self::$object->getKeys( $user, $app ); + } + + /** + * @brief Gets the preference + * @param string $user user + * @param string $app app + * @param string $key key + * @param string $default = null, default value if the key does not exist + * @return string the value or $default + * + * This function gets a value from the preferences table. If the key does + * not exist the default value will be returned + */ + public static function getValue( $user, $app, $key, $default = null ) { + return self::$object->getValue( $user, $app, $key, $default ); + } + + /** + * @brief sets a value in the preferences + * @param string $user user + * @param string $app app + * @param string $key key + * @param string $value value + * @return bool + * + * Adds a value to the preferences. If the key did not exist before, it + * will be added automagically. + */ + public static function setValue( $user, $app, $key, $value ) { + self::$object->setValue( $user, $app, $key, $value ); + return true; + } + + /** + * @brief Deletes a key + * @param string $user user + * @param string $app app + * @param string $key key + * + * Deletes a key. + */ + public static function deleteKey( $user, $app, $key ) { + self::$object->deleteKey( $user, $app, $key ); + return true; + } + + /** + * @brief Remove app of user from preferences + * @param string $user user + * @param string $app app + * @return bool + * + * Removes all keys in preferences belonging to the app and the user. + */ + public static function deleteApp( $user, $app ) { + self::$object->deleteApp( $user, $app ); + return true; + } + + /** + * @brief Remove user from preferences + * @param string $user user + * @return bool + * + * Removes all keys in preferences belonging to the user. + */ + public static function deleteUser( $user ) { + self::$object->deleteUser( $user ); + return true; + } + + /** + * @brief Remove app from all users + * @param string $app app + * @return bool + * + * Removes all keys in preferences belonging to the app. + */ + public static function deleteAppFromAllUsers( $app ) { + self::$object->deleteAppFromAllUsers( $app ); + return true; + } +} diff --git a/lib/private/legacy/updater.php b/lib/private/legacy/updater.php new file mode 100644 index 00000000000..eea7bb129cf --- /dev/null +++ b/lib/private/legacy/updater.php @@ -0,0 +1,14 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Updater { + public static function check() { + $updater = new \OC\Updater(); + return $updater->check('http://apps.owncloud.com/updater.php'); + } +} diff --git a/lib/private/log.php b/lib/private/log.php new file mode 100644 index 00000000000..e0b9fe3c696 --- /dev/null +++ b/lib/private/log.php @@ -0,0 +1,136 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC; + +/** + * logging utilities + * + * This is a stand in, this should be replaced by a Psr\Log\LoggerInterface + * compatible logger. See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md + * for the full interface specification. + * + * MonoLog is an example implementing this interface. + */ + +class Log { + private $logClass; + + /** + * System is unusable. + * + * @param string $message + * @param array $context + */ + public function emergency($message, array $context = array()) { + $this->log(\OC_Log::FATAL, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + */ + public function alert($message, array $context = array()) { + $this->log(\OC_Log::ERROR, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + */ + public function critical($message, array $context = array()) { + $this->log(\OC_Log::ERROR, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + */ + public function error($message, array $context = array()) { + $this->log(\OC_Log::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + */ + public function warning($message, array $context = array()) { + $this->log(\OC_Log::WARN, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + */ + public function notice($message, array $context = array()) { + $this->log(\OC_Log::INFO, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + */ + public function info($message, array $context = array()) { + $this->log(\OC_Log::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + */ + public function debug($message, array $context = array()) { + $this->log(\OC_Log::DEBUG, $message, $context); + } + + public function __construct() { + $this->logClass = 'OC_Log_'.ucfirst(\OC_Config::getValue('log_type', 'owncloud')); + call_user_func(array($this->logClass, 'init')); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + */ + public function log($level, $message, array $context = array()) { + if (isset($context['app'])) { + $app = $context['app']; + } else { + $app = 'no app in context'; + } + $logClass=$this->logClass; + $logClass::write($app, $message, $level); + } +} diff --git a/lib/private/log/errorhandler.php b/lib/private/log/errorhandler.php new file mode 100644 index 00000000000..69cb960de91 --- /dev/null +++ b/lib/private/log/errorhandler.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Log; + +use OC\Log as LoggerInterface; + +class ErrorHandler { + /** @var LoggerInterface */ + private static $logger; + + public static function register() { + $handler = new ErrorHandler(); + + set_error_handler(array($handler, 'onError')); + register_shutdown_function(array($handler, 'onShutdown')); + set_exception_handler(array($handler, 'onException')); + } + + public static function setLogger(LoggerInterface $logger) { + self::$logger = $logger; + } + + //Fatal errors handler + public static function onShutdown() { + $error = error_get_last(); + if($error && self::$logger) { + //ob_end_clean(); + $msg = $error['message'] . ' at ' . $error['file'] . '#' . $error['line']; + self::$logger->critical($msg, array('app' => 'PHP')); + } + } + + // Uncaught exception handler + public static function onException($exception) { + $msg = $exception->getMessage() . ' at ' . $exception->getFile() . '#' . $exception->getLine(); + self::$logger->critical($msg, array('app' => 'PHP')); + } + + //Recoverable errors handler + public static function onError($number, $message, $file, $line) { + if (error_reporting() === 0) { + return; + } + $msg = $message . ' at ' . $file . '#' . $line; + self::$logger->warning($msg, array('app' => 'PHP')); + + } +} diff --git a/lib/private/log/owncloud.php b/lib/private/log/owncloud.php new file mode 100644 index 00000000000..d16b9537a16 --- /dev/null +++ b/lib/private/log/owncloud.php @@ -0,0 +1,112 @@ +<?php +/** + * ownCloud + * + * @author Robin Appelman + * @copyright 2012 Robin Appelman icewind1991@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * logging utilities + * + * Log is saved at data/owncloud.log (on default) + */ + +class OC_Log_Owncloud { + static protected $logFile; + + /** + * Init class data + */ + public static function init() { + $defaultLogFile = OC_Config::getValue("datadirectory", OC::$SERVERROOT.'/data').'/owncloud.log'; + self::$logFile = OC_Config::getValue("logfile", $defaultLogFile); + if (!file_exists(self::$logFile)) { + self::$logFile = $defaultLogFile; + } + } + + /** + * write a message in the log + * @param string $app + * @param string $message + * @param int $level + */ + public static function write($app, $message, $level) { + $minLevel=min(OC_Config::getValue( "loglevel", OC_Log::WARN ), OC_Log::ERROR); + if($level>=$minLevel) { + // default to ISO8601 + $format = OC_Config::getValue('logdateformat', 'c'); + $time = date($format, time()); + $entry=array('app'=>$app, 'message'=>$message, 'level'=>$level, 'time'=> $time); + $handle = @fopen(self::$logFile, 'a'); + if ($handle) { + fwrite($handle, json_encode($entry)."\n"); + fclose($handle); + } + } + } + + /** + * get entries from the log in reverse chronological order + * @param int $limit + * @param int $offset + * @return array + */ + public static function getEntries($limit=50, $offset=0) { + self::init(); + $minLevel=OC_Config::getValue( "loglevel", OC_Log::WARN ); + $entries = array(); + $handle = @fopen(self::$logFile, 'rb'); + if ($handle) { + fseek($handle, 0, SEEK_END); + $pos = ftell($handle); + $line = ''; + $entriesCount = 0; + $lines = 0; + // Loop through each character of the file looking for new lines + while ($pos >= 0 && $entriesCount < $limit) { + fseek($handle, $pos); + $ch = fgetc($handle); + if ($ch == "\n" || $pos == 0) { + if ($line != '') { + // Add the first character if at the start of the file, + // because it doesn't hit the else in the loop + if ($pos == 0) { + $line = $ch.$line; + } + $entry = json_decode($line); + // Add the line as an entry if it is passed the offset and is equal or above the log level + if ($entry->level >= $minLevel) { + $lines++; + if ($lines > $offset) { + $entries[] = $entry; + $entriesCount++; + } + } + $line = ''; + } + } else { + $line = $ch.$line; + } + $pos--; + } + fclose($handle); + } + return $entries; + } +} diff --git a/lib/private/log/rotate.php b/lib/private/log/rotate.php new file mode 100644 index 00000000000..bf23ad588b3 --- /dev/null +++ b/lib/private/log/rotate.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Log; + +/** + * This rotates the current logfile to a new name, this way the total log usage + * will stay limited and older entries are available for a while longer. + * For more professional log management set the 'logfile' config to a different + * location and manage that with your own tools. + */ +class Rotate extends \OC\BackgroundJob\Job { + private $max_log_size; + public function run($logFile) { + $this->max_log_size = \OC_Config::getValue('log_rotate_size', false); + if ($this->max_log_size) { + $filesize = @filesize($logFile); + if ($filesize >= $this->max_log_size) { + $this->rotate($logFile); + } + } + } + + protected function rotate($logfile) { + $rotatedLogfile = $logfile.'.1'; + rename($logfile, $rotatedLogfile); + $msg = 'Log file "'.$logfile.'" was over '.$this->max_log_size.' bytes, moved to "'.$rotatedLogfile.'"'; + \OC_Log::write('OC\Log\Rotate', $msg, \OC_Log::WARN); + } +} diff --git a/lib/private/log/syslog.php b/lib/private/log/syslog.php new file mode 100644 index 00000000000..c98deab7109 --- /dev/null +++ b/lib/private/log/syslog.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Log_Syslog { + static protected $levels = array( + OC_Log::DEBUG => LOG_DEBUG, + OC_Log::INFO => LOG_INFO, + OC_Log::WARN => LOG_WARNING, + OC_Log::ERROR => LOG_ERR, + OC_Log::FATAL => LOG_CRIT, + ); + + /** + * Init class data + */ + public static function init() { + openlog('ownCloud', LOG_PID | LOG_CONS, LOG_USER); + // Close at shutdown + register_shutdown_function('closelog'); + } + + /** + * write a message in the log + * @param string $app + * @param string $message + * @param int $level + */ + public static function write($app, $message, $level) { + $minLevel = min(OC_Config::getValue("loglevel", OC_Log::WARN), OC_Log::ERROR); + if ($level >= $minLevel) { + $syslog_level = self::$levels[$level]; + syslog($syslog_level, '{'.$app.'} '.$message); + } + } +} diff --git a/lib/private/mail.php b/lib/private/mail.php new file mode 100644 index 00000000000..b339b33e962 --- /dev/null +++ b/lib/private/mail.php @@ -0,0 +1,133 @@ +<?php +/** + * Copyright (c) 2012 Frank Karlitschek <frank@owncloud.org> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * OC_Mail + * + * A class to handle mail sending. + */ + +require_once 'class.phpmailer.php'; + +class OC_Mail { + + /** + * send an email + * + * @param string $toaddress + * @param string $toname + * @param string $subject + * @param string $mailtext + * @param string $fromaddress + * @param string $fromname + * @param bool|int $html + * @param string $altbody + * @param string $ccaddress + * @param string $ccname + * @param string $bcc + * @throws Exception + */ + public static function send($toaddress, $toname, $subject, $mailtext, $fromaddress, $fromname, + $html=0, $altbody='', $ccaddress='', $ccname='', $bcc='') { + + $SMTPMODE = OC_Config::getValue( 'mail_smtpmode', 'sendmail' ); + $SMTPHOST = OC_Config::getValue( 'mail_smtphost', '127.0.0.1' ); + $SMTPPORT = OC_Config::getValue( 'mail_smtpport', 25 ); + $SMTPAUTH = OC_Config::getValue( 'mail_smtpauth', false ); + $SMTPAUTHTYPE = OC_Config::getValue( 'mail_smtpauthtype', 'LOGIN' ); + $SMTPUSERNAME = OC_Config::getValue( 'mail_smtpname', '' ); + $SMTPPASSWORD = OC_Config::getValue( 'mail_smtppassword', '' ); + $SMTPDEBUG = OC_Config::getValue( 'mail_smtpdebug', false ); + $SMTPTIMEOUT = OC_Config::getValue( 'mail_smtptimeout', 10 ); + $SMTPSECURE = OC_Config::getValue( 'mail_smtpsecure', '' ); + + + $mailo = new PHPMailer(true); + if($SMTPMODE=='sendmail') { + $mailo->IsSendmail(); + }elseif($SMTPMODE=='smtp') { + $mailo->IsSMTP(); + }elseif($SMTPMODE=='qmail') { + $mailo->IsQmail(); + }else{ + $mailo->IsMail(); + } + + + $mailo->Host = $SMTPHOST; + $mailo->Port = $SMTPPORT; + $mailo->SMTPAuth = $SMTPAUTH; + $mailo->SMTPDebug = $SMTPDEBUG; + $mailo->SMTPSecure = $SMTPSECURE; + $mailo->AuthType = $SMTPAUTHTYPE; + $mailo->Username = $SMTPUSERNAME; + $mailo->Password = $SMTPPASSWORD; + $mailo->Timeout = $SMTPTIMEOUT; + + $mailo->From = $fromaddress; + $mailo->FromName = $fromname;; + $mailo->Sender = $fromaddress; + $a=explode(' ', $toaddress); + try { + foreach($a as $ad) { + $mailo->AddAddress($ad, $toname); + } + + if($ccaddress<>'') $mailo->AddCC($ccaddress, $ccname); + if($bcc<>'') $mailo->AddBCC($bcc); + + $mailo->AddReplyTo($fromaddress, $fromname); + + $mailo->WordWrap = 50; + if($html==1) $mailo->IsHTML(true); else $mailo->IsHTML(false); + + $mailo->Subject = $subject; + if($altbody=='') { + $mailo->Body = $mailtext.OC_MAIL::getfooter(); + $mailo->AltBody = ''; + }else{ + $mailo->Body = $mailtext; + $mailo->AltBody = $altbody; + } + $mailo->CharSet = 'UTF-8'; + + $mailo->Send(); + unset($mailo); + OC_Log::write('mail', + 'Mail from '.$fromname.' ('.$fromaddress.')'.' to: '.$toname.'('.$toaddress.')'.' subject: '.$subject, + OC_Log::DEBUG); + } catch (Exception $exception) { + OC_Log::write('mail', $exception->getMessage(), OC_Log::ERROR); + throw($exception); + } + } + + /** + * return the footer for a mail + * + */ + public static function getfooter() { + + $defaults = new OC_Defaults(); + + $txt="\n--\n"; + $txt.=$defaults->getName() . "\n"; + $txt.=$defaults->getSlogan() . "\n"; + + return($txt); + + } + + /** + * @param string $emailAddress a given email address to be validated + * @return bool + */ + public static function ValidateAddress($emailAddress) { + return PHPMailer::ValidateAddress($emailAddress); + } +} diff --git a/lib/private/memcache/apc.php b/lib/private/memcache/apc.php new file mode 100644 index 00000000000..575ee4427db --- /dev/null +++ b/lib/private/memcache/apc.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Memcache; + +class APC extends Cache { + /** + * entries in APC gets namespaced to prevent collisions between owncloud instances and users + */ + protected function getNameSpace() { + return $this->prefix; + } + + public function get($key) { + $result = apc_fetch($this->getNamespace() . $key, $success); + if (!$success) { + return null; + } + return $result; + } + + public function set($key, $value, $ttl = 0) { + return apc_store($this->getNamespace() . $key, $value, $ttl); + } + + public function hasKey($key) { + return apc_exists($this->getNamespace() . $key); + } + + public function remove($key) { + return apc_delete($this->getNamespace() . $key); + } + + public function clear($prefix = '') { + $ns = $this->getNamespace() . $prefix; + $cache = apc_cache_info('user'); + foreach ($cache['cache_list'] as $entry) { + if (strpos($entry['info'], $ns) === 0) { + apc_delete($entry['info']); + } + } + return true; + } + + static public function isAvailable() { + if (!extension_loaded('apc')) { + return false; + } elseif (!ini_get('apc.enable_cli') && \OC::$CLI) { + return false; + } else { + return true; + } + } +} + +if (!function_exists('apc_exists')) { + function apc_exists($keys) { + $result = false; + apc_fetch($keys, $result); + return $result; + } +} diff --git a/lib/private/memcache/apcu.php b/lib/private/memcache/apcu.php new file mode 100644 index 00000000000..ccc1aa6e562 --- /dev/null +++ b/lib/private/memcache/apcu.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Memcache; + +class APCu extends APC { + public function clear($prefix = '') { + $ns = $this->getNamespace() . $prefix; + $ns = preg_quote($ns, '/'); + $iter = new \APCIterator('/^'.$ns.'/'); + return apc_delete($iter); + } + + static public function isAvailable() { + if (!extension_loaded('apcu')) { + return false; + } elseif (!ini_get('apc.enable_cli') && \OC::$CLI) { + return false; + } else { + return true; + } + } +} diff --git a/lib/private/memcache/cache.php b/lib/private/memcache/cache.php new file mode 100644 index 00000000000..0ad1cc7ec03 --- /dev/null +++ b/lib/private/memcache/cache.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Memcache; + +abstract class Cache implements \ArrayAccess { + /** + * @var string $prefix + */ + protected $prefix; + + /** + * @param string $prefix + */ + public function __construct($prefix = '') { + $this->prefix = \OC_Util::getInstanceId() . '/' . $prefix; + } + + public function getPrefix() { + return $this->prefix; + } + + /** + * @param string $key + * @return mixed + */ + abstract public function get($key); + + /** + * @param string $key + * @param mixed $value + * @param int $ttl + * @return mixed + */ + abstract public function set($key, $value, $ttl = 0); + + /** + * @param string $key + * @return mixed + */ + abstract public function hasKey($key); + + /** + * @param string $key + * @return mixed + */ + abstract public function remove($key); + + /** + * @param string $prefix + * @return mixed + */ + abstract public function clear($prefix = ''); + + //implement the ArrayAccess interface + + public function offsetExists($offset) { + return $this->hasKey($offset); + } + + public function offsetSet($offset, $value) { + $this->set($offset, $value); + } + + public function offsetGet($offset) { + return $this->get($offset); + } + + public function offsetUnset($offset) { + $this->remove($offset); + } +} diff --git a/lib/private/memcache/factory.php b/lib/private/memcache/factory.php new file mode 100644 index 00000000000..fde7d947567 --- /dev/null +++ b/lib/private/memcache/factory.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Memcache; + +class Factory { + /** + * get a cache instance, will return null if no backend is available + * + * @param string $prefix + * @return \OC\Memcache\Cache + */ + function create($prefix = '') { + if (XCache::isAvailable()) { + return new XCache($prefix); + } elseif (APCu::isAvailable()) { + return new APCu($prefix); + } elseif (APC::isAvailable()) { + return new APC($prefix); + } elseif (Memcached::isAvailable()) { + return new Memcached($prefix); + } else { + return null; + } + } + + /** + * check if there is a memcache backend available + * + * @return bool + */ + public function isAvailable() { + return XCache::isAvailable() || APCu::isAvailable() || APC::isAvailable() || Memcached::isAvailable(); + } + + /** + * get a in-server cache instance, will return null if no backend is available + * + * @param string $prefix + * @return \OC\Memcache\Cache + */ + public static function createLowLatency($prefix = '') { + if (XCache::isAvailable()) { + return new XCache($prefix); + } elseif (APCu::isAvailable()) { + return new APCu($prefix); + } elseif (APC::isAvailable()) { + return new APC($prefix); + } else { + return null; + } + } + + /** + * check if there is a in-server backend available + * + * @return bool + */ + public static function isAvailableLowLatency() { + return XCache::isAvailable() || APCu::isAvailable() || APC::isAvailable(); + } + + +} diff --git a/lib/private/memcache/memcached.php b/lib/private/memcache/memcached.php new file mode 100644 index 00000000000..978e6c2eff1 --- /dev/null +++ b/lib/private/memcache/memcached.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Memcache; + +class Memcached extends Cache { + /** + * @var \Memcached $cache + */ + private static $cache = null; + + public function __construct($prefix = '') { + parent::__construct($prefix); + if (is_null(self::$cache)) { + self::$cache = new \Memcached(); + list($host, $port) = \OC_Config::getValue('memcached_server', array('localhost', 11211)); + self::$cache->addServer($host, $port); + } + } + + /** + * entries in XCache gets namespaced to prevent collisions between owncloud instances and users + */ + protected function getNameSpace() { + return $this->prefix; + } + + public function get($key) { + $result = self::$cache->get($this->getNamespace() . $key); + if ($result === false and self::$cache->getResultCode() == \Memcached::RES_NOTFOUND) { + return null; + } else { + return $result; + } + } + + public function set($key, $value, $ttl = 0) { + if ($ttl > 0) { + return self::$cache->set($this->getNamespace() . $key, $value, $ttl); + } else { + return self::$cache->set($this->getNamespace() . $key, $value); + } + } + + public function hasKey($key) { + self::$cache->get($this->getNamespace() . $key); + return self::$cache->getResultCode() !== \Memcached::RES_NOTFOUND; + } + + public function remove($key) { + return self::$cache->delete($this->getNamespace() . $key); + } + + public function clear($prefix = '') { + $prefix = $this->getNamespace() . $prefix; + $allKeys = self::$cache->getAllKeys(); + $keys = array(); + $prefixLength = strlen($prefix); + foreach ($allKeys as $key) { + if (substr($key, 0, $prefixLength) === $prefix) { + $keys[] = $key; + } + } + self::$cache->deleteMulti($keys); + return true; + } + + static public function isAvailable() { + return extension_loaded('memcached'); + } +} diff --git a/lib/private/memcache/xcache.php b/lib/private/memcache/xcache.php new file mode 100644 index 00000000000..33de30562f9 --- /dev/null +++ b/lib/private/memcache/xcache.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Memcache; + +class XCache extends Cache { + /** + * entries in XCache gets namespaced to prevent collisions between owncloud instances and users + */ + protected function getNameSpace() { + return $this->prefix; + } + + public function get($key) { + return xcache_get($this->getNamespace().$key); + } + + public function set($key, $value, $ttl=0) { + if($ttl>0) { + return xcache_set($this->getNamespace().$key, $value, $ttl); + }else{ + return xcache_set($this->getNamespace().$key, $value); + } + } + + public function hasKey($key) { + return xcache_isset($this->getNamespace().$key); + } + + public function remove($key) { + return xcache_unset($this->getNamespace().$key); + } + + public function clear($prefix='') { + xcache_unset_by_prefix($this->getNamespace().$prefix); + return true; + } + + static public function isAvailable(){ + if (!extension_loaded('xcache')) { + return false; + } elseif (\OC::$CLI) { + return false; + }else{ + return true; + } + } +} + +if(!function_exists('xcache_unset_by_prefix')) { + function xcache_unset_by_prefix($prefix) { + // Since we can't clear targetted cache, we'll clear all. :( + xcache_clear_cache(\XC_TYPE_VAR, 0); + } +} diff --git a/lib/private/migrate.php b/lib/private/migrate.php new file mode 100644 index 00000000000..0b319177400 --- /dev/null +++ b/lib/private/migrate.php @@ -0,0 +1,693 @@ +<?php +/** + * ownCloud + * + * @author Tom Needham + * @copyright 2012 Tom Needham tom@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +/** + * provides an interface to migrate users and whole ownclouds + */ +class OC_Migrate{ + + + // Array of OC_Migration_Provider objects + static private $providers=array(); + // User id of the user to import/export + static private $uid=false; + // Holds the ZipArchive object + static private $zip=false; + // Stores the type of export + static private $exporttype=false; + // Array of temp files to be deleted after zip creation + static private $tmpfiles=array(); + // Holds the db object + static private $MDB2=false; + // Schema db object + static private $schema=false; + // Path to the sqlite db + static private $dbpath=false; + // Holds the path to the zip file + static private $zippath=false; + // Holds the OC_Migration_Content object + static private $content=false; + + /** + * register a new migration provider + * @param OC_Migrate_Provider $provider + */ + public static function registerProvider($provider) { + self::$providers[]=$provider; + } + + /** + * @brief finds and loads the providers + */ + static private function findProviders() { + // Find the providers + $apps = OC_App::getAllApps(); + + foreach($apps as $app) { + $path = OC_App::getAppPath($app) . '/appinfo/migrate.php'; + if( file_exists( $path ) ) { + include $path; + } + } + } + + /** + * @brief exports a user, or owncloud instance + * @param optional $uid string user id of user to export if export type is user, defaults to current + * @param ootional $type string type of export, defualts to user + * @param otional $path string path to zip output folder + * @return false on error, path to zip on success + */ + public static function export( $uid=null, $type='user', $path=null ) { + $datadir = OC_Config::getValue( 'datadirectory' ); + // Validate export type + $types = array( 'user', 'instance', 'system', 'userfiles' ); + if( !in_array( $type, $types ) ) { + OC_Log::write( 'migration', 'Invalid export type', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + self::$exporttype = $type; + // Userid? + if( self::$exporttype == 'user' ) { + // Check user exists + self::$uid = is_null($uid) ? OC_User::getUser() : $uid; + if(!OC_User::userExists(self::$uid)) { + return json_encode( array( 'success' => false) ); + } + } + // Calculate zipname + if( self::$exporttype == 'user' ) { + $zipname = 'oc_export_' . self::$uid . '_' . date("y-m-d_H-i-s") . '.zip'; + } else { + $zipname = 'oc_export_' . self::$exporttype . '_' . date("y-m-d_H-i-s") . '.zip'; + } + // Calculate path + if( self::$exporttype == 'user' ) { + self::$zippath = $datadir . '/' . self::$uid . '/' . $zipname; + } else { + if( !is_null( $path ) ) { + // Validate custom path + if( !file_exists( $path ) || !is_writeable( $path ) ) { + OC_Log::write( 'migration', 'Path supplied is invalid.', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + self::$zippath = $path . $zipname; + } else { + // Default path + self::$zippath = get_temp_dir() . '/' . $zipname; + } + } + // Create the zip object + if( !self::createZip() ) { + return json_encode( array( 'success' => false ) ); + } + // Do the export + self::findProviders(); + $exportdata = array(); + switch( self::$exporttype ) { + case 'user': + // Connect to the db + self::$dbpath = $datadir . '/' . self::$uid . '/migration.db'; + if( !self::connectDB() ) { + return json_encode( array( 'success' => false ) ); + } + self::$content = new OC_Migration_Content( self::$zip, self::$MDB2 ); + // Export the app info + $exportdata = self::exportAppData(); + // Add the data dir to the zip + self::$content->addDir(OC_User::getHome(self::$uid), true, '/' ); + break; + case 'instance': + self::$content = new OC_Migration_Content( self::$zip ); + // Creates a zip that is compatable with the import function + $dbfile = tempnam( get_temp_dir(), "owncloud_export_data_" ); + OC_DB::getDbStructure( $dbfile, 'MDB2_SCHEMA_DUMP_ALL'); + + // Now add in *dbname* and *dbprefix* + $dbexport = file_get_contents( $dbfile ); + $dbnamestring = "<database>\n\n <name>" . OC_Config::getValue( "dbname", "owncloud" ); + $dbtableprefixstring = "<table>\n\n <name>" . OC_Config::getValue( "dbtableprefix", "oc_" ); + $dbexport = str_replace( $dbnamestring, "<database>\n\n <name>*dbname*", $dbexport ); + $dbexport = str_replace( $dbtableprefixstring, "<table>\n\n <name>*dbprefix*", $dbexport ); + // Add the export to the zip + self::$content->addFromString( $dbexport, "dbexport.xml" ); + // Add user data + foreach(OC_User::getUsers() as $user) { + self::$content->addDir(OC_User::getHome($user), true, "/userdata/" ); + } + break; + case 'userfiles': + self::$content = new OC_Migration_Content( self::$zip ); + // Creates a zip with all of the users files + foreach(OC_User::getUsers() as $user) { + self::$content->addDir(OC_User::getHome($user), true, "/" ); + } + break; + case 'system': + self::$content = new OC_Migration_Content( self::$zip ); + // Creates a zip with the owncloud system files + self::$content->addDir( OC::$SERVERROOT . '/', false, '/'); + foreach (array( + ".git", + "3rdparty", + "apps", + "core", + "files", + "l10n", + "lib", + "ocs", + "search", + "settings", + "tests" + ) as $dir) { + self::$content->addDir( OC::$SERVERROOT . '/' . $dir, true, "/"); + } + break; + } + if( !$info = self::getExportInfo( $exportdata ) ) { + return json_encode( array( 'success' => false ) ); + } + // Add the export info json to the export zip + self::$content->addFromString( $info, 'export_info.json' ); + if( !self::$content->finish() ) { + return json_encode( array( 'success' => false ) ); + } + return json_encode( array( 'success' => true, 'data' => self::$zippath ) ); + } + + /** + * @brief imports a user, or owncloud instance + * @param $path string path to zip + * @param optional $type type of import (user or instance) + * @param optional $uid userid of new user + */ + public static function import( $path, $type='user', $uid=null ) { + + $datadir = OC_Config::getValue( 'datadirectory' ); + // Extract the zip + if( !$extractpath = self::extractZip( $path ) ) { + return json_encode( array( 'success' => false ) ); + } + // Get export_info.json + $scan = scandir( $extractpath ); + // Check for export_info.json + if( !in_array( 'export_info.json', $scan ) ) { + OC_Log::write( 'migration', 'Invalid import file, export_info.json not found', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + $json = json_decode( file_get_contents( $extractpath . 'export_info.json' ) ); + if( $json->exporttype != $type ) { + OC_Log::write( 'migration', 'Invalid import file', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + self::$exporttype = $type; + + $currentuser = OC_User::getUser(); + + // Have we got a user if type is user + if( self::$exporttype == 'user' ) { + self::$uid = !is_null($uid) ? $uid : $currentuser; + } + + // We need to be an admin if we are not importing our own data + if(($type == 'user' && self::$uid != $currentuser) || $type != 'user' ) { + if( !OC_User::isAdminUser($currentuser)) { + // Naughty. + OC_Log::write( 'migration', 'Import not permitted.', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + } + + // Handle export types + switch( self::$exporttype ) { + case 'user': + // Check user availability + if( !OC_User::userExists( self::$uid ) ) { + OC_Log::write( 'migration', 'User doesn\'t exist', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + + // Check if the username is valid + if( preg_match( '/[^a-zA-Z0-9 _\.@\-]/', $json->exporteduser )) { + OC_Log::write( 'migration', 'Username is not valid', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + + // Copy data + $userfolder = $extractpath . $json->exporteduser; + $newuserfolder = $datadir . '/' . self::$uid; + foreach(scandir($userfolder) as $file){ + if($file !== '.' && $file !== '..' && is_dir($file)) { + $file = str_replace(array('/', '\\'), '', $file); + + // Then copy the folder over + OC_Helper::copyr($userfolder.'/'.$file, $newuserfolder.'/'.$file); + } + } + // Import user app data + if(file_exists($extractpath . $json->exporteduser . '/migration.db')) { + if( !$appsimported = self::importAppData( $extractpath . $json->exporteduser . '/migration.db', + $json, + self::$uid ) ) { + return json_encode( array( 'success' => false ) ); + } + } + // All done! + if( !self::unlink_r( $extractpath ) ) { + OC_Log::write( 'migration', 'Failed to delete the extracted zip', OC_Log::ERROR ); + } + return json_encode( array( 'success' => true, 'data' => $appsimported ) ); + break; + case 'instance': + /* + * EXPERIMENTAL + // Check for new data dir and dbexport before doing anything + // TODO + + // Delete current data folder. + OC_Log::write( 'migration', "Deleting current data dir", OC_Log::INFO ); + if( !self::unlink_r( $datadir, false ) ) { + OC_Log::write( 'migration', 'Failed to delete the current data dir', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + + // Copy over data + if( !self::copy_r( $extractpath . 'userdata', $datadir ) ) { + OC_Log::write( 'migration', 'Failed to copy over data directory', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + + // Import the db + if( !OC_DB::replaceDB( $extractpath . 'dbexport.xml' ) ) { + return json_encode( array( 'success' => false ) ); + } + // Done + return json_encode( array( 'success' => true ) ); + */ + break; + } + + } + + /** + * @brief recursively deletes a directory + * @param $dir string path of dir to delete + * $param optional $deleteRootToo bool delete the root directory + * @return bool + */ + private static function unlink_r( $dir, $deleteRootToo=true ) { + if( !$dh = @opendir( $dir ) ) { + return false; + } + while (false !== ($obj = readdir($dh))) { + if($obj == '.' || $obj == '..') { + continue; + } + if (!@unlink($dir . '/' . $obj)) { + self::unlink_r($dir.'/'.$obj, true); + } + } + closedir($dh); + if ( $deleteRootToo ) { + @rmdir($dir); + } + return true; + } + + /** + * @brief tries to extract the import zip + * @param $path string path to the zip + * @return string path to extract location (with a trailing slash) or false on failure + */ + static private function extractZip( $path ) { + self::$zip = new ZipArchive; + // Validate path + if( !file_exists( $path ) ) { + OC_Log::write( 'migration', 'Zip not found', OC_Log::ERROR ); + return false; + } + if ( self::$zip->open( $path ) != true ) { + OC_Log::write( 'migration', "Failed to open zip file", OC_Log::ERROR ); + return false; + } + $to = get_temp_dir() . '/oc_import_' . self::$exporttype . '_' . date("y-m-d_H-i-s") . '/'; + if( !self::$zip->extractTo( $to ) ) { + return false; + } + self::$zip->close(); + return $to; + } + + /** + * @brief connects to a MDB2 database scheme + * @returns bool + */ + static private function connectScheme() { + // We need a mdb2 database connection + self::$MDB2->loadModule( 'Manager' ); + self::$MDB2->loadModule( 'Reverse' ); + + // Connect if this did not happen before + if( !self::$schema ) { + require_once 'MDB2/Schema.php'; + self::$schema=MDB2_Schema::factory( self::$MDB2 ); + } + + return true; + } + + /** + * @brief creates a migration.db in the users data dir with their app data in + * @return bool whether operation was successfull + */ + private static function exportAppData( ) { + + $success = true; + $return = array(); + + // Foreach provider + foreach( self::$providers as $provider ) { + // Check if the app is enabled + if( OC_App::isEnabled( $provider->getID() ) ) { + $success = true; + // Does this app use the database? + if( file_exists( OC_App::getAppPath($provider->getID()).'/appinfo/database.xml' ) ) { + // Create some app tables + $tables = self::createAppTables( $provider->getID() ); + if( is_array( $tables ) ) { + // Save the table names + foreach($tables as $table) { + $return['apps'][$provider->getID()]['tables'][] = $table; + } + } else { + // It failed to create the tables + $success = false; + } + } + + // Run the export function? + if( $success ) { + // Set the provider properties + $provider->setData( self::$uid, self::$content ); + $return['apps'][$provider->getID()]['success'] = $provider->export(); + } else { + $return['apps'][$provider->getID()]['success'] = false; + $return['apps'][$provider->getID()]['message'] = 'failed to create the app tables'; + } + + // Now add some app info the the return array + $appinfo = OC_App::getAppInfo( $provider->getID() ); + $return['apps'][$provider->getID()]['version'] = OC_App::getAppVersion($provider->getID()); + } + } + + return $return; + + } + + + /** + * @brief generates json containing export info, and merges any data supplied + * @param optional $array array of data to include in the returned json + * @return bool + */ + static private function getExportInfo( $array=array() ) { + $info = array( + 'ocversion' => OC_Util::getVersion(), + 'exporttime' => time(), + 'exportedby' => OC_User::getUser(), + 'exporttype' => self::$exporttype, + 'exporteduser' => self::$uid + ); + + if( !is_array( $array ) ) { + OC_Log::write( 'migration', 'Supplied $array was not an array in getExportInfo()', OC_Log::ERROR ); + } + // Merge in other data + $info = array_merge( $info, (array)$array ); + // Create json + $json = json_encode( $info ); + return $json; + } + + /** + * @brief connects to migration.db, or creates if not found + * @param $db optional path to migration.db, defaults to user data dir + * @return bool whether the operation was successful + */ + static private function connectDB( $path=null ) { + // Has the dbpath been set? + self::$dbpath = !is_null( $path ) ? $path : self::$dbpath; + if( !self::$dbpath ) { + OC_Log::write( 'migration', 'connectDB() was called without dbpath being set', OC_Log::ERROR ); + return false; + } + // Already connected + if(!self::$MDB2) { + require_once 'MDB2.php'; + + $datadir = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); + + // DB type + if( class_exists( 'SQLite3' ) ) { + $dbtype = 'sqlite3'; + } else if( is_callable( 'sqlite_open' ) ) { + $dbtype = 'sqlite'; + } else { + OC_Log::write( 'migration', 'SQLite not found', OC_Log::ERROR ); + return false; + } + + // Prepare options array + $options = array( + 'portability' => MDB2_PORTABILITY_ALL & (!MDB2_PORTABILITY_FIX_CASE), + 'log_line_break' => '<br>', + 'idxname_format' => '%s', + 'debug' => true, + 'quote_identifier' => true + ); + $dsn = array( + 'phptype' => $dbtype, + 'database' => self::$dbpath, + 'mode' => '0644' + ); + + // Try to establish connection + self::$MDB2 = MDB2::factory( $dsn, $options ); + // Die if we could not connect + if( PEAR::isError( self::$MDB2 ) ) { + die( self::$MDB2->getMessage() ); + OC_Log::write( 'migration', 'Failed to create/connect to migration.db', OC_Log::FATAL ); + OC_Log::write( 'migration', self::$MDB2->getUserInfo(), OC_Log::FATAL ); + OC_Log::write( 'migration', self::$MDB2->getMessage(), OC_Log::FATAL ); + return false; + } + // We always, really always want associative arrays + self::$MDB2->setFetchMode(MDB2_FETCHMODE_ASSOC); + } + return true; + + } + + /** + * @brief creates the tables in migration.db from an apps database.xml + * @param $appid string id of the app + * @return bool whether the operation was successful + */ + static private function createAppTables( $appid ) { + + if( !self::connectScheme() ) { + return false; + } + + // There is a database.xml file + $content = file_get_contents(OC_App::getAppPath($appid) . '/appinfo/database.xml' ); + + $file2 = 'static://db_scheme'; + // TODO get the relative path to migration.db from the data dir + // For now just cheat + $path = pathinfo( self::$dbpath ); + $content = str_replace( '*dbname*', self::$uid.'/migration', $content ); + $content = str_replace( '*dbprefix*', '', $content ); + + $xml = new SimpleXMLElement($content); + foreach($xml->table as $table) { + $tables[] = (string)$table->name; + } + + file_put_contents( $file2, $content ); + + // Try to create tables + $definition = self::$schema->parseDatabaseDefinitionFile( $file2 ); + + unlink( $file2 ); + + // Die in case something went wrong + if( $definition instanceof MDB2_Schema_Error ) { + OC_Log::write( 'migration', 'Failed to parse database.xml for: '.$appid, OC_Log::FATAL ); + OC_Log::write( 'migration', $definition->getMessage().': '.$definition->getUserInfo(), OC_Log::FATAL ); + return false; + } + + $definition['overwrite'] = true; + + $ret = self::$schema->createDatabase( $definition ); + + // Die in case something went wrong + if( $ret instanceof MDB2_Error ) { + OC_Log::write( 'migration', 'Failed to create tables for: '.$appid, OC_Log::FATAL ); + OC_Log::write( 'migration', $ret->getMessage().': '.$ret->getUserInfo(), OC_Log::FATAL ); + return false; + } + return $tables; + + } + + /** + * @brief tries to create the zip + * @param $path string path to zip destination + * @return bool + */ + static private function createZip() { + self::$zip = new ZipArchive; + // Check if properties are set + if( !self::$zippath ) { + OC_Log::write('migration', 'createZip() called but $zip and/or $zippath have not been set', OC_Log::ERROR); + return false; + } + if ( self::$zip->open( self::$zippath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE ) !== true ) { + OC_Log::write('migration', + 'Failed to create the zip with error: '.self::$zip->getStatusString(), + OC_Log::ERROR); + return false; + } else { + return true; + } + } + + /** + * @brief returns an array of apps that support migration + * @return array + */ + static public function getApps() { + $allapps = OC_App::getAllApps(); + foreach($allapps as $app) { + $path = self::getAppPath($app) . '/lib/migrate.php'; + if( file_exists( $path ) ) { + $supportsmigration[] = $app; + } + } + return $supportsmigration; + } + + /** + * @brief imports a new user + * @param $db string path to migration.db + * @param $info object of migration info + * @param $uid optional uid to use + * @return array of apps with import statuses, or false on failure. + */ + public static function importAppData( $db, $info, $uid=null ) { + // Check if the db exists + if( file_exists( $db ) ) { + // Connect to the db + if(!self::connectDB( $db )) { + OC_Log::write('migration', 'Failed to connect to migration.db', OC_Log::ERROR); + return false; + } + } else { + OC_Log::write('migration', 'Migration.db not found at: '.$db, OC_Log::FATAL ); + return false; + } + + // Find providers + self::findProviders(); + + // Generate importinfo array + $importinfo = array( + 'olduid' => $info->exporteduser, + 'newuid' => self::$uid + ); + + foreach( self::$providers as $provider) { + // Is the app in the export? + $id = $provider->getID(); + if( isset( $info->apps->$id ) ) { + // Is the app installed + if( !OC_App::isEnabled( $id ) ) { + OC_Log::write( 'migration', + 'App: ' . $id . ' is not installed, can\'t import data.', + OC_Log::INFO ); + $appsstatus[$id] = 'notsupported'; + } else { + // Did it succeed on export? + if( $info->apps->$id->success ) { + // Give the provider the content object + if( !self::connectDB( $db ) ) { + return false; + } + $content = new OC_Migration_Content( self::$zip, self::$MDB2 ); + $provider->setData( self::$uid, $content, $info ); + // Then do the import + if( !$appsstatus[$id] = $provider->import( $info->apps->$id, $importinfo ) ) { + // Failed to import app + OC_Log::write( 'migration', + 'Failed to import app data for user: ' . self::$uid . ' for app: ' . $id, + OC_Log::ERROR ); + } + } else { + // Add to failed list + $appsstatus[$id] = false; + } + } + } + } + + return $appsstatus; + + } + + /* + * @brief creates a new user in the database + * @param $uid string user_id of the user to be created + * @param $hash string hash of the user to be created + * @return bool result of user creation + */ + public static function createUser( $uid, $hash ) { + + // Check if userid exists + if(OC_User::userExists( $uid )) { + return false; + } + + // Create the user + $query = OC_DB::prepare( "INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ?, ? )" ); + $result = $query->execute( array( $uid, $hash)); + if( !$result ) { + OC_Log::write('migration', 'Failed to create the new user "'.$uid."", OC_Log::ERROR); + } + return $result ? true : false; + + } + +} diff --git a/lib/private/migration/content.php b/lib/private/migration/content.php new file mode 100644 index 00000000000..4413d722731 --- /dev/null +++ b/lib/private/migration/content.php @@ -0,0 +1,258 @@ +<?php +/** + * ownCloud + * + * @author Tom Needham + * @copyright 2012 Tom Needham tom@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +/** + * provides methods to add and access data from the migration + */ +class OC_Migration_Content{ + + private $zip=false; + // Holds the MDB2 object + private $db=null; + // Holds an array of tmpfiles to delete after zip creation + private $tmpfiles=array(); + + /** + * @brief sets up the + * @param $zip ZipArchive object + * @param optional $db a MDB2 database object (required for exporttype user) + * @return bool + */ + public function __construct( $zip, $db=null ) { + + $this->zip = $zip; + $this->db = $db; + + } + + // @brief prepares the db + // @param $query the sql query to prepare + public function prepare( $query ) { + + // Only add database to tmpfiles if actually used + if( !is_null( $this->db ) ) { + // Get db path + $db = $this->db->getDatabase(); + if(!in_array($db, $this->tmpfiles)) { + $this->tmpfiles[] = $db; + } + } + + // Optimize the query + $query = $this->processQuery( $query ); + + // Optimize the query + $query = $this->db->prepare( $query ); + + // Die if we have an error (error means: bad query, not 0 results!) + if( PEAR::isError( $query ) ) { + $entry = 'DB Error: "'.$query->getMessage().'"<br />'; + $entry .= 'Offending command was: '.$query.'<br />'; + OC_Log::write( 'migration', $entry, OC_Log::FATAL ); + return false; + } else { + return $query; + } + + } + + /** + * @brief processes the db query + * @param $query the query to process + * @return string of processed query + */ + private function processQuery( $query ) { + $query = str_replace( '`', '\'', $query ); + $query = str_replace( 'NOW()', 'datetime(\'now\')', $query ); + $query = str_replace( 'now()', 'datetime(\'now\')', $query ); + // remove table prefixes + $query = str_replace( '*PREFIX*', '', $query ); + return $query; + } + + /** + * @brief copys rows to migration.db from the main database + * @param $options array of options. + * @return bool + */ + public function copyRows( $options ) { + if( !array_key_exists( 'table', $options ) ) { + return false; + } + + $return = array(); + + // Need to include 'where' in the query? + if( array_key_exists( 'matchval', $options ) && array_key_exists( 'matchcol', $options ) ) { + + // If only one matchval, create an array + if(!is_array($options['matchval'])) { + $options['matchval'] = array( $options['matchval'] ); + } + + foreach( $options['matchval'] as $matchval ) { + // Run the query for this match value (where x = y value) + $sql = 'SELECT * FROM `*PREFIX*' . $options['table'] . '` WHERE `' . $options['matchcol'] . '` = ?'; + $query = OC_DB::prepare( $sql ); + $results = $query->execute( array( $matchval ) ); + $newreturns = $this->insertData( $results, $options ); + $return = array_merge( $return, $newreturns ); + } + + } else { + // Just get everything + $sql = 'SELECT * FROM `*PREFIX*' . $options['table'] . '`'; + $query = OC_DB::prepare( $sql ); + $results = $query->execute(); + $return = $this->insertData( $results, $options ); + + } + + return $return; + + } + + /** + * @brief saves a sql data set into migration.db + * @param $data a sql data set returned from self::prepare()->query() + * @param $options array of copyRows options + * @return void + */ + private function insertData( $data, $options ) { + $return = array(); + // Foreach row of data to insert + while( $row = $data->fetchRow() ) { + // Now save all this to the migration.db + foreach($row as $field=>$value) { + $fields[] = $field; + $values[] = $value; + } + + // Generate some sql + $sql = "INSERT INTO `" . $options['table'] . '` ( `'; + $fieldssql = implode( '`, `', $fields ); + $sql .= $fieldssql . "` ) VALUES( "; + $valuessql = substr( str_repeat( '?, ', count( $fields ) ), 0, -2 ); + $sql .= $valuessql . " )"; + // Make the query + $query = $this->prepare( $sql ); + if( !$query ) { + OC_Log::write( 'migration', 'Invalid sql produced: '.$sql, OC_Log::FATAL ); + return false; + exit(); + } else { + $query->execute( $values ); + // Do we need to return some values? + if( array_key_exists( 'idcol', $options ) ) { + // Yes we do + $return[] = $row[$options['idcol']]; + } else { + // Take a guess and return the first field :) + $return[] = reset($row); + } + } + $fields = ''; + $values = ''; + } + return $return; + } + + /** + * @brief adds a directory to the zip object + * @param $dir string path of the directory to add + * @param $recursive bool + * @param $internaldir string path of folder to add dir to in zip + * @return bool + */ + public function addDir( $dir, $recursive=true, $internaldir='' ) { + $dirname = basename($dir); + $this->zip->addEmptyDir($internaldir . $dirname); + $internaldir.=$dirname.='/'; + if( !file_exists( $dir ) ) { + return false; + } + $dirhandle = opendir($dir); + if(is_resource($dirhandle)) { + while (false !== ( $file = readdir($dirhandle))) { + + if (( $file != '.' ) && ( $file != '..' )) { + + if (is_dir($dir . '/' . $file) && $recursive) { + $this->addDir($dir . '/' . $file, $recursive, $internaldir); + } elseif (is_file($dir . '/' . $file)) { + $this->zip->addFile($dir . '/' . $file, $internaldir . $file); + } + } + } + closedir($dirhandle); + } else { + OC_Log::write('admin_export', "Was not able to open directory: " . $dir, OC_Log::ERROR); + return false; + } + return true; + } + + /** + * @brief adds a file to the zip from a given string + * @param $data string of data to add + * @param $path the relative path inside of the zip to save the file to + * @return bool + */ + public function addFromString( $data, $path ) { + // Create a temp file + $file = tempnam( get_temp_dir(). '/', 'oc_export_tmp_' ); + $this->tmpfiles[] = $file; + if( !file_put_contents( $file, $data ) ) { + OC_Log::write( 'migation', 'Failed to save data to a temporary file', OC_Log::ERROR ); + return false; + } + // Add file to the zip + $this->zip->addFile( $file, $path ); + return true; + } + + /** + * @brief closes the zip, removes temp files + * @return bool + */ + public function finish() { + if( !$this->zip->close() ) { + OC_Log::write( 'migration', + 'Failed to write the zip file with error: '.$this->zip->getStatusString(), + OC_Log::ERROR ); + return false; + } + $this->cleanup(); + return true; + } + + /** + * @brief cleans up after the zip + */ + private function cleanup() { + // Delete tmp files + foreach($this->tmpfiles as $i) { + unlink( $i ); + } + } +} diff --git a/lib/private/migration/provider.php b/lib/private/migration/provider.php new file mode 100644 index 00000000000..234ab3351f3 --- /dev/null +++ b/lib/private/migration/provider.php @@ -0,0 +1,52 @@ +<?php +/** + * provides search functionalty + */ +abstract class OC_Migration_Provider{ + + protected $id=false; + protected $content=false; + protected $uid=false; + protected $olduid=false; + protected $appinfo=false; + + public function __construct( $appid ) { + // Set the id + $this->id = $appid; + OC_Migrate::registerProvider( $this ); + } + + /** + * @brief exports data for apps + * @return array appdata to be exported + */ + abstract function export( ); + + /** + * @brief imports data for the app + * @return void + */ + abstract function import( ); + + /** + * @brief sets the OC_Migration_Content object to $this->content + * @param $content a OC_Migration_Content object + */ + public function setData( $uid, $content, $info=null ) { + $this->content = $content; + $this->uid = $uid; + $id = $this->id; + if( !is_null( $info ) ) { + $this->olduid = $info->exporteduser; + $this->appinfo = $info->apps->$id; + } + } + + /** + * @brief returns the appid of the provider + * @return string + */ + public function getID() { + return $this->id; + } +} diff --git a/lib/private/mimetypes.list.php b/lib/private/mimetypes.list.php new file mode 100644 index 00000000000..8ab8ac81bd8 --- /dev/null +++ b/lib/private/mimetypes.list.php @@ -0,0 +1,107 @@ +<?php +/** +* ownCloud +* +* @author Robin Appelman +* @copyright 2011 Robin Appelman icewind1991@gmail.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +/** + * list of mimetypes by extension + */ + +return array( + 'css'=>'text/css', + 'flac'=>'audio/flac', + 'gif'=>'image/gif', + 'gzip'=>'application/x-gzip', + 'gz'=>'application/x-gzip', + 'html'=>'text/html', + 'htm'=>'text/html', + 'ics'=>'text/calendar', + 'ical'=>'text/calendar', + 'jpeg'=>'image/jpeg', + 'jpg'=>'image/jpeg', + 'js'=>'application/javascript', + 'oga'=>'audio/ogg', + 'ogg'=>'audio/ogg', + 'ogv'=>'video/ogg', + 'pdf'=>'application/pdf', + 'png'=>'image/png', + 'svg'=>'image/svg+xml', + 'tar'=>'application/x-tar', + 'tgz'=>'application/x-compressed', + 'tar.gz'=>'application/x-compressed', + 'tif'=>'image/tiff', + 'tiff'=>'image/tiff', + 'txt'=>'text/plain', + 'zip'=>'application/zip', + 'wav'=>'audio/wav', + 'odt'=>'application/vnd.oasis.opendocument.text', + 'ods'=>'application/vnd.oasis.opendocument.spreadsheet', + 'odg'=>'application/vnd.oasis.opendocument.graphics', + 'odp'=>'application/vnd.oasis.opendocument.presentation', + 'pages'=>'application/x-iwork-pages-sffpages', + 'numbers'=>'application/x-iwork-numbers-sffnumbers', + 'keynote'=>'application/x-iwork-keynote-sffkey', + 'kra'=>'application/x-krita', + 'mp3'=>'audio/mpeg', + 'doc'=>'application/msword', + 'docx'=>'application/msword', + 'xls'=>'application/msexcel', + 'xlsx'=>'application/msexcel', + 'php'=>'application/x-php', + 'exe'=>'application/x-ms-dos-executable', + 'pl'=>'application/x-pearl', + 'py'=>'application/x-python', + 'blend'=>'application/x-blender', + 'xcf'=>'application/x-gimp', + 'psd'=>'application/x-photoshop', + 'xml'=>'application/xml', + 'avi'=>'video/x-msvideo', + 'dv'=>'video/dv', + 'm2t'=>'video/mp2t', + 'mp4'=>'video/mp4', + 'm4v'=>'video/mp4', + 'mpg'=>'video/mpeg', + 'mpeg'=>'video/mpeg', + 'mov'=>'video/quicktime', + 'webm'=>'video/webm', + 'wmv'=>'video/x-ms-asf', + 'py'=>'text/x-script.phyton', + 'vcf' => 'text/vcard', + 'vcard' => 'text/vcard', + 'doc'=>'application/msword', + 'docx'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xls'=>'application/msexcel', + 'xlsx'=>'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'ppt'=>'application/mspowerpoint', + 'pptx'=>'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'sgf' => 'application/sgf', + 'cdr' => 'application/coreldraw', + 'impress' => 'text/impress', + 'ai' => 'application/illustrator', + 'epub' => 'application/epub+zip', + 'mobi' => 'application/x-mobipocket-ebook', + 'exe' => 'application', + 'msi' => 'application', + 'md' => 'text/markdown', + 'markdown' => 'text/markdown', + 'mdown' => 'text/markdown', + 'mdwn' => 'text/markdown', + 'reveal' => 'text/reveal' +); diff --git a/lib/private/minimizer.php b/lib/private/minimizer.php new file mode 100644 index 00000000000..db522de74dc --- /dev/null +++ b/lib/private/minimizer.php @@ -0,0 +1,64 @@ +<?php + +abstract class OC_Minimizer { + public function generateETag($files) { + $fullpath_files = array(); + foreach($files as $file_info) { + $fullpath_files[] = $file_info[0] . '/' . $file_info[2]; + } + return OC_Cache::generateCacheKeyFromFiles($fullpath_files); + } + + abstract public function minimizeFiles($files); + + public function output($files, $cache_key) { + header('Content-Type: '.$this->contentType); + OC_Response::enableCaching(); + $etag = $this->generateETag($files); + $cache_key .= '-'.$etag; + + $gzout = false; + $cache = OC_Cache::getGlobalCache(); + if (!OC_Request::isNoCache() && (!defined('DEBUG') || !DEBUG)) { + OC_Response::setETagHeader($etag); + $gzout = $cache->get($cache_key.'.gz'); + } + + if (!$gzout) { + $out = $this->minimizeFiles($files); + $gzout = gzencode($out); + $cache->set($cache_key.'.gz', $gzout); + OC_Response::setETagHeader($etag); + } + // on some systems (e.g. SLES 11, but not Ubuntu) mod_deflate and zlib compression will compress the output twice. + // This results in broken core.css and core.js. To avoid it, we switch off zlib compression. + // Since mod_deflate is still active, Apache will compress what needs to be compressed, i.e. no disadvantage. + if(function_exists('apache_get_modules') && ini_get('zlib.output_compression') && in_array('mod_deflate', apache_get_modules())) { + ini_set('zlib.output_compression', 'Off'); + } + if ($encoding = OC_Request::acceptGZip()) { + header('Content-Encoding: '.$encoding); + $out = $gzout; + } else { + $out = gzdecode($gzout); + } + header('Content-Length: '.strlen($out)); + echo $out; + } + + public function clearCache() { + $cache = OC_Cache::getGlobalCache(); + $cache->clear('core.css'); + $cache->clear('core.js'); + } +} + +if (!function_exists('gzdecode')) { + function gzdecode($data, $maxlength=null, &$filename='', &$error='') + { + if (strcmp(substr($data, 0, 9),"\x1f\x8b\x8\0\0\0\0\0\0")) { + return null; // Not the GZIP format we expect (See RFC 1952) + } + return gzinflate(substr($data, 10, -8)); + } +} diff --git a/lib/private/minimizer/css.php b/lib/private/minimizer/css.php new file mode 100644 index 00000000000..8d130572e2b --- /dev/null +++ b/lib/private/minimizer/css.php @@ -0,0 +1,38 @@ +<?php + +require_once 'mediawiki/CSSMin.php'; + +class OC_Minimizer_CSS extends OC_Minimizer +{ + protected $contentType = 'text/css'; + + public function minimizeFiles($files) { + $css_out = ''; + $webroot = (string) OC::$WEBROOT; + foreach($files as $file_info) { + $file = $file_info[0] . '/' . $file_info[2]; + $css_out .= '/* ' . $file . ' */' . "\n"; + $css = file_get_contents($file); + + $in_root = false; + foreach(OC::$APPSROOTS as $app_root) { + if(strpos($file, $app_root['path'].'/') === 0) { + $in_root = rtrim($webroot.$app_root['url'], '/'); + break; + } + } + if ($in_root !== false) { + $css = str_replace('%appswebroot%', $in_root, $css); + $css = str_replace('%webroot%', $webroot, $css); + } + $remote = $file_info[1]; + $remote .= '/'; + $remote .= dirname($file_info[2]); + $css_out .= CSSMin::remap($css, dirname($file), $remote, true); + } + if (!defined('DEBUG') || !DEBUG) { + $css_out = CSSMin::minify($css_out); + } + return $css_out; + } +} diff --git a/lib/private/minimizer/js.php b/lib/private/minimizer/js.php new file mode 100644 index 00000000000..bd2d836deb0 --- /dev/null +++ b/lib/private/minimizer/js.php @@ -0,0 +1,21 @@ +<?php + +require_once 'mediawiki/JavaScriptMinifier.php'; + +class OC_Minimizer_JS extends OC_Minimizer +{ + protected $contentType = 'application/javascript'; + + public function minimizeFiles($files) { + $js_out = ''; + foreach($files as $file_info) { + $file = $file_info[0] . '/' . $file_info[2]; + $js_out .= '/* ' . $file . ' */' . "\n"; + $js_out .= file_get_contents($file); + } + if (!defined('DEBUG') || !DEBUG) { + $js_out = JavaScriptMinifier::minify($js_out); + } + return $js_out; + } +} diff --git a/lib/private/navigationmanager.php b/lib/private/navigationmanager.php new file mode 100644 index 00000000000..1f657b9ad80 --- /dev/null +++ b/lib/private/navigationmanager.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + * + */ + +namespace OC; + +/** + * Manages the ownCloud navigation + */ +class NavigationManager implements \OCP\INavigationManager { + protected $entries = array(); + protected $activeEntry; + + /** + * Creates a new navigation entry + * @param array $entry containing: id, name, order, icon and href key + */ + public function add(array $entry) { + $entry['active'] = false; + if(!isset($entry['icon'])) { + $entry['icon'] = ''; + } + $this->entries[] = $entry; + } + + /** + * @brief returns all the added Menu entries + * @return array of the added entries + */ + public function getAll() { + return $this->entries; + } + + /** + * @brief removes all the entries + */ + public function clear() { + $this->entries = array(); + } + + /** + * Sets the current navigation entry of the currently running app + * @param string $id of the app entry to activate (from added $entry) + */ + public function setActiveEntry($id) { + $this->activeEntry = $id; + } + + /** + * @brief gets the active Menu entry + * @return string id or empty string + * + * This function returns the id of the active navigation entry (set by + * setActiveEntry + */ + public function getActiveEntry() { + return $this->activeEntry; + } +} diff --git a/lib/private/notsquareexception.php b/lib/private/notsquareexception.php new file mode 100644 index 00000000000..03dba8fb25f --- /dev/null +++ b/lib/private/notsquareexception.php @@ -0,0 +1,12 @@ +<?php +/** + * Copyright (c) 2013 Christopher Schäpers <christopher@schaepers.it> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC; + +class NotSquareException extends \Exception { +} diff --git a/lib/private/ocs.php b/lib/private/ocs.php new file mode 100644 index 00000000000..93e8931ce2e --- /dev/null +++ b/lib/private/ocs.php @@ -0,0 +1,263 @@ +<?php + +/** +* ownCloud +* +* @author Frank Karlitschek +* @author Michael Gapczynski +* @copyright 2012 Frank Karlitschek frank@owncloud.org +* @copyright 2012 Michael Gapczynski mtgap@owncloud.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; + +/** + * Class to handle open collaboration services API requests + * + */ +class OC_OCS { + + /** + * reads input date from get/post/cookies and converts the date to a special data-type + * + * @param string HTTP method to read the key from + * @param string Parameter to read + * @param string Variable type to format data + * @param mixed Default value to return if the key is not found + * @return mixed Data or if the key is not found and no default is set it will exit with a 400 Bad request + */ + public static function readData($method, $key, $type = 'raw', $default = null) { + if ($method == 'get') { + if (isset($_GET[$key])) { + $data = $_GET[$key]; + } else if (isset($default)) { + return $default; + } else { + $data = false; + } + } else if ($method == 'post') { + if (isset($_POST[$key])) { + $data = $_POST[$key]; + } else if (isset($default)) { + return $default; + } else { + $data = false; + } + } + if ($data === false) { + echo self::generateXml('', 'fail', 400, 'Bad request. Please provide a valid '.$key); + exit(); + } else { + // NOTE: Is the raw type necessary? It might be a little risky without sanitization + if ($type == 'raw') return $data; + elseif ($type == 'text') return OC_Util::sanitizeHTML($data); + elseif ($type == 'int') return (int) $data; + elseif ($type == 'float') return (float) $data; + elseif ($type == 'array') return OC_Util::sanitizeHTML($data); + else return OC_Util::sanitizeHTML($data); + } + } + + public static function notFound() { + if($_SERVER['REQUEST_METHOD'] == 'GET') { + $method='get'; + }elseif($_SERVER['REQUEST_METHOD'] == 'PUT') { + $method='put'; + parse_str(file_get_contents("php://input"), $put_vars); + }elseif($_SERVER['REQUEST_METHOD'] == 'POST') { + $method='post'; + }else{ + echo('internal server error: method not supported'); + exit(); + } + + $format = self::readData($method, 'format', 'text', ''); + $txt='Invalid query, please check the syntax. API specifications are here:' + .' http://www.freedesktop.org/wiki/Specifications/open-collaboration-services. DEBUG OUTPUT:'."\n"; + $txt.=OC_OCS::getDebugOutput(); + echo(OC_OCS::generateXml($format, 'failed', 999, $txt)); + + } + + /** + * generated some debug information to make it easier to find faild API calls + * @return debug data string + */ + private static function getDebugOutput() { + $txt=''; + $txt.="debug output:\n"; + if(isset($_SERVER['REQUEST_METHOD'])) $txt.='http request method: '.$_SERVER['REQUEST_METHOD']."\n"; + if(isset($_SERVER['REQUEST_URI'])) $txt.='http request uri: '.$_SERVER['REQUEST_URI']."\n"; + if(isset($_GET)) foreach($_GET as $key=>$value) $txt.='get parameter: '.$key.'->'.$value."\n"; + if(isset($_POST)) foreach($_POST as $key=>$value) $txt.='post parameter: '.$key.'->'.$value."\n"; + return($txt); + } + + + /** + * generates the xml or json response for the API call from an multidimenional data array. + * @param string $format + * @param string $status + * @param string $statuscode + * @param string $message + * @param array $data + * @param string $tag + * @param string $tagattribute + * @param int $dimension + * @param int $itemscount + * @param int $itemsperpage + * @return string xml/json + */ + private static function generateXml($format, $status, $statuscode, + $message, $data=array(), $tag='', $tagattribute='', $dimension=-1, $itemscount='', $itemsperpage='') { + if($format=='json') { + $json=array(); + $json['status']=$status; + $json['statuscode']=$statuscode; + $json['message']=$message; + $json['totalitems']=$itemscount; + $json['itemsperpage']=$itemsperpage; + $json['data']=$data; + return(json_encode($json)); + }else{ + $txt=''; + $writer = xmlwriter_open_memory(); + xmlwriter_set_indent( $writer, 2 ); + xmlwriter_start_document($writer ); + xmlwriter_start_element($writer, 'ocs'); + xmlwriter_start_element($writer, 'meta'); + xmlwriter_write_element($writer, 'status', $status); + xmlwriter_write_element($writer, 'statuscode', $statuscode); + xmlwriter_write_element($writer, 'message', $message); + if($itemscount<>'') xmlwriter_write_element($writer, 'totalitems', $itemscount); + if(!empty($itemsperpage)) xmlwriter_write_element($writer, 'itemsperpage', $itemsperpage); + xmlwriter_end_element($writer); + if($dimension=='0') { + // 0 dimensions + xmlwriter_write_element($writer, 'data', $data); + + }elseif($dimension=='1') { + xmlwriter_start_element($writer, 'data'); + foreach($data as $key=>$entry) { + xmlwriter_write_element($writer, $key, $entry); + } + xmlwriter_end_element($writer); + + }elseif($dimension=='2') { + xmlwriter_start_element($writer, 'data'); + foreach($data as $entry) { + xmlwriter_start_element($writer, $tag); + if(!empty($tagattribute)) { + xmlwriter_write_attribute($writer, 'details', $tagattribute); + } + foreach($entry as $key=>$value) { + if(is_array($value)) { + foreach($value as $k=>$v) { + xmlwriter_write_element($writer, $k, $v); + } + } else { + xmlwriter_write_element($writer, $key, $value); + } + } + xmlwriter_end_element($writer); + } + xmlwriter_end_element($writer); + + }elseif($dimension=='3') { + xmlwriter_start_element($writer, 'data'); + foreach($data as $entrykey=>$entry) { + xmlwriter_start_element($writer, $tag); + if(!empty($tagattribute)) { + xmlwriter_write_attribute($writer, 'details', $tagattribute); + } + foreach($entry as $key=>$value) { + if(is_array($value)) { + xmlwriter_start_element($writer, $entrykey); + foreach($value as $k=>$v) { + xmlwriter_write_element($writer, $k, $v); + } + xmlwriter_end_element($writer); + } else { + xmlwriter_write_element($writer, $key, $value); + } + } + xmlwriter_end_element($writer); + } + xmlwriter_end_element($writer); + }elseif($dimension=='dynamic') { + xmlwriter_start_element($writer, 'data'); + OC_OCS::toxml($writer, $data, 'comment'); + xmlwriter_end_element($writer); + } + + xmlwriter_end_element($writer); + + xmlwriter_end_document( $writer ); + $txt.=xmlwriter_output_memory( $writer ); + unset($writer); + return($txt); + } + } + + public static function toXml($writer, $data, $node) { + foreach($data as $key => $value) { + if (is_numeric($key)) { + $key = $node; + } + if (is_array($value)) { + xmlwriter_start_element($writer, $key); + OC_OCS::toxml($writer, $value, $node); + xmlwriter_end_element($writer); + }else{ + xmlwriter_write_element($writer, $key, $value); + } + } + } + + /** + * get private data + * @param string $user + * @param string $app + * @param string $key + * @param bool $like use LIKE instead of = when comparing keys + * @return array + */ + public static function getData($user, $app="", $key="") { + if($app) { + $apps=array($app); + }else{ + $apps=OC_Preferences::getApps($user); + } + if($key) { + $keys=array($key); + }else{ + foreach($apps as $app) { + $keys=OC_Preferences::getKeys($user, $app); + } + } + $result=array(); + foreach($apps as $app) { + foreach($keys as $key) { + $value=OC_Preferences::getValue($user, $app, $key); + $result[]=array('app'=>$app, 'key'=>$key, 'value'=>$value); + } + } + return $result; + } + +} diff --git a/lib/private/ocs/cloud.php b/lib/private/ocs/cloud.php new file mode 100644 index 00000000000..2dd99319057 --- /dev/null +++ b/lib/private/ocs/cloud.php @@ -0,0 +1,108 @@ +<?php +/** +* ownCloud +* +* @author Frank Karlitschek +* @author Tom Needham +* @copyright 2012 Frank Karlitschek frank@owncloud.org +* @copyright 2012 Tom Needham tom@owncloud.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +class OC_OCS_Cloud { + + public static function getCapabilities($parameters) { + $result = array(); + list($major, $minor, $micro) = OC_Util::getVersion(); + $result['version'] = array( + 'major' => $major, + 'minor' => $minor, + 'micro' => $micro, + 'string' => OC_Util::getVersionString(), + 'edition' => OC_Util::getEditionString(), + ); + + $result['capabilities'] = array( + 'core' => array( + 'pollinterval' => OC_Config::getValue('pollinterval', 60), + ), + ); + + return new OC_OCS_Result($result); + } + + /** + * gets user info + * + * exposes the quota of an user: + * <data> + * <quota> + * <free>1234</free> + * <used>4321</used> + * <total>5555</total> + * <ralative>0.78</ralative> + * </quota> + * </data> + * + * @param $parameters object should contain parameter 'userid' which identifies + * the user from whom the information will be returned + */ + public static function getUser($parameters) { + // Check if they are viewing information on themselves + if($parameters['userid'] === OC_User::getUser()) { + // Self lookup + $quota = array(); + $storage = OC_Helper::getStorageInfo(); + $quota = array( + 'free' => $storage['free'], + 'used' => $storage['used'], + 'total' => $storage['total'], + 'relative' => $storage['relative'], + ); + return new OC_OCS_Result(array('quota' => $quota)); + } else { + // No permission to view this user data + return new OC_OCS_Result(null, 997); + } + } + + public static function getUserPublickey($parameters) { + + if(OC_User::userExists($parameters['user'])) { + // calculate the disc space + // TODO + return new OC_OCS_Result(array()); + } else { + return new OC_OCS_Result(null, 300); + } + } + + public static function getUserPrivatekey($parameters) { + $user = OC_User::getUser(); + if(OC_User::isAdminUser($user) or ($user==$parameters['user'])) { + + if(OC_User::userExists($user)) { + // calculate the disc space + $txt = 'this is the private key of '.$parameters['user']; + echo($txt); + } else { + return new OC_OCS_Result(null, 300, 'User does not exist'); + } + } else { + return new OC_OCS_Result('null', 300, 'You don´t have permission to access this ressource.'); + } + } +} diff --git a/lib/private/ocs/config.php b/lib/private/ocs/config.php new file mode 100644 index 00000000000..f19121f4b2b --- /dev/null +++ b/lib/private/ocs/config.php @@ -0,0 +1,36 @@ +<?php +/** +* ownCloud +* +* @author Frank Karlitschek +* @author Tom Needham +* @copyright 2012 Frank Karlitschek frank@owncloud.org +* @copyright 2012 Tom Needham tom@owncloud.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +class OC_OCS_Config { + + public static function apiConfig($parameters) { + $xml['version'] = '1.7'; + $xml['website'] = 'ownCloud'; + $xml['host'] = OCP\Util::getServerHost(); + $xml['contact'] = ''; + $xml['ssl'] = 'false'; + return new OC_OCS_Result($xml); + } + +} diff --git a/lib/private/ocs/person.php b/lib/private/ocs/person.php new file mode 100644 index 00000000000..1c8210d0825 --- /dev/null +++ b/lib/private/ocs/person.php @@ -0,0 +1,42 @@ +<?php +/** +* ownCloud +* +* @author Frank Karlitschek +* @author Tom Needham +* @copyright 2012 Frank Karlitschek frank@owncloud.org +* @copyright 2012 Tom Needham tom@owncloud.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +class OC_OCS_Person { + + public static function check($parameters) { + $login = isset($_POST['login']) ? $_POST['login'] : false; + $password = isset($_POST['password']) ? $_POST['password'] : false; + if($login && $password) { + if(OC_User::checkPassword($login, $password)) { + $xml['person']['personid'] = $login; + return new OC_OCS_Result($xml); + } else { + return new OC_OCS_Result(null, 102); + } + } else { + return new OC_OCS_Result(null, 101); + } + } + +} diff --git a/lib/private/ocs/privatedata.php b/lib/private/ocs/privatedata.php new file mode 100644 index 00000000000..4dfd0a6e66e --- /dev/null +++ b/lib/private/ocs/privatedata.php @@ -0,0 +1,66 @@ +<?php +/** +* ownCloud +* +* @author Frank Karlitschek +* @author Tom Needham +* @copyright 2012 Frank Karlitschek frank@owncloud.org +* @copyright 2012 Tom Needham tom@owncloud.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +class OC_OCS_Privatedata { + + public static function get($parameters) { + OC_Util::checkLoggedIn(); + $user = OC_User::getUser(); + $app = addslashes(strip_tags($parameters['app'])); + $key = addslashes(strip_tags($parameters['key'])); + $result = OC_OCS::getData($user, $app, $key); + $xml = array(); + foreach($result as $i=>$log) { + $xml[$i]['key']=$log['key']; + $xml[$i]['app']=$log['app']; + $xml[$i]['value']=$log['value']; + } + return new OC_OCS_Result($xml); + //TODO: replace 'privatedata' with 'attribute' once a new libattice has been released that works with it + } + + public static function set($parameters) { + OC_Util::checkLoggedIn(); + $user = OC_User::getUser(); + $app = addslashes(strip_tags($parameters['app'])); + $key = addslashes(strip_tags($parameters['key'])); + $value = OC_OCS::readData('post', 'value', 'text'); + if(OC_Preferences::setValue($user, $app, $key, $value)) { + return new OC_OCS_Result(null, 100); + } + } + + public static function delete($parameters) { + OC_Util::checkLoggedIn(); + $user = OC_User::getUser(); + $app = addslashes(strip_tags($parameters['app'])); + $key = addslashes(strip_tags($parameters['key'])); + if($key==="" or $app==="") { + return new OC_OCS_Result(null, 101); //key and app are NOT optional here + } + if(OC_Preferences::deleteKey($user, $app, $key)) { + return new OC_OCS_Result(null, 100); + } + } +} diff --git a/lib/private/ocs/result.php b/lib/private/ocs/result.php new file mode 100644 index 00000000000..84f06fa01c7 --- /dev/null +++ b/lib/private/ocs/result.php @@ -0,0 +1,97 @@ +<?php +/** +* ownCloud +* +* @author Tom Needham +* @copyright 2012 Tom Needham tom@owncloud.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +class OC_OCS_Result{ + + protected $data, $message, $statusCode, $items, $perPage; + + /** + * create the OCS_Result object + * @param $data mixed the data to return + */ + public function __construct($data=null, $code=100, $message=null) { + $this->data = $data; + $this->statusCode = $code; + $this->message = $message; + } + + /** + * optionally set the total number of items available + * @param $items int + */ + public function setTotalItems(int $items) { + $this->items = $items; + } + + /** + * optionally set the the number of items per page + * @param $items int + */ + public function setItemsPerPage(int $items) { + $this->perPage = $items; + } + + /** + * get the status code + * @return int + */ + public function getStatusCode() { + return $this->statusCode; + } + + /** + * get the meta data for the result + * @return array + */ + public function getMeta() { + $meta = array(); + $meta['status'] = ($this->statusCode === 100) ? 'ok' : 'failure'; + $meta['statuscode'] = $this->statusCode; + $meta['message'] = $this->message; + if(isset($this->items)) { + $meta['totalitems'] = $this->items; + } + if(isset($this->perPage)) { + $meta['itemsperpage'] = $this->perPage; + } + return $meta; + + } + + /** + * get the result data + * @return array|string|int + */ + public function getData() { + return $this->data; + } + + /** + * return bool if the method succedded + * @return bool + */ + public function succeeded() { + return (substr($this->statusCode, 0, 1) === '1'); + } + + +} diff --git a/lib/private/ocsclient.php b/lib/private/ocsclient.php new file mode 100644 index 00000000000..58636f806be --- /dev/null +++ b/lib/private/ocsclient.php @@ -0,0 +1,208 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @author Jakob Sack + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * This class provides an easy way for apps to store config values in the + * database. + */ + +class OC_OCSClient{ + + /** + * @brief Get the url of the OCS AppStore server. + * @returns string of the AppStore server + * + * This function returns the url of the OCS AppStore server. It´s possible + * to set it in the config file or it will fallback to the default + */ + private static function getAppStoreURL() { + $url = OC_Config::getValue('appstoreurl', 'http://api.apps.owncloud.com/v1'); + return($url); + } + + + /** + * @brief Get the content of an OCS url call. + * @returns string of the response + * This function calls an OCS server and returns the response. It also sets a sane timeout + */ + private static function getOCSresponse($url) { + $data = \OC_Util::getUrlContent($url); + return($data); + } + + /** + * @brief Get all the categories from the OCS server + * @returns array with category ids + * @note returns NULL if config value appstoreenabled is set to false + * This function returns a list of all the application categories on the OCS server + */ + public static function getCategories() { + if(OC_Config::getValue('appstoreenabled', true)==false) { + return null; + } + $url=OC_OCSClient::getAppStoreURL().'/content/categories'; + $xml=OC_OCSClient::getOCSresponse($url); + if($xml==false) { + return null; + } + $data=simplexml_load_string($xml); + + $tmp=$data->data; + $cats=array(); + + foreach($tmp->category as $value) { + + $id= (int) $value->id; + $name= (string) $value->name; + $cats[$id]=$name; + + } + + return $cats; + } + + /** + * @brief Get all the applications from the OCS server + * @returns array with application data + * + * This function returns a list of all the applications on the OCS server + */ + public static function getApplications($categories, $page, $filter) { + if(OC_Config::getValue('appstoreenabled', true)==false) { + return(array()); + } + + if(is_array($categories)) { + $categoriesstring=implode('x', $categories); + }else{ + $categoriesstring=$categories; + } + + $version='&version='.implode('x', \OC_Util::getVersion()); + $filterurl='&filter='.urlencode($filter); + $url=OC_OCSClient::getAppStoreURL().'/content/data?categories='.urlencode($categoriesstring) + .'&sortmode=new&page='.urlencode($page).'&pagesize=100'.$filterurl.$version; + $apps=array(); + $xml=OC_OCSClient::getOCSresponse($url); + + if($xml==false) { + return null; + } + $data=simplexml_load_string($xml); + + $tmp=$data->data->content; + for($i = 0; $i < count($tmp); $i++) { + $app=array(); + $app['id']=(string)$tmp[$i]->id; + $app['name']=(string)$tmp[$i]->name; + $app['label']=(string)$tmp[$i]->label; + $app['version']=(string)$tmp[$i]->version; + $app['type']=(string)$tmp[$i]->typeid; + $app['typename']=(string)$tmp[$i]->typename; + $app['personid']=(string)$tmp[$i]->personid; + $app['license']=(string)$tmp[$i]->license; + $app['detailpage']=(string)$tmp[$i]->detailpage; + $app['preview']=(string)$tmp[$i]->smallpreviewpic1; + $app['changed']=strtotime($tmp[$i]->changed); + $app['description']=(string)$tmp[$i]->description; + $app['score']=(string)$tmp[$i]->score; + + $apps[]=$app; + } + return $apps; + } + + + /** + * @brief Get an the applications from the OCS server + * @returns array with application data + * + * This function returns an applications from the OCS server + */ + public static function getApplication($id) { + if(OC_Config::getValue('appstoreenabled', true)==false) { + return null; + } + $url=OC_OCSClient::getAppStoreURL().'/content/data/'.urlencode($id); + $xml=OC_OCSClient::getOCSresponse($url); + + if($xml==false) { + OC_Log::write('core', 'Unable to parse OCS content', OC_Log::FATAL); + return null; + } + $data=simplexml_load_string($xml); + + $tmp=$data->data->content; + $app=array(); + $app['id']=$tmp->id; + $app['name']=$tmp->name; + $app['version']=$tmp->version; + $app['type']=$tmp->typeid; + $app['label']=$tmp->label; + $app['typename']=$tmp->typename; + $app['personid']=$tmp->personid; + $app['detailpage']=$tmp->detailpage; + $app['preview1']=$tmp->smallpreviewpic1; + $app['preview2']=$tmp->smallpreviewpic2; + $app['preview3']=$tmp->smallpreviewpic3; + $app['changed']=strtotime($tmp->changed); + $app['description']=$tmp->description; + $app['detailpage']=$tmp->detailpage; + $app['score']=$tmp->score; + + return $app; + } + + /** + * @brief Get the download url for an application from the OCS server + * @returns array with application data + * + * This function returns an download url for an applications from the OCS server + */ + public static function getApplicationDownload($id, $item) { + if(OC_Config::getValue('appstoreenabled', true)==false) { + return null; + } + $url=OC_OCSClient::getAppStoreURL().'/content/download/'.urlencode($id).'/'.urlencode($item); + $xml=OC_OCSClient::getOCSresponse($url); + + if($xml==false) { + OC_Log::write('core', 'Unable to parse OCS content', OC_Log::FATAL); + return null; + } + $data=simplexml_load_string($xml); + + $tmp=$data->data->content; + $app=array(); + if(isset($tmp->downloadlink)) { + $app['downloadlink']=$tmp->downloadlink; + }else{ + $app['downloadlink']=''; + } + return $app; + } + + + +} diff --git a/lib/private/preferences.php b/lib/private/preferences.php new file mode 100644 index 00000000000..359d9a83589 --- /dev/null +++ b/lib/private/preferences.php @@ -0,0 +1,232 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @author Jakob Sack + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +/* + * + * The following SQL statement is just a help for developers and will not be + * executed! + * + * CREATE TABLE `preferences` ( + * `userid` VARCHAR( 255 ) NOT NULL , + * `appid` VARCHAR( 255 ) NOT NULL , + * `configkey` VARCHAR( 255 ) NOT NULL , + * `configvalue` VARCHAR( 255 ) NOT NULL + * ) + * + */ + +namespace OC; + +use \OC\DB\Connection; + + +/** + * This class provides an easy way for storing user preferences. + */ +class Preferences { + protected $conn; + + public function __construct(Connection $conn) { + $this->conn = $conn; + } + + /** + * @brief Get all users using the preferences + * @return array with user ids + * + * This function returns a list of all users that have at least one entry + * in the preferences table. + */ + public function getUsers() { + $query = 'SELECT DISTINCT `userid` FROM `*PREFIX*preferences`'; + $result = $this->conn->executeQuery( $query ); + + $users = array(); + while( $userid = $result->fetchColumn()) { + $users[] = $userid; + } + + return $users; + } + + /** + * @brief Get all apps of an user + * @param string $user user + * @return array with app ids + * + * This function returns a list of all apps of the user that have at least + * one entry in the preferences table. + */ + public function getApps( $user ) { + $query = 'SELECT DISTINCT `appid` FROM `*PREFIX*preferences` WHERE `userid` = ?'; + $result = $this->conn->executeQuery( $query, array( $user ) ); + + $apps = array(); + while( $appid = $result->fetchColumn()) { + $apps[] = $appid; + } + + return $apps; + } + + /** + * @brief Get the available keys for an app + * @param string $user user + * @param string $app the app we are looking for + * @return array with key names + * + * This function gets all keys of an app of an user. Please note that the + * values are not returned. + */ + public function getKeys( $user, $app ) { + $query = 'SELECT `configkey` FROM `*PREFIX*preferences` WHERE `userid` = ? AND `appid` = ?'; + $result = $this->conn->executeQuery( $query, array( $user, $app )); + + $keys = array(); + while( $key = $result->fetchColumn()) { + $keys[] = $key; + } + + return $keys; + } + + /** + * @brief Gets the preference + * @param string $user user + * @param string $app app + * @param string $key key + * @param string $default = null, default value if the key does not exist + * @return string the value or $default + * + * This function gets a value from the preferences table. If the key does + * not exist the default value will be returned + */ + public function getValue( $user, $app, $key, $default = null ) { + // Try to fetch the value, return default if not exists. + $query = 'SELECT `configvalue` FROM `*PREFIX*preferences`' + .' WHERE `userid` = ? AND `appid` = ? AND `configkey` = ?'; + $row = $this->conn->fetchAssoc( $query, array( $user, $app, $key )); + if($row) { + return $row["configvalue"]; + } else { + return $default; + } + } + + /** + * @brief sets a value in the preferences + * @param string $user user + * @param string $app app + * @param string $key key + * @param string $value value + * + * Adds a value to the preferences. If the key did not exist before, it + * will be added automagically. + */ + public function setValue( $user, $app, $key, $value ) { + // Check if the key does exist + $query = 'SELECT COUNT(*) FROM `*PREFIX*preferences`' + .' WHERE `userid` = ? AND `appid` = ? AND `configkey` = ?'; + $count = $this->conn->fetchColumn( $query, array( $user, $app, $key )); + $exists = $count > 0; + + if( !$exists ) { + $data = array( + 'userid' => $user, + 'appid' => $app, + 'configkey' => $key, + 'configvalue' => $value, + ); + $this->conn->insert('*PREFIX*preferences', $data); + } else { + $data = array( + 'configvalue' => $value, + ); + $where = array( + 'userid' => $user, + 'appid' => $app, + 'configkey' => $key, + ); + $this->conn->update('*PREFIX*preferences', $data, $where); + } + } + + /** + * @brief Deletes a key + * @param string $user user + * @param string $app app + * @param string $key key + * + * Deletes a key. + */ + public function deleteKey( $user, $app, $key ) { + $where = array( + 'userid' => $user, + 'appid' => $app, + 'configkey' => $key, + ); + $this->conn->delete('*PREFIX*preferences', $where); + } + + /** + * @brief Remove app of user from preferences + * @param string $user user + * @param string $app app + * + * Removes all keys in preferences belonging to the app and the user. + */ + public function deleteApp( $user, $app ) { + $where = array( + 'userid' => $user, + 'appid' => $app, + ); + $this->conn->delete('*PREFIX*preferences', $where); + } + + /** + * @brief Remove user from preferences + * @param string $user user + * + * Removes all keys in preferences belonging to the user. + */ + public function deleteUser( $user ) { + $where = array( + 'userid' => $user, + ); + $this->conn->delete('*PREFIX*preferences', $where); + } + + /** + * @brief Remove app from all users + * @param string $app app + * + * Removes all keys in preferences belonging to the app. + */ + public function deleteAppFromAllUsers( $app ) { + $where = array( + 'appid' => $app, + ); + $this->conn->delete('*PREFIX*preferences', $where); + } +} + +require_once __DIR__.'/legacy/'.basename(__FILE__); diff --git a/lib/private/preview.php b/lib/private/preview.php new file mode 100755 index 00000000000..266f7795f12 --- /dev/null +++ b/lib/private/preview.php @@ -0,0 +1,630 @@ +<?php +/** + * Copyright (c) 2013 Frank Karlitschek frank@owncloud.org + * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + * + * Thumbnails: + * structure of filename: + * /data/user/thumbnails/pathhash/x-y.png + * + */ +namespace OC; + +require_once 'preview/image.php'; +require_once 'preview/movies.php'; +require_once 'preview/mp3.php'; +require_once 'preview/pdf.php'; +require_once 'preview/svg.php'; +require_once 'preview/txt.php'; +require_once 'preview/unknown.php'; +require_once 'preview/office.php'; + +class Preview { + //the thumbnail folder + const THUMBNAILS_FOLDER = 'thumbnails'; + + //config + private $maxScaleFactor; + private $configMaxX; + private $configMaxY; + + //fileview object + private $fileView = null; + private $userView = null; + + //vars + private $file; + private $maxX; + private $maxY; + private $scalingup; + + //preview images object + /** + * @var \OC_Image + */ + private $preview; + + //preview providers + static private $providers = array(); + static private $registeredProviders = array(); + + /** + * @brief check if thumbnail or bigger version of thumbnail of file is cached + * @param string $user userid - if no user is given, OC_User::getUser will be used + * @param string $root path of root + * @param string $file The path to the file where you want a thumbnail from + * @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the shape of the image + * @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the shape of the image + * @param bool $scalingUp Disable/Enable upscaling of previews + * @return mixed (bool / string) + * false if thumbnail does not exist + * path to thumbnail if thumbnail exists + */ + public function __construct($user='', $root='/', $file='', $maxX=1, $maxY=1, $scalingUp=true) { + //set config + $this->configMaxX = \OC_Config::getValue('preview_max_x', null); + $this->configMaxY = \OC_Config::getValue('preview_max_y', null); + $this->maxScaleFactor = \OC_Config::getValue('preview_max_scale_factor', 2); + + //save parameters + $this->setFile($file); + $this->setMaxX($maxX); + $this->setMaxY($maxY); + $this->setScalingUp($scalingUp); + + //init fileviews + if($user === ''){ + $user = \OC_User::getUser(); + } + $this->fileView = new \OC\Files\View('/' . $user . '/' . $root); + $this->userView = new \OC\Files\View('/' . $user); + + $this->preview = null; + + //check if there are preview backends + if(empty(self::$providers)) { + self::initProviders(); + } + + if(empty(self::$providers)) { + \OC_Log::write('core', 'No preview providers exist', \OC_Log::ERROR); + throw new \Exception('No preview providers'); + } + } + + /** + * @brief returns the path of the file you want a thumbnail from + * @return string + */ + public function getFile() { + return $this->file; + } + + /** + * @brief returns the max width of the preview + * @return integer + */ + public function getMaxX() { + return $this->maxX; + } + + /** + * @brief returns the max height of the preview + * @return integer + */ + public function getMaxY() { + return $this->maxY; + } + + /** + * @brief returns whether or not scalingup is enabled + * @return bool + */ + public function getScalingUp() { + return $this->scalingup; + } + + /** + * @brief returns the name of the thumbnailfolder + * @return string + */ + public function getThumbnailsFolder() { + return self::THUMBNAILS_FOLDER; + } + + /** + * @brief returns the max scale factor + * @return integer + */ + public function getMaxScaleFactor() { + return $this->maxScaleFactor; + } + + /** + * @brief returns the max width set in ownCloud's config + * @return integer + */ + public function getConfigMaxX() { + return $this->configMaxX; + } + + /** + * @brief returns the max height set in ownCloud's config + * @return integer + */ + public function getConfigMaxY() { + return $this->configMaxY; + } + + /** + * @brief set the path of the file you want a thumbnail from + * @param string $file + * @return $this + */ + public function setFile($file) { + $this->file = $file; + return $this; + } + + /** + * @brief set the the max width of the preview + * @param int $maxX + * @return $this + */ + public function setMaxX($maxX=1) { + if($maxX <= 0) { + throw new \Exception('Cannot set width of 0 or smaller!'); + } + $configMaxX = $this->getConfigMaxX(); + if(!is_null($configMaxX)) { + if($maxX > $configMaxX) { + \OC_Log::write('core', 'maxX reduced from ' . $maxX . ' to ' . $configMaxX, \OC_Log::DEBUG); + $maxX = $configMaxX; + } + } + $this->maxX = $maxX; + return $this; + } + + /** + * @brief set the the max height of the preview + * @param int $maxY + * @return $this + */ + public function setMaxY($maxY=1) { + if($maxY <= 0) { + throw new \Exception('Cannot set height of 0 or smaller!'); + } + $configMaxY = $this->getConfigMaxY(); + if(!is_null($configMaxY)) { + if($maxY > $configMaxY) { + \OC_Log::write('core', 'maxX reduced from ' . $maxY . ' to ' . $configMaxY, \OC_Log::DEBUG); + $maxY = $configMaxY; + } + } + $this->maxY = $maxY; + return $this; + } + + /** + * @brief set whether or not scalingup is enabled + * @param bool $scalingUp + * @return $this + */ + public function setScalingup($scalingUp) { + if($this->getMaxScaleFactor() === 1) { + $scalingUp = false; + } + $this->scalingup = $scalingUp; + return $this; + } + + /** + * @brief check if all parameters are valid + * @return bool + */ + public function isFileValid() { + $file = $this->getFile(); + if($file === '') { + \OC_Log::write('core', 'No filename passed', \OC_Log::DEBUG); + return false; + } + + if(!$this->fileView->file_exists($file)) { + \OC_Log::write('core', 'File:"' . $file . '" not found', \OC_Log::DEBUG); + return false; + } + + return true; + } + + /** + * @brief deletes previews of a file with specific x and y + * @return bool + */ + public function deletePreview() { + $file = $this->getFile(); + + $fileInfo = $this->fileView->getFileInfo($file); + $fileId = $fileInfo['fileid']; + + $previewPath = $this->getThumbnailsFolder() . '/' . $fileId . '/' . $this->getMaxX() . '-' . $this->getMaxY() . '.png'; + $this->userView->unlink($previewPath); + return !$this->userView->file_exists($previewPath); + } + + /** + * @brief deletes all previews of a file + * @return bool + */ + public function deleteAllPreviews() { + $file = $this->getFile(); + + $fileInfo = $this->fileView->getFileInfo($file); + $fileId = $fileInfo['fileid']; + + $previewPath = $this->getThumbnailsFolder() . '/' . $fileId . '/'; + $this->userView->deleteAll($previewPath); + $this->userView->rmdir($previewPath); + return !$this->userView->is_dir($previewPath); + } + + /** + * @brief check if thumbnail or bigger version of thumbnail of file is cached + * @return mixed (bool / string) + * false if thumbnail does not exist + * path to thumbnail if thumbnail exists + */ + private function isCached() { + $file = $this->getFile(); + $maxX = $this->getMaxX(); + $maxY = $this->getMaxY(); + $scalingUp = $this->getScalingUp(); + $maxScaleFactor = $this->getMaxScaleFactor(); + + $fileInfo = $this->fileView->getFileInfo($file); + $fileId = $fileInfo['fileid']; + + if(is_null($fileId)) { + return false; + } + + $previewPath = $this->getThumbnailsFolder() . '/' . $fileId . '/'; + if(!$this->userView->is_dir($previewPath)) { + return false; + } + + //does a preview with the wanted height and width already exist? + if($this->userView->file_exists($previewPath . $maxX . '-' . $maxY . '.png')) { + return $previewPath . $maxX . '-' . $maxY . '.png'; + } + + $wantedAspectRatio = (float) ($maxX / $maxY); + + //array for usable cached thumbnails + $possibleThumbnails = array(); + + $allThumbnails = $this->userView->getDirectoryContent($previewPath); + foreach($allThumbnails as $thumbnail) { + $name = rtrim($thumbnail['name'], '.png'); + $size = explode('-', $name); + $x = (int) $size[0]; + $y = (int) $size[1]; + + $aspectRatio = (float) ($x / $y); + if($aspectRatio !== $wantedAspectRatio) { + continue; + } + + if($x < $maxX || $y < $maxY) { + if($scalingUp) { + $scalefactor = $maxX / $x; + if($scalefactor > $maxScaleFactor) { + continue; + } + }else{ + continue; + } + } + $possibleThumbnails[$x] = $thumbnail['path']; + } + + if(count($possibleThumbnails) === 0) { + return false; + } + + if(count($possibleThumbnails) === 1) { + return current($possibleThumbnails); + } + + ksort($possibleThumbnails); + + if(key(reset($possibleThumbnails)) > $maxX) { + return current(reset($possibleThumbnails)); + } + + if(key(end($possibleThumbnails)) < $maxX) { + return current(end($possibleThumbnails)); + } + + foreach($possibleThumbnails as $width => $path) { + if($width < $maxX) { + continue; + }else{ + return $path; + } + } + } + + /** + * @brief return a preview of a file + * @return \OC_Image + */ + public function getPreview() { + if(!is_null($this->preview) && $this->preview->valid()){ + return $this->preview; + } + + $this->preview = null; + $file = $this->getFile(); + $maxX = $this->getMaxX(); + $maxY = $this->getMaxY(); + $scalingUp = $this->getScalingUp(); + + $fileInfo = $this->fileView->getFileInfo($file); + $fileId = $fileInfo['fileid']; + + $cached = $this->isCached(); + + if($cached) { + $image = new \OC_Image($this->userView->file_get_contents($cached, 'r')); + $this->preview = $image->valid() ? $image : null; + $this->resizeAndCrop(); + } + + if(is_null($this->preview)) { + $mimetype = $this->fileView->getMimeType($file); + $preview = null; + + foreach(self::$providers as $supportedMimetype => $provider) { + if(!preg_match($supportedMimetype, $mimetype)) { + continue; + } + + \OC_Log::write('core', 'Generating preview for "' . $file . '" with "' . get_class($provider) . '"', \OC_Log::DEBUG); + + $preview = $provider->getThumbnail($file, $maxX, $maxY, $scalingUp, $this->fileView); + + if(!($preview instanceof \OC_Image)) { + continue; + } + + $this->preview = $preview; + $this->resizeAndCrop(); + + $previewPath = $this->getThumbnailsFolder() . '/' . $fileId . '/'; + $cachePath = $previewPath . $maxX . '-' . $maxY . '.png'; + + if($this->userView->is_dir($this->getThumbnailsFolder() . '/') === false) { + $this->userView->mkdir($this->getThumbnailsFolder() . '/'); + } + + if($this->userView->is_dir($previewPath) === false) { + $this->userView->mkdir($previewPath); + } + + $this->userView->file_put_contents($cachePath, $preview->data()); + + break; + } + } + + if(is_null($this->preview)) { + $this->preview = new \OC_Image(); + } + + return $this->preview; + } + + /** + * @brief show preview + * @return void + */ + public function showPreview() { + \OCP\Response::enableCaching(3600 * 24); // 24 hours + if(is_null($this->preview)) { + $this->getPreview(); + } + $this->preview->show(); + return; + } + + /** + * @brief show preview + * @return void + */ + public function show() { + $this->showPreview(); + return; + } + + /** + * @brief resize, crop and fix orientation + * @return void + */ + private function resizeAndCrop() { + $image = $this->preview; + $x = $this->getMaxX(); + $y = $this->getMaxY(); + $scalingUp = $this->getScalingUp(); + $maxscalefactor = $this->getMaxScaleFactor(); + + if(!($image instanceof \OC_Image)) { + \OC_Log::write('core', '$this->preview is not an instance of OC_Image', \OC_Log::DEBUG); + return; + } + + $image->fixOrientation(); + + $realx = (int) $image->width(); + $realy = (int) $image->height(); + + if($x === $realx && $y === $realy) { + $this->preview = $image; + return; + } + + $factorX = $x / $realx; + $factorY = $y / $realy; + + if($factorX >= $factorY) { + $factor = $factorX; + }else{ + $factor = $factorY; + } + + if($scalingUp === false) { + if($factor > 1) { + $factor = 1; + } + } + + if(!is_null($maxscalefactor)) { + if($factor > $maxscalefactor) { + \OC_Log::write('core', 'scalefactor reduced from ' . $factor . ' to ' . $maxscalefactor, \OC_Log::DEBUG); + $factor = $maxscalefactor; + } + } + + $newXsize = (int) ($realx * $factor); + $newYsize = (int) ($realy * $factor); + + $image->preciseResize($newXsize, $newYsize); + + if($newXsize === $x && $newYsize === $y) { + $this->preview = $image; + return; + } + + if($newXsize >= $x && $newYsize >= $y) { + $cropX = floor(abs($x - $newXsize) * 0.5); + //don't crop previews on the Y axis, this sucks if it's a document. + //$cropY = floor(abs($y - $newYsize) * 0.5); + $cropY = 0; + + $image->crop($cropX, $cropY, $x, $y); + + $this->preview = $image; + return; + } + + if($newXsize < $x || $newYsize < $y) { + if($newXsize > $x) { + $cropX = floor(($newXsize - $x) * 0.5); + $image->crop($cropX, 0, $x, $newYsize); + } + + if($newYsize > $y) { + $cropY = floor(($newYsize - $y) * 0.5); + $image->crop(0, $cropY, $newXsize, $y); + } + + $newXsize = (int) $image->width(); + $newYsize = (int) $image->height(); + + //create transparent background layer + $backgroundlayer = imagecreatetruecolor($x, $y); + $white = imagecolorallocate($backgroundlayer, 255, 255, 255); + imagefill($backgroundlayer, 0, 0, $white); + + $image = $image->resource(); + + $mergeX = floor(abs($x - $newXsize) * 0.5); + $mergeY = floor(abs($y - $newYsize) * 0.5); + + imagecopy($backgroundlayer, $image, $mergeX, $mergeY, 0, 0, $newXsize, $newYsize); + + //$black = imagecolorallocate(0,0,0); + //imagecolortransparent($transparentlayer, $black); + + $image = new \OC_Image($backgroundlayer); + + $this->preview = $image; + return; + } + } + + /** + * @brief register a new preview provider to be used + * @param string $provider class name of a Preview_Provider + * @param array $options + * @return void + */ + public static function registerProvider($class, $options=array()) { + self::$registeredProviders[]=array('class'=>$class, 'options'=>$options); + } + + /** + * @brief create instances of all the registered preview providers + * @return void + */ + private static function initProviders() { + if(!\OC_Config::getValue('enable_previews', true)) { + $provider = new Preview\Unknown(array()); + self::$providers = array($provider->getMimeType() => $provider); + return; + } + + if(count(self::$providers)>0) { + return; + } + + foreach(self::$registeredProviders as $provider) { + $class=$provider['class']; + $options=$provider['options']; + + $object = new $class($options); + + self::$providers[$object->getMimeType()] = $object; + } + + $keys = array_map('strlen', array_keys(self::$providers)); + array_multisort($keys, SORT_DESC, self::$providers); + } + + public static function post_write($args) { + self::post_delete($args); + } + + public static function post_delete($args) { + $path = $args['path']; + if(substr($path, 0, 1) === '/') { + $path = substr($path, 1); + } + $preview = new Preview(\OC_User::getUser(), 'files/', $path); + $preview->deleteAllPreviews(); + } + + public static function isMimeSupported($mimetype) { + if(!\OC_Config::getValue('enable_previews', true)) { + return false; + } + + //check if there are preview backends + if(empty(self::$providers)) { + self::initProviders(); + } + + //remove last element because it has the mimetype * + $providers = array_slice(self::$providers, 0, -1); + foreach($providers as $supportedMimetype => $provider) { + if(preg_match($supportedMimetype, $mimetype)) { + return true; + } + } + return false; + } +} diff --git a/lib/private/preview/image.php b/lib/private/preview/image.php new file mode 100644 index 00000000000..9aec967282d --- /dev/null +++ b/lib/private/preview/image.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright (c) 2013 Frank Karlitschek frank@owncloud.org + * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace OC\Preview; + +class Image extends Provider { + + public function getMimeType() { + return '/image\/.*/'; + } + + public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) { + //get fileinfo + $fileInfo = $fileview->getFileInfo($path); + if(!$fileInfo) { + return false; + } + + //check if file is encrypted + if($fileInfo['encrypted'] === true) { + $image = new \OC_Image(stream_get_contents($fileview->fopen($path, 'r'))); + }else{ + $image = new \OC_Image(); + $image->loadFromFile($fileview->getLocalFile($path)); + } + + return $image->valid() ? $image : false; + } +} + +\OC\Preview::registerProvider('OC\Preview\Image');
\ No newline at end of file diff --git a/lib/private/preview/movies.php b/lib/private/preview/movies.php new file mode 100644 index 00000000000..c318137ff0e --- /dev/null +++ b/lib/private/preview/movies.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright (c) 2013 Frank Karlitschek frank@owncloud.org + * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace OC\Preview; + +$isShellExecEnabled = !in_array('shell_exec', explode(', ', ini_get('disable_functions'))); +$whichAVCONV = shell_exec('which avconv'); +$isAVCONVAvailable = !empty($whichAVCONV); + +if($isShellExecEnabled && $isAVCONVAvailable) { + + class Movie extends Provider { + + public function getMimeType() { + return '/video\/.*/'; + } + + public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) { + $absPath = \OC_Helper::tmpFile(); + $tmpPath = \OC_Helper::tmpFile(); + + $handle = $fileview->fopen($path, 'rb'); + + $firstmb = stream_get_contents($handle, 1048576); //1024 * 1024 = 1048576 + file_put_contents($absPath, $firstmb); + + //$cmd = 'ffmpeg -y -i ' . escapeshellarg($absPath) . ' -f mjpeg -vframes 1 -ss 1 -s ' . escapeshellarg($maxX) . 'x' . escapeshellarg($maxY) . ' ' . $tmpPath; + $cmd = 'avconv -an -y -ss 1 -i ' . escapeshellarg($absPath) . ' -f mjpeg -vframes 1 ' . escapeshellarg($tmpPath); + + shell_exec($cmd); + + $image = new \OC_Image($tmpPath); + + unlink($absPath); + unlink($tmpPath); + + return $image->valid() ? $image : false; + } + } + + \OC\Preview::registerProvider('OC\Preview\Movie'); +}
\ No newline at end of file diff --git a/lib/private/preview/mp3.php b/lib/private/preview/mp3.php new file mode 100644 index 00000000000..1eed566315c --- /dev/null +++ b/lib/private/preview/mp3.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace OC\Preview; + +class MP3 extends Provider { + + public function getMimeType() { + return '/audio\/mpeg/'; + } + + public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) { + require_once('getid3/getid3.php'); + + $getID3 = new \getID3(); + + $tmpPath = $fileview->toTmpFile($path); + + $tags = $getID3->analyze($tmpPath); + \getid3_lib::CopyTagsToComments($tags); + if(isset($tags['id3v2']['APIC'][0]['data'])) { + $picture = @$tags['id3v2']['APIC'][0]['data']; + unlink($tmpPath); + $image = new \OC_Image($picture); + return $image->valid() ? $image : $this->getNoCoverThumbnail(); + } + + return $this->getNoCoverThumbnail(); + } + + private function getNoCoverThumbnail() { + $icon = \OC::$SERVERROOT . '/core/img/filetypes/audio.png'; + + if(!file_exists($icon)) { + return false; + } + + $image = new \OC_Image($icon); + return $image->valid() ? $image : false; + } + +} + +\OC\Preview::registerProvider('OC\Preview\MP3');
\ No newline at end of file diff --git a/lib/private/preview/office-cl.php b/lib/private/preview/office-cl.php new file mode 100644 index 00000000000..112909d6523 --- /dev/null +++ b/lib/private/preview/office-cl.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace OC\Preview; + +//we need imagick to convert +class Office extends Provider { + + private $cmd; + + public function getMimeType() { + return null; + } + + public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) { + $this->initCmd(); + if(is_null($this->cmd)) { + return false; + } + + $absPath = $fileview->toTmpFile($path); + + $tmpDir = get_temp_dir(); + + $defaultParameters = ' --headless --nologo --nofirststartwizard --invisible --norestore -convert-to pdf -outdir '; + $clParameters = \OCP\Config::getSystemValue('preview_office_cl_parameters', $defaultParameters); + + $exec = $this->cmd . $clParameters . escapeshellarg($tmpDir) . ' ' . escapeshellarg($absPath); + $export = 'export HOME=/' . $tmpDir; + + shell_exec($export . "\n" . $exec); + + //create imagick object from pdf + try{ + $pdf = new \imagick($absPath . '.pdf' . '[0]'); + $pdf->setImageFormat('jpg'); + } catch (\Exception $e) { + unlink($absPath); + unlink($absPath . '.pdf'); + \OC_Log::write('core', $e->getmessage(), \OC_Log::ERROR); + return false; + } + + $image = new \OC_Image($pdf); + + unlink($absPath); + unlink($absPath . '.pdf'); + + return $image->valid() ? $image : false; + } + + private function initCmd() { + $cmd = ''; + + if(is_string(\OC_Config::getValue('preview_libreoffice_path', null))) { + $cmd = \OC_Config::getValue('preview_libreoffice_path', null); + } + + $whichLibreOffice = shell_exec('which libreoffice'); + if($cmd === '' && !empty($whichLibreOffice)) { + $cmd = 'libreoffice'; + } + + $whichOpenOffice = shell_exec('which openoffice'); + if($cmd === '' && !empty($whichOpenOffice)) { + $cmd = 'openoffice'; + } + + if($cmd === '') { + $cmd = null; + } + + $this->cmd = $cmd; + } +} + +//.doc, .dot +class MSOfficeDoc extends Office { + + public function getMimeType() { + return '/application\/msword/'; + } + +} + +\OC\Preview::registerProvider('OC\Preview\MSOfficeDoc'); + +//.docm, .dotm, .xls(m), .xlt(m), .xla(m), .ppt(m), .pot(m), .pps(m), .ppa(m) +class MSOffice2003 extends Office { + + public function getMimeType() { + return '/application\/vnd.ms-.*/'; + } + +} + +\OC\Preview::registerProvider('OC\Preview\MSOffice2003'); + +//.docx, .dotx, .xlsx, .xltx, .pptx, .potx, .ppsx +class MSOffice2007 extends Office { + + public function getMimeType() { + return '/application\/vnd.openxmlformats-officedocument.*/'; + } + +} + +\OC\Preview::registerProvider('OC\Preview\MSOffice2007'); + +//.odt, .ott, .oth, .odm, .odg, .otg, .odp, .otp, .ods, .ots, .odc, .odf, .odb, .odi, .oxt +class OpenDocument extends Office { + + public function getMimeType() { + return '/application\/vnd.oasis.opendocument.*/'; + } + +} + +\OC\Preview::registerProvider('OC\Preview\OpenDocument'); + +//.sxw, .stw, .sxc, .stc, .sxd, .std, .sxi, .sti, .sxg, .sxm +class StarOffice extends Office { + + public function getMimeType() { + return '/application\/vnd.sun.xml.*/'; + } + +} + +\OC\Preview::registerProvider('OC\Preview\StarOffice');
\ No newline at end of file diff --git a/lib/private/preview/office-fallback.php b/lib/private/preview/office-fallback.php new file mode 100644 index 00000000000..e69ab0ab8cb --- /dev/null +++ b/lib/private/preview/office-fallback.php @@ -0,0 +1,142 @@ +<?php +/** + * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace OC\Preview; + +/* //There is no (good) php-only solution for converting 2003 word documents to pdfs / pngs ... +class DOC extends Provider { + + public function getMimeType() { + return '/application\/msword/'; + } + + public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) { + require_once(''); + } + +} + +\OC\Preview::registerProvider('OC\Preview\DOC'); +*/ + +class DOCX extends Provider { + + public function getMimeType() { + return '/application\/vnd.openxmlformats-officedocument.wordprocessingml.document/'; + } + + public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) { + require_once('phpdocx/classes/TransformDoc.inc'); + + $tmpDoc = $fileview->toTmpFile($path); + + $transformdoc = new \TransformDoc(); + $transformdoc->setStrFile($tmpDoc); + $transformdoc->generatePDF($tmpDoc); + + $pdf = new \imagick($tmpDoc . '[0]'); + $pdf->setImageFormat('jpg'); + + unlink($tmpDoc); + + $image = new \OC_Image($pdf); + + return $image->valid() ? $image : false; + } + +} + +\OC\Preview::registerProvider('OC\Preview\DOCX'); + +class MSOfficeExcel extends Provider { + + public function getMimeType() { + return null; + } + + public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) { + require_once('PHPExcel/Classes/PHPExcel.php'); + require_once('PHPExcel/Classes/PHPExcel/IOFactory.php'); + + $absPath = $fileview->toTmpFile($path); + $tmpPath = \OC_Helper::tmpFile(); + + $rendererName = \PHPExcel_Settings::PDF_RENDERER_DOMPDF; + $rendererLibraryPath = \OC::$THIRDPARTYROOT . '/3rdparty/dompdf'; + + \PHPExcel_Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + + $phpexcel = new \PHPExcel($absPath); + $excel = \PHPExcel_IOFactory::createWriter($phpexcel, 'PDF'); + $excel->save($tmpPath); + + $pdf = new \imagick($tmpPath . '[0]'); + $pdf->setImageFormat('jpg'); + + unlink($absPath); + unlink($tmpPath); + + $image = new \OC_Image($pdf); + + return $image->valid() ? $image : false; + } + +} + +class XLS extends MSOfficeExcel { + + public function getMimeType() { + return '/application\/vnd.ms-excel/'; + } + +} + +\OC\Preview::registerProvider('OC\Preview\XLS'); + +class XLSX extends MSOfficeExcel { + + public function getMimeType() { + return '/application\/vnd.openxmlformats-officedocument.spreadsheetml.sheet/'; + } + +} + +\OC\Preview::registerProvider('OC\Preview\XLSX'); + +/* //There is no (good) php-only solution for converting powerpoint documents to pdfs / pngs ... +class MSOfficePowerPoint extends Provider { + + public function getMimeType() { + return null; + } + + public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) { + return false; + } + +} + +class PPT extends MSOfficePowerPoint { + + public function getMimeType() { + return '/application\/vnd.ms-powerpoint/'; + } + +} + +\OC\Preview::registerProvider('OC\Preview\PPT'); + +class PPTX extends MSOfficePowerPoint { + + public function getMimeType() { + return '/application\/vnd.openxmlformats-officedocument.presentationml.presentation/'; + } + +} + +\OC\Preview::registerProvider('OC\Preview\PPTX'); +*/
\ No newline at end of file diff --git a/lib/private/preview/office.php b/lib/private/preview/office.php new file mode 100644 index 00000000000..5287bbd6ac1 --- /dev/null +++ b/lib/private/preview/office.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +//both, libreoffice backend and php fallback, need imagick +if (extension_loaded('imagick')) { + $isShellExecEnabled = !in_array('shell_exec', explode(', ', ini_get('disable_functions'))); + $whichLibreOffice = shell_exec('which libreoffice'); + $isLibreOfficeAvailable = !empty($whichLibreOffice); + $whichOpenOffice = shell_exec('which libreoffice'); + $isOpenOfficeAvailable = !empty($whichOpenOffice); + //let's see if there is libreoffice or openoffice on this machine + if($isShellExecEnabled && ($isLibreOfficeAvailable || $isOpenOfficeAvailable || is_string(\OC_Config::getValue('preview_libreoffice_path', null)))) { + require_once('office-cl.php'); + }else{ + //in case there isn't, use our fallback + require_once('office-fallback.php'); + } +}
\ No newline at end of file diff --git a/lib/private/preview/pdf.php b/lib/private/preview/pdf.php new file mode 100644 index 00000000000..cc974b68818 --- /dev/null +++ b/lib/private/preview/pdf.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace OC\Preview; + +if (extension_loaded('imagick')) { + + class PDF extends Provider { + + public function getMimeType() { + return '/application\/pdf/'; + } + + public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) { + $tmpPath = $fileview->toTmpFile($path); + + //create imagick object from pdf + try{ + $pdf = new \imagick($tmpPath . '[0]'); + $pdf->setImageFormat('jpg'); + } catch (\Exception $e) { + \OC_Log::write('core', $e->getmessage(), \OC_Log::ERROR); + return false; + } + + unlink($tmpPath); + + //new image object + $image = new \OC_Image($pdf); + //check if image object is valid + return $image->valid() ? $image : false; + } + } + + \OC\Preview::registerProvider('OC\Preview\PDF'); +} diff --git a/lib/private/preview/provider.php b/lib/private/preview/provider.php new file mode 100644 index 00000000000..e4a730bafc8 --- /dev/null +++ b/lib/private/preview/provider.php @@ -0,0 +1,19 @@ +<?php +namespace OC\Preview; + +abstract class Provider { + private $options; + + public function __construct($options) { + $this->options=$options; + } + + abstract public function getMimeType(); + + /** + * search for $query + * @param string $query + * @return + */ + abstract public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview); +} diff --git a/lib/private/preview/svg.php b/lib/private/preview/svg.php new file mode 100644 index 00000000000..b49e51720fa --- /dev/null +++ b/lib/private/preview/svg.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace OC\Preview; + +if (extension_loaded('imagick')) { + + class SVG extends Provider { + + public function getMimeType() { + return '/image\/svg\+xml/'; + } + + public function getThumbnail($path,$maxX,$maxY,$scalingup,$fileview) { + try{ + $svg = new \Imagick(); + $svg->setBackgroundColor(new \ImagickPixel('transparent')); + + $content = stream_get_contents($fileview->fopen($path, 'r')); + if(substr($content, 0, 5) !== '<?xml') { + $content = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' . $content; + } + + $svg->readImageBlob($content); + $svg->setImageFormat('png32'); + } catch (\Exception $e) { + \OC_Log::write('core', $e->getmessage(), \OC_Log::ERROR); + return false; + } + + + //new image object + $image = new \OC_Image(); + $image->loadFromData($svg); + //check if image object is valid + return $image->valid() ? $image : false; + } + } + + \OC\Preview::registerProvider('OC\Preview\SVG'); + +}
\ No newline at end of file diff --git a/lib/private/preview/txt.php b/lib/private/preview/txt.php new file mode 100644 index 00000000000..77e728eb364 --- /dev/null +++ b/lib/private/preview/txt.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace OC\Preview; + +class TXT extends Provider { + + private static $blacklist = array( + 'text/calendar', + 'text/vcard', + ); + + public function getMimeType() { + return '/text\/.*/'; + } + + public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) { + $mimetype = $fileview->getMimeType($path); + if(in_array($mimetype, self::$blacklist)) { + return false; + } + + $content = $fileview->fopen($path, 'r'); + $content = stream_get_contents($content); + + //don't create previews of empty text files + if(trim($content) === '') { + return false; + } + + $lines = preg_split("/\r\n|\n|\r/", $content); + + $fontSize = 5; //5px + $lineSize = ceil($fontSize * 1.25); + + $image = imagecreate($maxX, $maxY); + imagecolorallocate($image, 255, 255, 255); + $textColor = imagecolorallocate($image, 0, 0, 0); + + foreach($lines as $index => $line) { + $index = $index + 1; + + $x = (int) 1; + $y = (int) ($index * $lineSize) - $fontSize; + + imagestring($image, 1, $x, $y, $line, $textColor); + + if(($index * $lineSize) >= $maxY) { + break; + } + } + + $image = new \OC_Image($image); + + return $image->valid() ? $image : false; + } +} + +\OC\Preview::registerProvider('OC\Preview\TXT'); + +class PHP extends TXT { + + public function getMimeType() { + return '/application\/x-php/'; + } + +} + +\OC\Preview::registerProvider('OC\Preview\PHP'); + +class JavaScript extends TXT { + + public function getMimeType() { + return '/application\/javascript/'; + } + +} + +\OC\Preview::registerProvider('OC\Preview\JavaScript');
\ No newline at end of file diff --git a/lib/private/preview/unknown.php b/lib/private/preview/unknown.php new file mode 100644 index 00000000000..9e6cd68d401 --- /dev/null +++ b/lib/private/preview/unknown.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright (c) 2013 Frank Karlitschek frank@owncloud.org + * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace OC\Preview; + +class Unknown extends Provider { + + public function getMimeType() { + return '/.*/'; + } + + public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) { + $mimetype = $fileview->getMimeType($path); + + $path = \OC_Helper::mimetypeIcon($mimetype); + $path = \OC::$SERVERROOT . substr($path, strlen(\OC::$WEBROOT)); + + return new \OC_Image($path); + } +} + +\OC\Preview::registerProvider('OC\Preview\Unknown');
\ No newline at end of file diff --git a/lib/private/previewmanager.php b/lib/private/previewmanager.php new file mode 100755 index 00000000000..ac9a866a75b --- /dev/null +++ b/lib/private/previewmanager.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright (c) 2013 Thomas Müller thomas.mueller@tmit.eu + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + * + */ +namespace OC; + +use OCP\image; +use OCP\IPreview; + +class PreviewManager implements IPreview { + /** + * @brief return a preview of a file + * @param string $file The path to the file where you want a thumbnail from + * @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the shape of the image + * @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the shape of the image + * @param boolean $scaleUp Scale smaller images up to the thumbnail size or not. Might look ugly + * @return \OCP\Image + */ + function createPreview($file, $maxX = 100, $maxY = 75, $scaleUp = false) + { + $preview = new \OC\Preview('', '/', $file, $maxX, $maxY, $scaleUp); + return $preview->getPreview(); + } + + /** + * @brief returns true if the passed mime type is supported + * @param string $mimeType + * @return boolean + */ + function isMimeSupported($mimeType = '*') + { + return \OC\Preview::isMimeSupported($mimeType); + } +} diff --git a/lib/private/request.php b/lib/private/request.php new file mode 100755 index 00000000000..df33217f95d --- /dev/null +++ b/lib/private/request.php @@ -0,0 +1,184 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Request { + /** + * @brief Check overwrite condition + * @returns bool + */ + private static function isOverwriteCondition($type = '') { + $regex = '/' . OC_Config::getValue('overwritecondaddr', '') . '/'; + return $regex === '//' or preg_match($regex, $_SERVER['REMOTE_ADDR']) === 1 + or ($type !== 'protocol' and OC_Config::getValue('forcessl', false)); + } + + /** + * @brief Returns the server host + * @returns string the server host + * + * Returns the server host, even if the website uses one or more + * reverse proxies + */ + public static function serverHost() { + if(OC::$CLI) { + return 'localhost'; + } + if(OC_Config::getValue('overwritehost', '') !== '' and self::isOverwriteCondition()) { + return OC_Config::getValue('overwritehost'); + } + if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { + if (strpos($_SERVER['HTTP_X_FORWARDED_HOST'], ",") !== false) { + $host = trim(array_pop(explode(",", $_SERVER['HTTP_X_FORWARDED_HOST']))); + } + else{ + $host=$_SERVER['HTTP_X_FORWARDED_HOST']; + } + } + else{ + if (isset($_SERVER['HTTP_HOST'])) { + return $_SERVER['HTTP_HOST']; + } + if (isset($_SERVER['SERVER_NAME'])) { + return $_SERVER['SERVER_NAME']; + } + return 'localhost'; + } + return $host; + } + + + /** + * @brief Returns the server protocol + * @returns string the server protocol + * + * Returns the server protocol. It respects reverse proxy servers and load balancers + */ + public static function serverProtocol() { + if(OC_Config::getValue('overwriteprotocol', '') !== '' and self::isOverwriteCondition('protocol')) { + return OC_Config::getValue('overwriteprotocol'); + } + if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + $proto = strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']); + }else{ + if(isset($_SERVER['HTTPS']) and !empty($_SERVER['HTTPS']) and ($_SERVER['HTTPS']!='off')) { + $proto = 'https'; + }else{ + $proto = 'http'; + } + } + return $proto; + } + + /** + * @brief Returns the request uri + * @returns string the request uri + * + * Returns the request uri, even if the website uses one or more + * reverse proxies + */ + public static function requestUri() { + $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + if (OC_Config::getValue('overwritewebroot', '') !== '' and self::isOverwriteCondition()) { + $uri = self::scriptName() . substr($uri, strlen($_SERVER['SCRIPT_NAME'])); + } + return $uri; + } + + /** + * @brief Returns the script name + * @returns string the script name + * + * Returns the script name, even if the website uses one or more + * reverse proxies + */ + public static function scriptName() { + $name = $_SERVER['SCRIPT_NAME']; + if (OC_Config::getValue('overwritewebroot', '') !== '' and self::isOverwriteCondition()) { + $serverroot = str_replace("\\", '/', substr(__DIR__, 0, -4)); + $suburi = str_replace("\\", "/", substr(realpath($_SERVER["SCRIPT_FILENAME"]), strlen($serverroot))); + $name = OC_Config::getValue('overwritewebroot', '') . $suburi; + } + return $name; + } + + /** + * @brief get Path info from request + * @returns string Path info or false when not found + */ + public static function getPathInfo() { + if (array_key_exists('PATH_INFO', $_SERVER)) { + $path_info = $_SERVER['PATH_INFO']; + }else{ + $path_info = self::getRawPathInfo(); + // following is taken from Sabre_DAV_URLUtil::decodePathSegment + $path_info = rawurldecode($path_info); + $encoding = mb_detect_encoding($path_info, array('UTF-8', 'ISO-8859-1')); + + switch($encoding) { + + case 'ISO-8859-1' : + $path_info = utf8_encode($path_info); + + } + // end copy + } + return $path_info; + } + + /** + * @brief get Path info from request, not urldecoded + * @returns string Path info or false when not found + */ + public static function getRawPathInfo() { + $path_info = substr($_SERVER['REQUEST_URI'], strlen($_SERVER['SCRIPT_NAME'])); + // Remove the query string from REQUEST_URI + if ($pos = strpos($path_info, '?')) { + $path_info = substr($path_info, 0, $pos); + } + return $path_info; + } + + /** + * @brief Check if this is a no-cache request + * @returns boolean true for no-cache + */ + static public function isNoCache() { + if (!isset($_SERVER['HTTP_CACHE_CONTROL'])) { + return false; + } + return $_SERVER['HTTP_CACHE_CONTROL'] == 'no-cache'; + } + + /** + * @brief Check if the requestor understands gzip + * @returns boolean true for gzip encoding supported + */ + static public function acceptGZip() { + if (!isset($_SERVER['HTTP_ACCEPT_ENCODING'])) { + return false; + } + $HTTP_ACCEPT_ENCODING = $_SERVER["HTTP_ACCEPT_ENCODING"]; + if( strpos($HTTP_ACCEPT_ENCODING, 'x-gzip') !== false ) + return 'x-gzip'; + else if( strpos($HTTP_ACCEPT_ENCODING, 'gzip') !== false ) + return 'gzip'; + return false; + } + + /** + * @brief Check if the requester sent along an mtime + * @returns false or an mtime + */ + static public function hasModificationTime () { + if (isset($_SERVER['HTTP_X_OC_MTIME'])) { + return $_SERVER['HTTP_X_OC_MTIME']; + } else { + return false; + } + } +} diff --git a/lib/private/response.php b/lib/private/response.php new file mode 100644 index 00000000000..674176d078b --- /dev/null +++ b/lib/private/response.php @@ -0,0 +1,167 @@ +<?php +/** + * Copyright (c) 2011 Bart Visscher bartv@thisnet.nl + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Response { + const STATUS_FOUND = 304; + const STATUS_NOT_MODIFIED = 304; + const STATUS_TEMPORARY_REDIRECT = 307; + const STATUS_NOT_FOUND = 404; + const STATUS_INTERNAL_SERVER_ERROR = 500; + + /** + * @brief Enable response caching by sending correct HTTP headers + * @param $cache_time time to cache the response + * >0 cache time in seconds + * 0 and <0 enable default browser caching + * null cache indefinitly + */ + 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'); + } + + } + + /** + * @brief disable browser caching + * @see enableCaching with cache_time = 0 + */ + static public function disableCaching() { + self::enableCaching(0); + } + + /** + * @brief Set response status + * @param $status a HTTP status code, see also the STATUS constants + */ + static public function setStatus($status) { + $protocol = $_SERVER['SERVER_PROTOCOL']; + 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; + } + header($protocol.' '.$status); + } + + /** + * @brief Send redirect response + * @param $location to redirect to + */ + static public function redirect($location) { + self::setStatus(self::STATUS_TEMPORARY_REDIRECT); + header('Location: '.$location); + } + + /** + * @brief Set reponse expire time + * @param $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 $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 $lastModified time when the reponse 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); + } + + /** + * @brief Send file as response, checking and setting caching headers + * @param $filepath of file to send + */ + static public function sendFile($filepath) { + $fp = fopen($filepath, 'rb'); + if ($fp) { + self::setLastModifiedHeader(filemtime($filepath)); + self::setETagHeader(md5_file($filepath)); + + header('Content-Length: '.filesize($filepath)); + fpassthru($fp); + } + else { + self::setStatus(self::STATUS_NOT_FOUND); + } + } +} diff --git a/lib/private/route.php b/lib/private/route.php new file mode 100644 index 00000000000..5901717c094 --- /dev/null +++ b/lib/private/route.php @@ -0,0 +1,116 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +use Symfony\Component\Routing\Route; + +class OC_Route extends Route { + /** + * Specify the method when this route is to be used + * + * @param string $method HTTP method (uppercase) + */ + public function method($method) { + $this->setRequirement('_method', strtoupper($method)); + return $this; + } + + /** + * Specify POST as the method to use with this route + */ + public function post() { + $this->method('POST'); + return $this; + } + + /** + * Specify GET as the method to use with this route + */ + public function get() { + $this->method('GET'); + return $this; + } + + /** + * Specify PUT as the method to use with this route + */ + public function put() { + $this->method('PUT'); + return $this; + } + + /** + * Specify DELETE as the method to use with this route + */ + public function delete() { + $this->method('DELETE'); + return $this; + } + + /** + * Defaults to use for this route + * + * @param array $defaults The defaults + */ + public function defaults($defaults) { + $action = $this->getDefault('action'); + $this->setDefaults($defaults); + if (isset($defaults['action'])) { + $action = $defaults['action']; + } + $this->action($action); + return $this; + } + + /** + * Requirements for this route + * + * @param array $requirements The requirements + */ + public function requirements($requirements) { + $method = $this->getRequirement('_method'); + $this->setRequirements($requirements); + if (isset($requirements['_method'])) { + $method = $requirements['_method']; + } + if ($method) { + $this->method($method); + } + return $this; + } + + /** + * The action to execute when this route matches + * @param string|callable $class the class or a callable + * @param string $function the function to use with the class + * + * This function is called with $class set to a callable or + * to the class with $function + */ + public function action($class, $function = null) { + $action = array($class, $function); + if (is_null($function)) { + $action = $class; + } + $this->setDefault('action', $action); + return $this; + } + + /** + * The action to execute when this route matches, includes a file like + * it is called directly + * @param $file + */ + public function actionInclude($file) { + $function = create_function('$param', + 'unset($param["_route"]);' + .'$_GET=array_merge($_GET, $param);' + .'unset($param);' + .'require_once "'.$file.'";'); + $this->action($function); + } +} diff --git a/lib/private/router.php b/lib/private/router.php new file mode 100644 index 00000000000..dbaca9e0d5d --- /dev/null +++ b/lib/private/router.php @@ -0,0 +1,183 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RouteCollection; +//use Symfony\Component\Routing\Route; + +class OC_Router { + protected $collections = array(); + protected $collection = null; + protected $root = null; + + protected $generator = null; + protected $routing_files; + protected $cache_key; + + public function __construct() { + $baseUrl = OC_Helper::linkTo('', 'index.php'); + if ( !OC::$CLI) { + $method = $_SERVER['REQUEST_METHOD']; + }else{ + $method = 'GET'; + } + $host = OC_Request::serverHost(); + $schema = OC_Request::serverProtocol(); + $this->context = new RequestContext($baseUrl, $method, $host, $schema); + // TODO cache + $this->root = $this->getCollection('root'); + } + + public function getRoutingFiles() { + if (!isset($this->routing_files)) { + $this->routing_files = array(); + foreach(OC_APP::getEnabledApps() as $app) { + $file = OC_App::getAppPath($app).'/appinfo/routes.php'; + if(file_exists($file)) { + $this->routing_files[$app] = $file; + } + } + } + return $this->routing_files; + } + + public function getCacheKey() { + if (!isset($this->cache_key)) { + $files = $this->getRoutingFiles(); + $files[] = 'settings/routes.php'; + $files[] = 'core/routes.php'; + $files[] = 'ocs/routes.php'; + $this->cache_key = OC_Cache::generateCacheKeyFromFiles($files); + } + return $this->cache_key; + } + + /** + * loads the api routes + */ + public function loadRoutes() { + foreach($this->getRoutingFiles() as $app => $file) { + $this->useCollection($app); + require_once $file; + $collection = $this->getCollection($app); + $this->root->addCollection($collection, '/apps/'.$app); + } + $this->useCollection('root'); + require_once 'settings/routes.php'; + require_once 'core/routes.php'; + + // include ocs routes + require_once 'ocs/routes.php'; + $collection = $this->getCollection('ocs'); + $this->root->addCollection($collection, '/ocs'); + } + + protected function getCollection($name) { + if (!isset($this->collections[$name])) { + $this->collections[$name] = new RouteCollection(); + } + return $this->collections[$name]; + } + + /** + * Sets the collection to use for adding routes + * + * @param string $name Name of the colletion to use. + */ + public function useCollection($name) { + $this->collection = $this->getCollection($name); + } + + /** + * Create a OC_Route. + * + * @param string $name Name of the route to create. + * @param string $pattern The pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + */ + public function create($name, $pattern, array $defaults = array(), array $requirements = array()) { + $route = new OC_Route($pattern, $defaults, $requirements); + $this->collection->add($name, $route); + return $route; + } + + /** + * Find the route matching $url. + * + * @param string $url The url to find + */ + public function match($url) { + $matcher = new UrlMatcher($this->root, $this->context); + $parameters = $matcher->match($url); + if (isset($parameters['action'])) { + $action = $parameters['action']; + if (!is_callable($action)) { + var_dump($action); + throw new Exception('not a callable action'); + } + unset($parameters['action']); + call_user_func($action, $parameters); + } elseif (isset($parameters['file'])) { + include $parameters['file']; + } else { + throw new Exception('no action available'); + } + } + + /** + * Get the url generator + * + */ + public function getGenerator() + { + if (null !== $this->generator) { + return $this->generator; + } + + return $this->generator = new UrlGenerator($this->root, $this->context); + } + + /** + * Generate url based on $name and $parameters + * + * @param string $name Name of the route to use. + * @param array $parameters Parameters for the route + */ + public function generate($name, $parameters = array(), $absolute = false) + { + return $this->getGenerator()->generate($name, $parameters, $absolute); + } + + /** + * Generate JSON response for routing in javascript + */ + public static function JSRoutes() + { + $router = OC::getRouter(); + + $etag = $router->getCacheKey(); + OC_Response::enableCaching(); + OC_Response::setETagHeader($etag); + + $root = $router->getCollection('root'); + $routes = array(); + foreach($root->all() as $name => $route) { + $compiled_route = $route->compile(); + $defaults = $route->getDefaults(); + unset($defaults['action']); + $routes[$name] = array( + 'tokens' => $compiled_route->getTokens(), + 'defaults' => $defaults, + ); + } + OCP\JSON::success ( array( 'data' => $routes ) ); + } +} diff --git a/lib/private/search.php b/lib/private/search.php new file mode 100644 index 00000000000..b9c75dfc333 --- /dev/null +++ b/lib/private/search.php @@ -0,0 +1,90 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +/** + * provides an interface to all search providers + */ +class OC_Search{ + static private $providers=array(); + static private $registeredProviders=array(); + + /** + * remove all registered search providers + */ + public static function clearProviders() { + self::$providers=array(); + self::$registeredProviders=array(); + } + + /** + * register a new search provider to be used + * @param string $provider class name of a OC_Search_Provider + */ + public static function registerProvider($class, $options=array()) { + self::$registeredProviders[]=array('class'=>$class, 'options'=>$options); + } + + /** + * search all provider for $query + * @param string query + * @return array An array of OC_Search_Result's + */ + public static function search($query) { + self::initProviders(); + $results=array(); + foreach(self::$providers as $provider) { + $results=array_merge($results, $provider->search($query)); + } + return $results; + } + + /** + * remove an existing search provider + * @param string $provider class name of a OC_Search_Provider + */ + public static function removeProvider($provider) { + self::$registeredProviders = array_filter( + self::$registeredProviders, + function ($element) use ($provider) { + return ($element['class'] != $provider); + } + ); + // force regeneration of providers on next search + self::$providers=array(); + } + + + /** + * create instances of all the registered search providers + */ + private static function initProviders() { + if(count(self::$providers)>0) { + return; + } + foreach(self::$registeredProviders as $provider) { + $class=$provider['class']; + $options=$provider['options']; + self::$providers[]=new $class($options); + } + } +} diff --git a/lib/private/search/provider.php b/lib/private/search/provider.php new file mode 100644 index 00000000000..b617b9c5d94 --- /dev/null +++ b/lib/private/search/provider.php @@ -0,0 +1,18 @@ +<?php +/** + * provides search functionalty + */ +abstract class OC_Search_Provider { + private $options; + + public function __construct($options) { + $this->options=$options; + } + + /** + * search for $query + * @param string $query + * @return array An array of OC_Search_Result's + */ + abstract public function search($query); +} diff --git a/lib/private/search/provider/file.php b/lib/private/search/provider/file.php new file mode 100644 index 00000000000..9bd50931517 --- /dev/null +++ b/lib/private/search/provider/file.php @@ -0,0 +1,46 @@ +<?php + +class OC_Search_Provider_File extends OC_Search_Provider{ + function search($query) { + $files=\OC\Files\Filesystem::search($query, true); + $results=array(); + $l=OC_L10N::get('lib'); + foreach($files as $fileData) { + $path = $fileData['path']; + $mime = $fileData['mimetype']; + + $name = basename($path); + $container = dirname($path); + $text = ''; + $skip = false; + if($mime=='httpd/unix-directory') { + $link = OC_Helper::linkTo( 'files', 'index.php', array('dir' => $path)); + $type = (string)$l->t('Files'); + }else{ + $link = OC_Helper::linkToRoute( 'download', array('file' => $path)); + $mimeBase = $fileData['mimepart']; + switch($mimeBase) { + case 'audio': + $skip = true; + break; + case 'text': + $type = (string)$l->t('Text'); + break; + case 'image': + $type = (string)$l->t('Images'); + break; + default: + if($mime=='application/xml') { + $type = (string)$l->t('Text'); + }else{ + $type = (string)$l->t('Files'); + } + } + } + if(!$skip) { + $results[] = new OC_Search_Result($name, $text, $link, $type, $container); + } + } + return $results; + } +} diff --git a/lib/private/search/result.php b/lib/private/search/result.php new file mode 100644 index 00000000000..42275c2df11 --- /dev/null +++ b/lib/private/search/result.php @@ -0,0 +1,26 @@ +<?php +/** + * a result of a search + */ +class OC_Search_Result{ + public $name; + public $text; + public $link; + public $type; + public $container; + + /** + * create a new search result + * @param string $name short name for the result + * @param string $text some more information about the result + * @param string $link link for the result + * @param string $type the type of result as human readable string ('File', 'Music', etc) + */ + public function __construct($name, $text, $link, $type, $container) { + $this->name=$name; + $this->text=$text; + $this->link=$link; + $this->type=$type; + $this->container=$container; + } +} diff --git a/lib/private/server.php b/lib/private/server.php new file mode 100644 index 00000000000..cabb15324ec --- /dev/null +++ b/lib/private/server.php @@ -0,0 +1,255 @@ +<?php + +namespace OC; + +use OC\AppFramework\Http\Request; +use OC\AppFramework\Utility\SimpleContainer; +use OC\Cache\UserCache; +use OC\Files\Node\Root; +use OC\Files\View; +use OCP\IServerContainer; + +/** + * Class Server + * @package OC + * + * TODO: hookup all manager classes + */ +class Server extends SimpleContainer implements IServerContainer { + + function __construct() { + $this->registerService('ContactsManager', function($c) { + return new ContactsManager(); + }); + $this->registerService('Request', function($c) { + $params = array(); + + // we json decode the body only in case of content type json + if (isset($_SERVER['CONTENT_TYPE']) && stripos($_SERVER['CONTENT_TYPE'],'json') !== false ) { + $params = json_decode(file_get_contents('php://input'), true); + $params = is_array($params) ? $params: array(); + } + + return new Request( + array( + 'get' => $_GET, + 'post' => $_POST, + 'files' => $_FILES, + 'server' => $_SERVER, + 'env' => $_ENV, + 'cookies' => $_COOKIE, + 'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD'])) + ? $_SERVER['REQUEST_METHOD'] + : null, + 'params' => $params, + 'urlParams' => $c['urlParams'] + ) + ); + }); + $this->registerService('PreviewManager', function($c) { + return new PreviewManager(); + }); + $this->registerService('TagManager', function($c) { + $user = \OC_User::getUser(); + return new TagManager($user); + }); + $this->registerService('RootFolder', function($c) { + // TODO: get user and user manager from container as well + $user = \OC_User::getUser(); + /** @var $c SimpleContainer */ + $userManager = $c->query('UserManager'); + $user = $userManager->get($user); + $manager = \OC\Files\Filesystem::getMountManager(); + $view = new View(); + return new Root($manager, $view, $user); + }); + $this->registerService('UserManager', function($c) { + return new \OC\User\Manager(); + }); + $this->registerService('UserSession', function($c) { + /** @var $c SimpleContainer */ + $manager = $c->query('UserManager'); + $userSession = new \OC\User\Session($manager, \OC::$session); + $userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) { + \OC_Hook::emit('OC_User', 'pre_createUser', array('run' => true, 'uid' => $uid, 'password' => $password)); + }); + $userSession->listen('\OC\User', 'postCreateUser', function ($user, $password) { + /** @var $user \OC\User\User */ + \OC_Hook::emit('OC_User', 'post_createUser', array('uid' => $user->getUID(), 'password' => $password)); + }); + $userSession->listen('\OC\User', 'preDelete', function ($user) { + /** @var $user \OC\User\User */ + \OC_Hook::emit('OC_User', 'pre_deleteUser', array('run' => true, 'uid' => $user->getUID())); + }); + $userSession->listen('\OC\User', 'postDelete', function ($user) { + /** @var $user \OC\User\User */ + \OC_Hook::emit('OC_User', 'post_deleteUser', array('uid' => $user->getUID())); + }); + $userSession->listen('\OC\User', 'preSetPassword', function ($user, $password, $recoveryPassword) { + /** @var $user \OC\User\User */ + \OC_Hook::emit('OC_User', 'pre_setPassword', array('run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword)); + }); + $userSession->listen('\OC\User', 'postSetPassword', function ($user, $password, $recoveryPassword) { + /** @var $user \OC\User\User */ + \OC_Hook::emit('OC_User', 'post_setPassword', array('run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword)); + }); + $userSession->listen('\OC\User', 'preLogin', function ($uid, $password) { + \OC_Hook::emit('OC_User', 'pre_login', array('run' => true, 'uid' => $uid, 'password' => $password)); + }); + $userSession->listen('\OC\User', 'postLogin', function ($user, $password) { + /** @var $user \OC\User\User */ + \OC_Hook::emit('OC_User', 'post_login', array('run' => true, 'uid' => $user->getUID(), 'password' => $password)); + }); + $userSession->listen('\OC\User', 'logout', function () { + \OC_Hook::emit('OC_User', 'logout', array()); + }); + return $userSession; + }); + $this->registerService('NavigationManager', function($c) { + return new \OC\NavigationManager(); + }); + $this->registerService('AllConfig', function($c) { + return new \OC\AllConfig(); + }); + $this->registerService('UserCache', function($c) { + return new UserCache(); + }); + } + + /** + * @return \OCP\Contacts\IManager + */ + function getContactsManager() { + return $this->query('ContactsManager'); + } + + /** + * The current request object holding all information about the request + * currently being processed is returned from this method. + * In case the current execution was not initiated by a web request null is returned + * + * @return \OCP\IRequest|null + */ + function getRequest() { + return $this->query('Request'); + } + + /** + * Returns the preview manager which can create preview images for a given file + * + * @return \OCP\IPreview + */ + function getPreviewManager() { + return $this->query('PreviewManager'); + } + + /** + * Returns the tag manager which can get and set tags for different object types + * + * @see \OCP\ITagManager::load() + * @return \OCP\ITagManager + */ + function getTagManager() { + return $this->query('TagManager'); + } + + /** + * Returns the root folder of ownCloud's data directory + * + * @return \OCP\Files\Folder + */ + function getRootFolder() { + return $this->query('RootFolder'); + } + + /** + * Returns a view to ownCloud's files folder + * + * @return \OCP\Files\Folder + */ + function getUserFolder() { + + $dir = '/files'; + $root = $this->getRootFolder(); + $folder = null; + if(!$root->nodeExists($dir)) { + $folder = $root->newFolder($dir); + } else { + $folder = $root->get($dir); + } + return $folder; + } + + /** + * Returns an app-specific view in ownClouds data directory + * + * @return \OCP\Files\Folder + */ + function getAppFolder() { + + $dir = '/' . \OC_App::getCurrentApp(); + $root = $this->getRootFolder(); + $folder = null; + if(!$root->nodeExists($dir)) { + $folder = $root->newFolder($dir); + } else { + $folder = $root->get($dir); + } + return $folder; + } + + /** + * @return \OC\User\Manager + */ + function getUserManager() { + return $this->query('UserManager'); + } + + /** + * @return \OC\User\Session + */ + function getUserSession() { + return $this->query('UserSession'); + } + + /** + * @return \OC\NavigationManager + */ + function getNavigationManager() { + return $this->query('NavigationManager'); + } + + /** + * @return \OC\Config + */ + function getConfig() { + return $this->query('AllConfig'); + } + + /** + * Returns an ICache instance + * + * @return \OCP\ICache + */ + function getCache() { + return $this->query('UserCache'); + } + + /** + * Returns the current session + * + * @return \OCP\ISession + */ + function getSession() { + return \OC::$session; + } + + /** + * Returns the current session + * + * @return \OCP\IDBConnection + */ + function getDatabaseConnection() { + return \OC_DB::getConnection(); + } +} diff --git a/lib/private/session/internal.php b/lib/private/session/internal.php new file mode 100644 index 00000000000..60aecccc8aa --- /dev/null +++ b/lib/private/session/internal.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Session; + +/** + * Class Internal + * + * wrap php's internal session handling into the Session interface + * + * @package OC\Session + */ +class Internal extends Memory { + public function __construct($name) { + session_name($name); + session_start(); + if (!isset($_SESSION)) { + throw new \Exception('Failed to start session'); + } + $this->data = $_SESSION; + } + + public function __destruct() { + $_SESSION = $this->data; + session_write_close(); + } + + public function clear() { + session_unset(); + @session_regenerate_id(true); + @session_start(); + $this->data = $_SESSION = array(); + } +} diff --git a/lib/private/session/memory.php b/lib/private/session/memory.php new file mode 100644 index 00000000000..c148ff4b9b9 --- /dev/null +++ b/lib/private/session/memory.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Session; + +/** + * Class Internal + * + * store session data in an in-memory array, not persistance + * + * @package OC\Session + */ +class Memory extends Session { + protected $data; + + public function __construct($name) { + //no need to use $name since all data is already scoped to this instance + $this->data = array(); + } + + /** + * @param string $key + * @param mixed $value + */ + public function set($key, $value) { + $this->data[$key] = $value; + } + + /** + * @param string $key + * @return mixed + */ + public function get($key) { + if (!$this->exists($key)) { + return null; + } + return $this->data[$key]; + } + + /** + * @param string $key + * @return bool + */ + public function exists($key) { + return isset($this->data[$key]); + } + + /** + * @param string $key + */ + public function remove($key) { + unset($this->data[$key]); + } + + public function clear() { + $this->data = array(); + } +} diff --git a/lib/private/session/session.php b/lib/private/session/session.php new file mode 100644 index 00000000000..c55001eccac --- /dev/null +++ b/lib/private/session/session.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Session; + +abstract class Session implements \ArrayAccess, \OCP\ISession { + /** + * $name serves as a namespace for the session keys + * + * @param string $name + */ + abstract public function __construct($name); + + /** + * @param string $key + * @param mixed $value + */ + abstract public function set($key, $value); + + /** + * @param string $key + * @return mixed should return null if $key does not exist + */ + abstract public function get($key); + + /** + * @param string $key + * @return bool + */ + abstract public function exists($key); + + /** + * should not throw any errors if $key does not exist + * + * @param string $key + */ + abstract public function remove($key); + + /** + * removes all entries within the cache namespace + */ + abstract public function clear(); + + /** + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) { + return $this->exists($offset); + } + + /** + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) { + return $this->get($offset); + } + + /** + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) { + $this->set($offset, $value); + } + + /** + * @param mixed $offset + */ + public function offsetUnset($offset) { + $this->remove($offset); + } +} diff --git a/lib/private/setup.php b/lib/private/setup.php new file mode 100644 index 00000000000..6bf3c88370f --- /dev/null +++ b/lib/private/setup.php @@ -0,0 +1,192 @@ +<?php + +class DatabaseSetupException extends \OC\HintException +{ +} + +class OC_Setup { + static $dbSetupClasses = array( + 'mysql' => '\OC\Setup\MySQL', + 'pgsql' => '\OC\Setup\PostgreSQL', + 'oci' => '\OC\Setup\OCI', + 'mssql' => '\OC\Setup\MSSQL', + 'sqlite' => '\OC\Setup\Sqlite', + 'sqlite3' => '\OC\Setup\Sqlite', + ); + + public static function getTrans(){ + return OC_L10N::get('lib'); + } + + public static function install($options) { + $l = self::getTrans(); + + $error = array(); + $dbtype = $options['dbtype']; + + if(empty($options['adminlogin'])) { + $error[] = $l->t('Set an admin username.'); + } + if(empty($options['adminpass'])) { + $error[] = $l->t('Set an admin password.'); + } + if(empty($options['directory'])) { + $options['directory'] = OC::$SERVERROOT."/data"; + } + + if (!isset(self::$dbSetupClasses[$dbtype])) { + $dbtype = 'sqlite'; + } + + $class = self::$dbSetupClasses[$dbtype]; + $dbSetup = new $class(self::getTrans(), 'db_structure.xml'); + $error = array_merge($error, $dbSetup->validate($options)); + + if(count($error) != 0) { + return $error; + } + + //no errors, good + $username = htmlspecialchars_decode($options['adminlogin']); + $password = htmlspecialchars_decode($options['adminpass']); + $datadir = htmlspecialchars_decode($options['directory']); + + if (OC_Util::runningOnWindows()) { + $datadir = rtrim(realpath($datadir), '\\'); + } + + //use sqlite3 when available, otherise sqlite2 will be used. + if($dbtype=='sqlite' and class_exists('SQLite3')) { + $dbtype='sqlite3'; + } + + //generate a random salt that is used to salt the local user passwords + $salt = OC_Util::generateRandomBytes(30); + OC_Config::setValue('passwordsalt', $salt); + + //write the config file + OC_Config::setValue('datadirectory', $datadir); + OC_Config::setValue('dbtype', $dbtype); + OC_Config::setValue('version', implode('.', OC_Util::getVersion())); + try { + $dbSetup->initialize($options); + $dbSetup->setupDatabase($username); + } catch (DatabaseSetupException $e) { + $error[] = array( + 'error' => $e->getMessage(), + 'hint' => $e->getHint() + ); + return($error); + } catch (Exception $e) { + $error[] = array( + 'error' => 'Error while trying to create admin user: ' . $e->getMessage(), + 'hint' => '' + ); + return($error); + } + + //create the user and group + try { + OC_User::createUser($username, $password); + } + catch(Exception $exception) { + $error[] = $exception->getMessage(); + } + + if(count($error) == 0) { + OC_Appconfig::setValue('core', 'installedat', microtime(true)); + OC_Appconfig::setValue('core', 'lastupdatedat', microtime(true)); + OC_AppConfig::setValue('core', 'remote_core.css', '/core/minimizer.php'); + OC_AppConfig::setValue('core', 'remote_core.js', '/core/minimizer.php'); + + OC_Group::createGroup('admin'); + OC_Group::addToGroup($username, 'admin'); + OC_User::login($username, $password); + + //guess what this does + OC_Installer::installShippedApps(); + + //create htaccess files for apache hosts + if (isset($_SERVER['SERVER_SOFTWARE']) && strstr($_SERVER['SERVER_SOFTWARE'], 'Apache')) { + self::createHtaccess(); + } + + //and we are done + OC_Config::setValue('installed', true); + } + + return $error; + } + + /** + * create .htaccess files for apache hosts + */ + private static function createHtaccess() { + $content = "<IfModule mod_fcgid.c>\n"; + $content.= "<IfModule mod_setenvif.c>\n"; + $content.= "<IfModule mod_headers.c>\n"; + $content.= "SetEnvIfNoCase ^Authorization$ \"(.+)\" XAUTHORIZATION=$1\n"; + $content.= "RequestHeader set XAuthorization %{XAUTHORIZATION}e env=XAUTHORIZATION\n"; + $content.= "</IfModule>\n"; + $content.= "</IfModule>\n"; + $content.= "</IfModule>\n"; + $content.= "ErrorDocument 403 ".OC::$WEBROOT."/core/templates/403.php\n";//custom 403 error page + $content.= "ErrorDocument 404 ".OC::$WEBROOT."/core/templates/404.php\n";//custom 404 error page + $content.= "<IfModule mod_php5.c>\n"; + $content.= "php_value upload_max_filesize 512M\n";//upload limit + $content.= "php_value post_max_size 512M\n"; + $content.= "php_value memory_limit 512M\n"; + $content.= "php_value mbstring.func_overload 0\n"; + $content.= "<IfModule env_module>\n"; + $content.= " SetEnv htaccessWorking true\n"; + $content.= "</IfModule>\n"; + $content.= "</IfModule>\n"; + $content.= "<IfModule mod_rewrite.c>\n"; + $content.= "RewriteEngine on\n"; + $content.= "RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n"; + $content.= "RewriteRule ^.well-known/host-meta /public.php?service=host-meta [QSA,L]\n"; + $content.= "RewriteRule ^.well-known/carddav /remote.php/carddav/ [R]\n"; + $content.= "RewriteRule ^.well-known/caldav /remote.php/caldav/ [R]\n"; + $content.= "RewriteRule ^apps/([^/]*)/(.*\.(css|php))$ index.php?app=$1&getfile=$2 [QSA,L]\n"; + $content.= "RewriteRule ^remote/(.*) remote.php [QSA,L]\n"; + $content.= "</IfModule>\n"; + $content.= "<IfModule mod_mime.c>\n"; + $content.= "AddType image/svg+xml svg svgz\n"; + $content.= "AddEncoding gzip svgz\n"; + $content.= "</IfModule>\n"; + $content.= "<IfModule dir_module>\n"; + $content.= "DirectoryIndex index.php index.html\n"; + $content.= "</IfModule>\n"; + $content.= "AddDefaultCharset utf-8\n"; + $content.= "Options -Indexes\n"; + @file_put_contents(OC::$SERVERROOT.'/.htaccess', $content); //supress errors in case we don't have permissions for it + + self::protectDataDirectory(); + } + + public static function protectDataDirectory() { + $content = "deny from all\n"; + $content.= "IndexIgnore *"; + file_put_contents(OC_Config::getValue('datadirectory', OC::$SERVERROOT.'/data').'/.htaccess', $content); + file_put_contents(OC_Config::getValue('datadirectory', OC::$SERVERROOT.'/data').'/index.html', ''); + } + + /** + * @brief Post installation checks + */ + public static function postSetupCheck($params) { + // setup was successful -> webdav testing now + $l = self::getTrans(); + if (OC_Util::isWebDAVWorking()) { + header("Location: ".OC::$WEBROOT.'/'); + } else { + + $error = $l->t('Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken.'); + $hint = $l->t('Please double check the <a href=\'%s\'>installation guides</a>.', + 'http://doc.owncloud.org/server/5.0/admin_manual/installation.html'); + + OC_Template::printErrorPage($error, $hint); + exit(); + } + } +} diff --git a/lib/private/setup/abstractdatabase.php b/lib/private/setup/abstractdatabase.php new file mode 100644 index 00000000000..0beada7bd29 --- /dev/null +++ b/lib/private/setup/abstractdatabase.php @@ -0,0 +1,50 @@ +<?php + +namespace OC\Setup; + +abstract class AbstractDatabase { + protected $trans; + protected $dbDefinitionFile; + protected $dbuser; + protected $dbpassword; + protected $dbname; + protected $dbhost; + protected $tableprefix; + + public function __construct($trans, $dbDefinitionFile) { + $this->trans = $trans; + $this->dbDefinitionFile = $dbDefinitionFile; + } + + public function validate($config) { + $errors = array(); + if(empty($config['dbuser'])) { + $errors[] = $this->trans->t("%s enter the database username.", array($this->dbprettyname)); + } + if(empty($config['dbname'])) { + $errors[] = $this->trans->t("%s enter the database name.", array($this->dbprettyname)); + } + if(substr_count($config['dbname'], '.') >= 1) { + $errors[] = $this->trans->t("%s you may not use dots in the database name", array($this->dbprettyname)); + } + return $errors; + } + + public function initialize($config) { + $dbuser = $config['dbuser']; + $dbpass = $config['dbpass']; + $dbname = $config['dbname']; + $dbhost = !empty($config['dbhost']) ? $config['dbhost'] : 'localhost'; + $dbtableprefix = isset($config['dbtableprefix']) ? $config['dbtableprefix'] : 'oc_'; + + \OC_Config::setValue('dbname', $dbname); + \OC_Config::setValue('dbhost', $dbhost); + \OC_Config::setValue('dbtableprefix', $dbtableprefix); + + $this->dbuser = $dbuser; + $this->dbpassword = $dbpass; + $this->dbname = $dbname; + $this->dbhost = $dbhost; + $this->tableprefix = $dbtableprefix; + } +} diff --git a/lib/private/setup/mssql.php b/lib/private/setup/mssql.php new file mode 100644 index 00000000000..b8329f99079 --- /dev/null +++ b/lib/private/setup/mssql.php @@ -0,0 +1,182 @@ +<?php + +namespace OC\Setup; + +class MSSQL extends AbstractDatabase { + public $dbprettyname = 'MS SQL Server'; + + public function setupDatabase() { + //check if the database user has admin right + $masterConnectionInfo = array( "Database" => "master", "UID" => $this->dbuser, "PWD" => $this->dbpassword); + + $masterConnection = @sqlsrv_connect($this->dbhost, $masterConnectionInfo); + if(!$masterConnection) { + $entry = null; + if( ($errors = sqlsrv_errors() ) != null) { + $entry='DB Error: "'.print_r(sqlsrv_errors()).'"<br />'; + } else { + $entry = ''; + } + throw new \DatabaseSetupException($this->trans->t('MS SQL username and/or password not valid: %s', array($entry)), + $this->trans->t('You need to enter either an existing account or the administrator.')); + } + + \OC_Config::setValue('dbuser', $this->dbuser); + \OC_Config::setValue('dbpassword', $this->dbpassword); + + $this->createDBLogin($masterConnection); + + $this->createDatabase($masterConnection); + + $this->createDBUser($masterConnection); + + sqlsrv_close($masterConnection); + + $this->createDatabaseStructure(); + } + + private function createDBLogin($connection) { + $query = "SELECT * FROM master.sys.server_principals WHERE name = '".$this->dbuser."';"; + $result = sqlsrv_query($connection, $query); + if ($result === false) { + if ( ($errors = sqlsrv_errors() ) != null) { + $entry='DB Error: "'.print_r(sqlsrv_errors()).'"<br />'; + } else { + $entry = ''; + } + $entry.='Offending command was: '.$query.'<br />'; + \OC_Log::write('setup.mssql', $entry, \OC_Log::WARN); + } else { + $row = sqlsrv_fetch_array($result); + + if ($row === false) { + if ( ($errors = sqlsrv_errors() ) != null) { + $entry='DB Error: "'.print_r(sqlsrv_errors()).'"<br />'; + } else { + $entry = ''; + } + $entry.='Offending command was: '.$query.'<br />'; + \OC_Log::write('setup.mssql', $entry, \OC_Log::WARN); + } else { + if ($row == null) { + $query = "CREATE LOGIN [".$this->dbuser."] WITH PASSWORD = '".$this->dbpassword."';"; + $result = sqlsrv_query($connection, $query); + if (!$result or $result === false) { + if ( ($errors = sqlsrv_errors() ) != null) { + $entry='DB Error: "'.print_r(sqlsrv_errors()).'"<br />'; + } else { + $entry = ''; + } + $entry.='Offending command was: '.$query.'<br />'; + \OC_Log::write('setup.mssql', $entry, \OC_Log::WARN); + } + } + } + } + } + + private function createDBUser($connection) { + $query = "SELECT * FROM [".$this->dbname."].sys.database_principals WHERE name = '".$this->dbuser."';"; + $result = sqlsrv_query($connection, $query); + if ($result === false) { + if ( ($errors = sqlsrv_errors() ) != null) { + $entry='DB Error: "'.print_r(sqlsrv_errors()).'"<br />'; + } else { + $entry = ''; + } + $entry.='Offending command was: '.$query.'<br />'; + \OC_Log::write('setup.mssql', $entry, \OC_Log::WARN); + } else { + $row = sqlsrv_fetch_array($result); + + if ($row === false) { + if ( ($errors = sqlsrv_errors() ) != null) { + $entry='DB Error: "'.print_r(sqlsrv_errors()).'"<br />'; + } else { + $entry = ''; + } + $entry.='Offending command was: '.$query.'<br />'; + \OC_Log::write('setup.mssql', $entry, \OC_Log::WARN); + } else { + if ($row == null) { + $query = "USE [".$this->dbname."]; CREATE USER [".$this->dbuser."] FOR LOGIN [".$this->dbuser."];"; + $result = sqlsrv_query($connection, $query); + if (!$result || $result === false) { + if ( ($errors = sqlsrv_errors() ) != null) { + $entry = 'DB Error: "'.print_r(sqlsrv_errors()).'"<br />'; + } else { + $entry = ''; + } + $entry.='Offending command was: '.$query.'<br />'; + \OC_Log::write('setup.mssql', $entry, \OC_Log::WARN); + } + } + + $query = "USE [".$this->dbname."]; EXEC sp_addrolemember 'db_owner', '".$this->dbuser."';"; + $result = sqlsrv_query($connection, $query); + if (!$result || $result === false) { + if ( ($errors = sqlsrv_errors() ) != null) { + $entry='DB Error: "'.print_r(sqlsrv_errors()).'"<br />'; + } else { + $entry = ''; + } + $entry.='Offending command was: '.$query.'<br />'; + \OC_Log::write('setup.mssql', $entry, \OC_Log::WARN); + } + } + } + } + + private function createDatabase($connection) { + $query = "CREATE DATABASE [".$this->dbname."];"; + $result = sqlsrv_query($connection, $query); + if (!$result || $result === false) { + if ( ($errors = sqlsrv_errors() ) != null) { + $entry='DB Error: "'.print_r(sqlsrv_errors()).'"<br />'; + } else { + $entry = ''; + } + $entry.='Offending command was: '.$query.'<br />'; + \OC_Log::write('setup.mssql', $entry, \OC_Log::WARN); + } + } + + private function createDatabaseStructure() { + $connectionInfo = array( "Database" => $this->dbname, "UID" => $this->dbuser, "PWD" => $this->dbpassword); + + $connection = @sqlsrv_connect($this->dbhost, $connectionInfo); + + //fill the database if needed + $query = "SELECT * FROM INFORMATION_SCHEMA.TABLES" + ." WHERE TABLE_SCHEMA = '".$this->dbname."'" + ." AND TABLE_NAME = '".$this->tableprefix."users'"; + $result = sqlsrv_query($connection, $query); + if ($result === false) { + if ( ($errors = sqlsrv_errors() ) != null) { + $entry='DB Error: "'.print_r(sqlsrv_errors()).'"<br />'; + } else { + $entry = ''; + } + $entry.='Offending command was: '.$query.'<br />'; + \OC_Log::write('setup.mssql', $entry, \OC_Log::WARN); + } else { + $row = sqlsrv_fetch_array($result); + + if ($row === false) { + if ( ($errors = sqlsrv_errors() ) != null) { + $entry='DB Error: "'.print_r(sqlsrv_errors()).'"<br />'; + } else { + $entry = ''; + } + $entry.='Offending command was: '.$query.'<br />'; + \OC_Log::write('setup.mssql', $entry, \OC_Log::WARN); + } else { + if ($row == null) { + \OC_DB::createDbFromStructure($this->dbDefinitionFile); + } + } + } + + sqlsrv_close($connection); + } +} diff --git a/lib/private/setup/mysql.php b/lib/private/setup/mysql.php new file mode 100644 index 00000000000..d97b6d2602f --- /dev/null +++ b/lib/private/setup/mysql.php @@ -0,0 +1,95 @@ +<?php + +namespace OC\Setup; + +class MySQL extends AbstractDatabase { + public $dbprettyname = 'MySQL'; + + public function setupDatabase($username) { + //check if the database user has admin right + $connection = @mysql_connect($this->dbhost, $this->dbuser, $this->dbpassword); + if(!$connection) { + throw new \DatabaseSetupException($this->trans->t('MySQL username and/or password not valid'), + $this->trans->t('You need to enter either an existing account or the administrator.')); + } + $oldUser=\OC_Config::getValue('dbuser', false); + + //this should be enough to check for admin rights in mysql + $query="SELECT user FROM mysql.user WHERE user='$this->dbuser'"; + if(mysql_query($query, $connection)) { + //use the admin login data for the new database user + + //add prefix to the mysql user name to prevent collisions + $this->dbuser=substr('oc_'.$username, 0, 16); + if($this->dbuser!=$oldUser) { + //hash the password so we don't need to store the admin config in the config file + $this->dbpassword=\OC_Util::generateRandomBytes(30); + + $this->createDBUser($connection); + + \OC_Config::setValue('dbuser', $this->dbuser); + \OC_Config::setValue('dbpassword', $this->dbpassword); + } + + //create the database + $this->createDatabase($connection); + } + else { + if($this->dbuser!=$oldUser) { + \OC_Config::setValue('dbuser', $this->dbuser); + \OC_Config::setValue('dbpassword', $this->dbpassword); + } + + //create the database + $this->createDatabase($connection); + } + + //fill the database if needed + $query='select count(*) from information_schema.tables' + ." where table_schema='".$this->dbname."' AND table_name = '".$this->tableprefix."users';"; + $result = mysql_query($query, $connection); + if($result) { + $row=mysql_fetch_row($result); + } + if(!$result or $row[0]==0) { + \OC_DB::createDbFromStructure($this->dbDefinitionFile); + } + mysql_close($connection); + } + + private function createDatabase($connection) { + $name = $this->dbname; + $user = $this->dbuser; + //we cant use OC_BD functions here because we need to connect as the administrative user. + $query = "CREATE DATABASE IF NOT EXISTS `$name`"; + $result = mysql_query($query, $connection); + if(!$result) { + $entry = $this->trans->t('DB Error: "%s"', array(mysql_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.mssql', $entry, \OC_Log::WARN); + } + $query="GRANT ALL PRIVILEGES ON `$name` . * TO '$user'"; + + //this query will fail if there aren't the right permissions, ignore the error + mysql_query($query, $connection); + } + + private function createDBUser($connection) { + $name = $this->dbuser; + $password = $this->dbpassword; + // we need to create 2 accounts, one for global use and one for local user. if we don't specify the local one, + // the anonymous user would take precedence when there is one. + $query = "CREATE USER '$name'@'localhost' IDENTIFIED BY '$password'"; + $result = mysql_query($query, $connection); + if (!$result) { + throw new \DatabaseSetupException($this->trans->t("MySQL user '%s'@'localhost' exists already.", array($name)), + $this->trans->t("Drop this user from MySQL", array($name))); + } + $query = "CREATE USER '$name'@'%' IDENTIFIED BY '$password'"; + $result = mysql_query($query, $connection); + if (!$result) { + throw new \DatabaseSetupException($this->trans->t("MySQL user '%s'@'%%' already exists", array($name)), + $this->trans->t("Drop this user from MySQL.")); + } + } +} diff --git a/lib/private/setup/oci.php b/lib/private/setup/oci.php new file mode 100644 index 00000000000..326d7a00531 --- /dev/null +++ b/lib/private/setup/oci.php @@ -0,0 +1,210 @@ +<?php + +namespace OC\Setup; + +class OCI extends AbstractDatabase { + public $dbprettyname = 'Oracle'; + + protected $dbtablespace; + + public function initialize($config) { + parent::initialize($config); + if (array_key_exists('dbtablespace', $config)) { + $this->dbtablespace = $config['dbtablespace']; + } else { + $this->dbtablespace = 'USERS'; + } + \OC_Config::setValue('dbtablespace', $this->dbtablespace); + } + + public function setupDatabase($username) { + $e_host = addslashes($this->dbhost); + $e_dbname = addslashes($this->dbname); + //check if the database user has admin right + if ($e_host == '') { + $easy_connect_string = $e_dbname; // use dbname as easy connect name + } else { + $easy_connect_string = '//'.$e_host.'/'.$e_dbname; + } + \OC_Log::write('setup oracle', 'connect string: ' . $easy_connect_string, \OC_Log::DEBUG); + $connection = @oci_connect($this->dbuser, $this->dbpassword, $easy_connect_string); + if(!$connection) { + $e = oci_error(); + if (is_array ($e) && isset ($e['message'])) { + throw new \DatabaseSetupException($this->trans->t('Oracle connection could not be established'), + $e['message'].' Check environment: ORACLE_HOME='.getenv('ORACLE_HOME') + .' ORACLE_SID='.getenv('ORACLE_SID') + .' LD_LIBRARY_PATH='.getenv('LD_LIBRARY_PATH') + .' NLS_LANG='.getenv('NLS_LANG') + .' tnsnames.ora is '.(is_readable(getenv('ORACLE_HOME').'/network/admin/tnsnames.ora')?'':'not ').'readable'); + } + throw new \DatabaseSetupException($this->trans->t('Oracle username and/or password not valid'), + 'Check environment: ORACLE_HOME='.getenv('ORACLE_HOME') + .' ORACLE_SID='.getenv('ORACLE_SID') + .' LD_LIBRARY_PATH='.getenv('LD_LIBRARY_PATH') + .' NLS_LANG='.getenv('NLS_LANG') + .' tnsnames.ora is '.(is_readable(getenv('ORACLE_HOME').'/network/admin/tnsnames.ora')?'':'not ').'readable'); + } + //check for roles creation rights in oracle + + $query='SELECT count(*) FROM user_role_privs, role_sys_privs' + ." WHERE user_role_privs.granted_role = role_sys_privs.role AND privilege = 'CREATE ROLE'"; + $stmt = oci_parse($connection, $query); + if (!$stmt) { + $entry = $this->trans->t('DB Error: "%s"', array(oci_last_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.oci', $entry, \OC_Log::WARN); + } + $result = oci_execute($stmt); + if($result) { + $row = oci_fetch_row($stmt); + } + if($result and $row[0] > 0) { + //use the admin login data for the new database user + + //add prefix to the oracle user name to prevent collisions + $this->dbuser='oc_'.$username; + //create a new password so we don't need to store the admin config in the config file + $this->dbpassword=\OC_Util::generateRandomBytes(30); + + //oracle passwords are treated as identifiers: + // must start with aphanumeric char + // needs to be shortened to 30 bytes, as the two " needed to escape the identifier count towards the identifier length. + $this->dbpassword=substr($this->dbpassword, 0, 30); + + $this->createDBUser($connection); + + \OC_Config::setValue('dbuser', $this->dbusername); + \OC_Config::setValue('dbname', $this->dbusername); + \OC_Config::setValue('dbpassword', $this->dbpassword); + + //create the database not neccessary, oracle implies user = schema + //$this->createDatabase($this->dbname, $this->dbusername, $connection); + } else { + + \OC_Config::setValue('dbuser', $this->dbuser); + \OC_Config::setValue('dbname', $this->dbname); + \OC_Config::setValue('dbpassword', $this->dbpassword); + + //create the database not neccessary, oracle implies user = schema + //$this->createDatabase($this->dbname, $this->dbuser, $connection); + } + + //FIXME check tablespace exists: select * from user_tablespaces + + // the connection to dbname=oracle is not needed anymore + oci_close($connection); + + // connect to the oracle database (schema=$this->dbuser) an check if the schema needs to be filled + $this->dbuser = \OC_Config::getValue('dbuser'); + //$this->dbname = \OC_Config::getValue('dbname'); + $this->dbpassword = \OC_Config::getValue('dbpassword'); + + $e_host = addslashes($this->dbhost); + $e_dbname = addslashes($this->dbname); + + if ($e_host == '') { + $easy_connect_string = $e_dbname; // use dbname as easy connect name + } else { + $easy_connect_string = '//'.$e_host.'/'.$e_dbname; + } + $connection = @oci_connect($this->dbuser, $this->dbpassword, $easy_connect_string); + if(!$connection) { + throw new \DatabaseSetupException($this->trans->t('Oracle username and/or password not valid'), + $this->trans->t('You need to enter either an existing account or the administrator.')); + } + $query = "SELECT count(*) FROM user_tables WHERE table_name = :un"; + $stmt = oci_parse($connection, $query); + $un = $this->dbtableprefix.'users'; + oci_bind_by_name($stmt, ':un', $un); + if (!$stmt) { + $entry = $this->trans->t('DB Error: "%s"', array(oci_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.oci', $entry, \OC_Log::WARN); + } + $result = oci_execute($stmt); + + if($result) { + $row = oci_fetch_row($stmt); + } + if(!$result or $row[0]==0) { + \OC_DB::createDbFromStructure($this->dbDefinitionFile); + } + } + + /** + * + * @param String $name + * @param String $password + * @param resource $connection + */ + private function createDBUser($connection) { + $name = $this->dbuser; + $password = $this->password; + $query = "SELECT * FROM all_users WHERE USERNAME = :un"; + $stmt = oci_parse($connection, $query); + if (!$stmt) { + $entry = $this->trans->t('DB Error: "%s"', array(oci_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.oci', $entry, \OC_Log::WARN); + } + oci_bind_by_name($stmt, ':un', $name); + $result = oci_execute($stmt); + if(!$result) { + $entry = $this->trans->t('DB Error: "%s"', array(oci_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.oci', $entry, \OC_Log::WARN); + } + + if(! oci_fetch_row($stmt)) { + //user does not exists let's create it :) + //password must start with alphabetic character in oracle + $query = 'CREATE USER '.$name.' IDENTIFIED BY "'.$password.'" DEFAULT TABLESPACE '.$this->dbtablespace; + $stmt = oci_parse($connection, $query); + if (!$stmt) { + $entry = $this->trans->t('DB Error: "%s"', array(oci_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.oci', $entry, \OC_Log::WARN); + } + //oci_bind_by_name($stmt, ':un', $name); + $result = oci_execute($stmt); + if(!$result) { + $entry = $this->trans->t('DB Error: "%s"', array(oci_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s", name: %s, password: %s', + array($query, $name, $password)) . '<br />'; + \OC_Log::write('setup.oci', $entry, \OC_Log::WARN); + } + } else { // change password of the existing role + $query = "ALTER USER :un IDENTIFIED BY :pw"; + $stmt = oci_parse($connection, $query); + if (!$stmt) { + $entry = $this->trans->t('DB Error: "%s"', array(oci_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.oci', $entry, \OC_Log::WARN); + } + oci_bind_by_name($stmt, ':un', $name); + oci_bind_by_name($stmt, ':pw', $password); + $result = oci_execute($stmt); + if(!$result) { + $entry = $this->trans->t('DB Error: "%s"', array(oci_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.oci', $entry, \OC_Log::WARN); + } + } + // grant necessary roles + $query = 'GRANT CREATE SESSION, CREATE TABLE, CREATE SEQUENCE, CREATE TRIGGER, UNLIMITED TABLESPACE TO '.$name; + $stmt = oci_parse($connection, $query); + if (!$stmt) { + $entry = $this->trans->t('DB Error: "%s"', array(oci_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.oci', $entry, \OC_Log::WARN); + } + $result = oci_execute($stmt); + if(!$result) { + $entry = $this->trans->t('DB Error: "%s"', array(oci_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s", name: %s, password: %s', + array($query, $name, $password)) . '<br />'; + \OC_Log::write('setup.oci', $entry, \OC_Log::WARN); + } + } +} diff --git a/lib/private/setup/postgresql.php b/lib/private/setup/postgresql.php new file mode 100644 index 00000000000..89d328ada19 --- /dev/null +++ b/lib/private/setup/postgresql.php @@ -0,0 +1,140 @@ +<?php + +namespace OC\Setup; + +class PostgreSQL extends AbstractDatabase { + public $dbprettyname = 'PostgreSQL'; + + public function setupDatabase($username) { + $e_host = addslashes($this->dbhost); + $e_user = addslashes($this->dbuser); + $e_password = addslashes($this->dbpassword); + + //check if the database user has admin rights + $connection_string = "host='$e_host' dbname=postgres user='$e_user' password='$e_password'"; + $connection = @pg_connect($connection_string); + if(!$connection) { + // Try if we can connect to the DB with the specified name + $e_dbname = addslashes($this->dbname); + $connection_string = "host='$e_host' dbname='$e_dbname' user='$e_user' password='$e_password'"; + $connection = @pg_connect($connection_string); + + if(!$connection) + throw new \DatabaseSetupException($this->trans->t('PostgreSQL username and/or password not valid'), + $this->trans->t('You need to enter either an existing account or the administrator.')); + } + $e_user = pg_escape_string($this->dbuser); + //check for roles creation rights in postgresql + $query="SELECT 1 FROM pg_roles WHERE rolcreaterole=TRUE AND rolname='$e_user'"; + $result = pg_query($connection, $query); + if($result and pg_num_rows($result) > 0) { + //use the admin login data for the new database user + + //add prefix to the postgresql user name to prevent collisions + $this->dbuser='oc_'.$username; + //create a new password so we don't need to store the admin config in the config file + $this->dbpassword=\OC_Util::generateRandomBytes(30); + + $this->createDBUser($connection); + + \OC_Config::setValue('dbuser', $this->dbuser); + \OC_Config::setValue('dbpassword', $this->dbpassword); + + //create the database + $this->createDatabase($connection); + } + else { + \OC_Config::setValue('dbuser', $this->dbuser); + \OC_Config::setValue('dbpassword', $this->dbpassword); + + //create the database + $this->createDatabase($connection); + } + + // the connection to dbname=postgres is not needed anymore + pg_close($connection); + + // connect to the ownCloud database (dbname=$this->dbname) and check if it needs to be filled + $this->dbuser = \OC_Config::getValue('dbuser'); + $this->dbpassword = \OC_Config::getValue('dbpassword'); + + $e_host = addslashes($this->dbhost); + $e_dbname = addslashes($this->dbname); + $e_user = addslashes($this->dbuser); + $e_password = addslashes($this->dbpassword); + + $connection_string = "host='$e_host' dbname='$e_dbname' user='$e_user' password='$e_password'"; + $connection = @pg_connect($connection_string); + if(!$connection) { + throw new \DatabaseSetupException($this->trans->t('PostgreSQL username and/or password not valid'), + $this->trans->t('You need to enter either an existing account or the administrator.')); + } + $query = "select count(*) FROM pg_class WHERE relname='".$this->tableprefix."users' limit 1"; + $result = pg_query($connection, $query); + if($result) { + $row = pg_fetch_row($result); + } + if(!$result or $row[0]==0) { + \OC_DB::createDbFromStructure($this->dbDefinitionFile); + } + } + + private function createDatabase($connection) { + //we cant use OC_BD functions here because we need to connect as the administrative user. + $e_name = pg_escape_string($this->dbname); + $e_user = pg_escape_string($this->dbuser); + $query = "select datname from pg_database where datname = '$e_name'"; + $result = pg_query($connection, $query); + if(!$result) { + $entry = $this->trans->t('DB Error: "%s"', array(pg_last_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.pg', $entry, \OC_Log::WARN); + } + if(! pg_fetch_row($result)) { + //The database does not exists... let's create it + $query = "CREATE DATABASE \"$e_name\" OWNER \"$e_user\""; + $result = pg_query($connection, $query); + if(!$result) { + $entry = $this->trans->t('DB Error: "%s"', array(pg_last_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.pg', $entry, \OC_Log::WARN); + } + else { + $query = "REVOKE ALL PRIVILEGES ON DATABASE \"$e_name\" FROM PUBLIC"; + pg_query($connection, $query); + } + } + } + + private function createDBUser($connection) { + $e_name = pg_escape_string($this->dbuser); + $e_password = pg_escape_string($this->dbpassword); + $query = "select * from pg_roles where rolname='$e_name';"; + $result = pg_query($connection, $query); + if(!$result) { + $entry = $this->trans->t('DB Error: "%s"', array(pg_last_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.pg', $entry, \OC_Log::WARN); + } + + if(! pg_fetch_row($result)) { + //user does not exists let's create it :) + $query = "CREATE USER \"$e_name\" CREATEDB PASSWORD '$e_password';"; + $result = pg_query($connection, $query); + if(!$result) { + $entry = $this->trans->t('DB Error: "%s"', array(pg_last_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.pg', $entry, \OC_Log::WARN); + } + } + else { // change password of the existing role + $query = "ALTER ROLE \"$e_name\" WITH PASSWORD '$e_password';"; + $result = pg_query($connection, $query); + if(!$result) { + $entry = $this->trans->t('DB Error: "%s"', array(pg_last_error($connection))) . '<br />'; + $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; + \OC_Log::write('setup.pg', $entry, \OC_Log::WARN); + } + } + } +} diff --git a/lib/private/setup/sqlite.php b/lib/private/setup/sqlite.php new file mode 100644 index 00000000000..fd4df792d62 --- /dev/null +++ b/lib/private/setup/sqlite.php @@ -0,0 +1,26 @@ +<?php + +namespace OC\Setup; + +class Sqlite extends AbstractDatabase { + public $dbprettyname = 'Sqlite'; + + public function validate($config) { + return array(); + } + + public function initialize($config) { + } + + public function setupDatabase($username) { + $datadir = \OC_Config::getValue('datadirectory'); + + //delete the old sqlite database first, might cause infinte loops otherwise + if(file_exists("$datadir/owncloud.db")) { + unlink("$datadir/owncloud.db"); + } + //in case of sqlite, we can always fill the database + error_log("creating sqlite db"); + \OC_DB::createDbFromStructure($this->dbDefinitionFile); + } +} diff --git a/lib/private/subadmin.php b/lib/private/subadmin.php new file mode 100644 index 00000000000..8cda7240ac9 --- /dev/null +++ b/lib/private/subadmin.php @@ -0,0 +1,189 @@ +<?php +/** + * ownCloud + * + * @author Georg Ehrke + * @copyright 2012 Georg Ehrke + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +OC_Hook::connect('OC_User', 'post_deleteUser', 'OC_SubAdmin', 'post_deleteUser'); +OC_Hook::connect('OC_User', 'post_deleteGroup', 'OC_SubAdmin', 'post_deleteGroup'); +/** + * This class provides all methods needed for managing groups. + * + * Hooks provided: + * post_createSubAdmin($gid) + * post_deleteSubAdmin($gid) + */ +class OC_SubAdmin{ + + /** + * @brief add a SubAdmin + * @param $uid uid of the SubAdmin + * @param $gid gid of the group + * @return boolean + */ + public static function createSubAdmin($uid, $gid) { + $stmt = OC_DB::prepare('INSERT INTO `*PREFIX*group_admin` (`gid`,`uid`) VALUES(?,?)'); + $result = $stmt->execute(array($gid, $uid)); + OC_Hook::emit( "OC_SubAdmin", "post_createSubAdmin", array( "gid" => $gid )); + return true; + } + + /** + * @brief delete a SubAdmin + * @param $uid uid of the SubAdmin + * @param $gid gid of the group + * @return boolean + */ + public static function deleteSubAdmin($uid, $gid) { + $stmt = OC_DB::prepare('DELETE FROM `*PREFIX*group_admin` WHERE `gid` = ? AND `uid` = ?'); + $result = $stmt->execute(array($gid, $uid)); + OC_Hook::emit( "OC_SubAdmin", "post_deleteSubAdmin", array( "gid" => $gid )); + return true; + } + + /** + * @brief get groups of a SubAdmin + * @param $uid uid of the SubAdmin + * @return array + */ + public static function getSubAdminsGroups($uid) { + $stmt = OC_DB::prepare('SELECT `gid` FROM `*PREFIX*group_admin` WHERE `uid` = ?'); + $result = $stmt->execute(array($uid)); + $gids = array(); + while($row = $result->fetchRow()) { + $gids[] = $row['gid']; + } + return $gids; + } + + /** + * @brief get SubAdmins of a group + * @param $gid gid of the group + * @return array + */ + public static function getGroupsSubAdmins($gid) { + $stmt = OC_DB::prepare('SELECT `uid` FROM `*PREFIX*group_admin` WHERE `gid` = ?'); + $result = $stmt->execute(array($gid)); + $uids = array(); + while($row = $result->fetchRow()) { + $uids[] = $row['uid']; + } + return $uids; + } + + /** + * @brief get all SubAdmins + * @return array + */ + public static function getAllSubAdmins() { + $stmt = OC_DB::prepare('SELECT * FROM `*PREFIX*group_admin`'); + $result = $stmt->execute(); + $subadmins = array(); + while($row = $result->fetchRow()) { + $subadmins[] = $row; + } + return $subadmins; + } + + /** + * @brief checks if a user is a SubAdmin of a group + * @param $uid uid of the subadmin + * @param $gid gid of the group + * @return bool + */ + public static function isSubAdminofGroup($uid, $gid) { + $stmt = OC_DB::prepare('SELECT COUNT(*) AS `count` FROM `*PREFIX*group_admin` WHERE `uid` = ? AND `gid` = ?'); + $result = $stmt->execute(array($uid, $gid)); + $result = $result->fetchRow(); + if($result['count'] >= 1) { + return true; + } + return false; + } + + /** + * @brief checks if a user is a SubAdmin + * @param $uid uid of the subadmin + * @return bool + */ + public static function isSubAdmin($uid) { + // Check if the user is already an admin + if(OC_Group::inGroup($uid, 'admin' )) { + return true; + } + + $stmt = OC_DB::prepare('SELECT COUNT(*) AS `count` FROM `*PREFIX*group_admin` WHERE `uid` = ?'); + $result = $stmt->execute(array($uid)); + $result = $result->fetchRow(); + if($result['count'] > 0) { + return true; + } + return false; + } + + /** + * @brief checks if a user is a accessible by a subadmin + * @param $subadmin uid of the subadmin + * @param $user uid of the user + * @return bool + */ + public static function isUserAccessible($subadmin, $user) { + if(!self::isSubAdmin($subadmin)) { + return false; + } + if(OC_User::isAdminUser($user)) { + return false; + } + $accessiblegroups = self::getSubAdminsGroups($subadmin); + foreach($accessiblegroups as $accessiblegroup) { + if(OC_Group::inGroup($user, $accessiblegroup)) { + return true; + } + } + return false; + } + + /* + * @brief alias for self::isSubAdminofGroup() + */ + public static function isGroupAccessible($subadmin, $group) { + return self::isSubAdminofGroup($subadmin, $group); + } + + /** + * @brief delete all SubAdmins by uid + * @param $parameters + * @return boolean + */ + public static function post_deleteUser($parameters) { + $stmt = OC_DB::prepare('DELETE FROM `*PREFIX*group_admin` WHERE `uid` = ?'); + $result = $stmt->execute(array($parameters['uid'])); + return true; + } + + /** + * @brief delete all SubAdmins by gid + * @param $parameters + * @return boolean + */ + public static function post_deleteGroup($parameters) { + $stmt = OC_DB::prepare('DELETE FROM `*PREFIX*group_admin` WHERE `gid` = ?'); + $result = $stmt->execute(array($parameters['gid'])); + return true; + } +} diff --git a/lib/private/template.php b/lib/private/template.php new file mode 100644 index 00000000000..9b2c1211e61 --- /dev/null +++ b/lib/private/template.php @@ -0,0 +1,311 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @author Jakob Sack + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +require_once __DIR__.'/template/functions.php'; + +/** + * This class provides the templates for ownCloud. + */ +class OC_Template extends \OC\Template\Base { + private $renderas; // Create a full page? + private $path; // The path to the template + private $headers=array(); //custom headers + + /** + * @brief Constructor + * @param string $app app providing the template + * @param string $name of the template file (without suffix) + * @param string $renderas = ""; produce a full page + * @return OC_Template object + * + * This function creates an OC_Template object. + * + * 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". + */ + public function __construct( $app, $name, $renderas = "" ) { + // Read the selected theme from the config file + $theme = OC_Util::getTheme(); + + // Read the detected formfactor and use the right file name. + $fext = self::getFormFactorExtension(); + + $requesttoken = OC::$session ? OC_Util::callRegister() : ''; + + $parts = explode('/', $app); // fix translation when app is something like core/lostpassword + $l10n = OC_L10N::get($parts[0]); + $themeDefaults = new OC_Defaults(); + + list($path, $template) = $this->findTemplate($theme, $app, $name, $fext); + + // Set the private data + $this->renderas = $renderas; + $this->path = $path; + + parent::__construct($template, $requesttoken, $l10n, $themeDefaults); + + // Some headers to enhance security + 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 + + // iFrame Restriction Policy + $xFramePolicy = OC_Config::getValue('xframe_restriction', true); + if($xFramePolicy) { + header('X-Frame-Options: Sameorigin'); // Disallow iFraming from other domains + } + + // Content Security Policy + // If you change the standard policy, please also change it in config.sample.php + $policy = OC_Config::getValue('custom_csp_policy', + 'default-src \'self\'; ' + .'script-src \'self\' \'unsafe-eval\'; ' + .'style-src \'self\' \'unsafe-inline\'; ' + .'frame-src *; ' + .'img-src *; ' + .'font-src \'self\' data:; ' + .'media-src *'); + header('Content-Security-Policy:'.$policy); // Standard + + } + + /** + * autodetect the formfactor of the used device + * default -> the normal desktop browser interface + * mobile -> interface for smartphones + * tablet -> interface for tablets + * standalone -> the default interface but without header, footer and + * sidebar, just the application. Useful to use just a specific + * app on the desktop in a standalone window. + */ + public static function detectFormfactor() { + // please add more useragent strings for other devices + if(isset($_SERVER['HTTP_USER_AGENT'])) { + if(stripos($_SERVER['HTTP_USER_AGENT'], 'ipad')>0) { + $mode='tablet'; + }elseif(stripos($_SERVER['HTTP_USER_AGENT'], 'iphone')>0) { + $mode='mobile'; + }elseif((stripos($_SERVER['HTTP_USER_AGENT'], 'N9')>0) + and (stripos($_SERVER['HTTP_USER_AGENT'], 'nokia')>0)) { + $mode='mobile'; + }else{ + $mode='default'; + } + }else{ + $mode='default'; + } + return($mode); + } + + /** + * @brief Returns the formfactor extension for current formfactor + */ + static public function getFormFactorExtension() + { + if (!\OC::$session) { + return ''; + } + // if the formfactor is not yet autodetected do the + // autodetection now. For possible formfactors check the + // detectFormfactor documentation + if (!\OC::$session->exists('formfactor')) { + \OC::$session->set('formfactor', self::detectFormfactor()); + } + // allow manual override via GET parameter + if(isset($_GET['formfactor'])) { + \OC::$session->set('formfactor', $_GET['formfactor']); + } + $formfactor = \OC::$session->get('formfactor'); + if($formfactor==='default') { + $fext=''; + }elseif($formfactor==='mobile') { + $fext='.mobile'; + }elseif($formfactor==='tablet') { + $fext='.tablet'; + }elseif($formfactor==='standalone') { + $fext='.standalone'; + }else{ + $fext=''; + } + return $fext; + } + + /** + * @brief 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 and formfactor. + * Checking all the possible locations. + */ + protected function findTemplate($theme, $app, $name, $fext) { + // 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( $fext, $dirs ); + $template = $locator->find($name); + $path = $locator->getPath(); + return array($path, $template); + } + + /** + * @brief 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 + */ + public function addHeader( $tag, $attributes, $text='') { + $this->headers[]=array('tag'=>$tag,'attributes'=>$attributes, 'text'=>$text); + } + + /** + * @brief Process the template + * @return bool + * + * This function process the template. If $this->renderas is set, it + * will produce a full page. + */ + public function fetchPage() { + $data = parent::fetchPage(); + + if( $this->renderas ) { + $page = new OC_TemplateLayout($this->renderas); + + // Add custom headers + $page->assign('headers', $this->headers, false); + foreach(OC_Util::$headers as $header) { + $page->append('headers', $header); + } + + $page->assign( "content", $data, false ); + return $page->fetchPage(); + } + else{ + return $data; + } + } + + /** + * @brief Include template + * @return string returns content of included template + * + * Includes another template. use <?php echo $this->inc('template'); ?> to + * do this. + */ + public function inc( $file, $additionalparams = null ) { + return $this->load($this->path.$file.'.php', $additionalparams); + } + + /** + * @brief 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 bool + */ + 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(); + } + + /** + * @brief 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(); + } + + /** + * @brief 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 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(); + } + + /** + * @brief 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 + * Warning: All data passed to $hint needs to get sanitized using OC_Util::sanitizeHTML + */ + public static function printErrorPage( $error_msg, $hint = '' ) { + $content = new OC_Template( '', 'error', 'error' ); + $errors = array(array('error' => $error_msg, 'hint' => $hint)); + $content->assign( 'errors', $errors ); + $content->printPage(); + die(); + } + + /** + * print error page using Exception details + * @param Exception $exception + */ + + public static function printExceptionErrorPage(Exception $exception) { + $error_msg = $exception->getMessage(); + if ($exception->getCode()) { + $error_msg = '['.$exception->getCode().'] '.$error_msg; + } + if (defined('DEBUG') and DEBUG) { + $hint = $exception->getTraceAsString(); + if (!empty($hint)) { + $hint = '<pre>'.$hint.'</pre>'; + } + $l = OC_L10N::get('lib'); + while (method_exists($exception, 'previous') && $exception = $exception->previous()) { + $error_msg .= '<br/>'.$l->t('Caused by:').' '; + if ($exception->getCode()) { + $error_msg .= '['.$exception->getCode().'] '; + } + $error_msg .= $exception->getMessage(); + }; + } else { + $hint = ''; + if ($exception instanceof \OC\HintException) { + $hint = $exception->getHint(); + } + } + self::printErrorPage($error_msg, $hint); + } +} diff --git a/lib/private/template/base.php b/lib/private/template/base.php new file mode 100644 index 00000000000..88941bc7132 --- /dev/null +++ b/lib/private/template/base.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Template; + +class Base { + private $template; // The template + private $vars; // Vars + private $l10n; // The l10n-Object + private $theme; // theme defaults + + public function __construct( $template, $requesttoken, $l10n, $theme ) { + $this->vars = array(); + $this->vars['requesttoken'] = $requesttoken; + $this->l10n = $l10n; + $this->template = $template; + $this->theme = $theme; + } + + protected function getAppTemplateDirs($theme, $app, $serverroot, $app_dir) { + // Check if the app is in the app folder or in the root + if( file_exists($app_dir.'/templates/' )) { + return array( + $serverroot.'/themes/'.$theme.'/apps/'.$app.'/templates/', + $app_dir.'/templates/', + ); + } + return array( + $serverroot.'/themes/'.$theme.'/'.$app.'/templates/', + $serverroot.'/'.$app.'/templates/', + ); + } + + protected function getCoreTemplateDirs($theme, $serverroot) { + return array( + $serverroot.'/themes/'.$theme.'/core/templates/', + $serverroot.'/core/templates/', + ); + } + + /** + * @brief Assign variables + * @param string $key key + * @param string $value value + * @return bool + * + * This function assigns a variable. It can be accessed via $_[$key] in + * the template. + * + * If the key existed before, it will be overwritten + */ + public function assign( $key, $value) { + $this->vars[$key] = $value; + return true; + } + + /** + * @brief Appends a variable + * @param string $key key + * @param string $value value + * @return bool + * + * This function assigns a variable in an array context. If the key already + * exists, the value will be appended. It can be accessed via + * $_[$key][$position] in the template. + */ + public function append( $key, $value ) { + if( array_key_exists( $key, $this->vars )) { + $this->vars[$key][] = $value; + } + else{ + $this->vars[$key] = array( $value ); + } + } + + /** + * @brief Prints the proceeded template + * @return bool + * + * This function proceeds the template and prints its output. + */ + public function printPage() { + $data = $this->fetchPage(); + if( $data === false ) { + return false; + } + else{ + print $data; + return true; + } + } + + /** + * @brief Process the template + * @return bool + * + * This function processes the template. + */ + public function fetchPage() { + return $this->load($this->template); + } + + /** + * @brief doing the actual work + * @return string content + * + * Includes the template file, fetches its output + */ + protected function load( $file, $additionalparams = null ) { + // Register the variables + $_ = $this->vars; + $l = $this->l10n; + $theme = $this->theme; + + if( !is_null($additionalparams)) { + $_ = array_merge( $additionalparams, $this->vars ); + } + + // Include + ob_start(); + include $file; + $data = ob_get_contents(); + @ob_end_clean(); + + // Return data + return $data; + } + +} diff --git a/lib/private/template/cssresourcelocator.php b/lib/private/template/cssresourcelocator.php new file mode 100644 index 00000000000..8e7831ca549 --- /dev/null +++ b/lib/private/template/cssresourcelocator.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Template; + +class CSSResourceLocator extends ResourceLocator { + public function doFind( $style ) { + if (strpos($style, '3rdparty') === 0 + && $this->appendIfExist($this->thirdpartyroot, $style.'.css') + || $this->appendIfExist($this->serverroot, $style.$this->form_factor.'.css') + || $this->appendIfExist($this->serverroot, $style.'.css') + || $this->appendIfExist($this->serverroot, 'core/'.$style.$this->form_factor.'.css') + || $this->appendIfExist($this->serverroot, 'core/'.$style.'.css') + ) { + return; + } + $app = substr($style, 0, strpos($style, '/')); + $style = substr($style, strpos($style, '/')+1); + $app_path = \OC_App::getAppPath($app); + $app_url = $this->webroot . '/index.php/apps/' . $app; + if ($this->appendIfExist($app_path, $style.$this->form_factor.'.css', $app_url) + || $this->appendIfExist($app_path, $style.'.css', $app_url) + ) { + return; + } + throw new \Exception('css file not found: style:'.$style); + } + + public function doFindTheme( $style ) { + $theme_dir = 'themes/'.$this->theme.'/'; + $this->appendIfExist($this->serverroot, $theme_dir.'apps/'.$style.$this->form_factor.'.css') + || $this->appendIfExist($this->serverroot, $theme_dir.'apps/'.$style.'.css') + || $this->appendIfExist($this->serverroot, $theme_dir.$style.$this->form_factor.'.css') + || $this->appendIfExist($this->serverroot, $theme_dir.$style.'.css') + || $this->appendIfExist($this->serverroot, $theme_dir.'core/'.$style.$this->form_factor.'.css') + || $this->appendIfExist($this->serverroot, $theme_dir.'core/'.$style.'.css'); + } +} diff --git a/lib/private/template/functions.php b/lib/private/template/functions.php new file mode 100644 index 00000000000..501f8081bff --- /dev/null +++ b/lib/private/template/functions.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * Prints an XSS escaped string + * @param string $string the string which will be escaped and printed + */ +function p($string) { + print(OC_Util::sanitizeHTML($string)); +} + +/** + * Prints an unescaped string + * @param string $string the string which will be printed as it is + */ +function print_unescaped($string) { + print($string); +} + +/** + * @brief make OC_Helper::linkTo available as a simple function + * @param string $app app + * @param string $file file + * @param array $args array with param=>value, will be appended to the returned url + * @return string link to the file + * + * For further information have a look at OC_Helper::linkTo + */ +function link_to( $app, $file, $args = array() ) { + return OC_Helper::linkTo( $app, $file, $args ); +} + +/** + * @brief make OC_Helper::imagePath available as a simple function + * @param string $app app + * @param string $image image + * @return string link to the image + * + * For further information have a look at OC_Helper::imagePath + */ +function image_path( $app, $image ) { + return OC_Helper::imagePath( $app, $image ); +} + +/** + * @brief make OC_Helper::mimetypeIcon available as a simple function + * @param string $mimetype mimetype + * @return string link to the image + * + * For further information have a look at OC_Helper::mimetypeIcon + */ +function mimetype_icon( $mimetype ) { + return OC_Helper::mimetypeIcon( $mimetype ); +} + +/** + * @brief make preview_icon available as a simple function + * Returns the path to the preview of the image. + * @param $path path of file + * @returns link to the preview + * + * For further information have a look at OC_Helper::previewIcon + */ +function preview_icon( $path ) { + return OC_Helper::previewIcon( $path ); +} + +function publicPreview_icon ( $path, $token ) { + return OC_Helper::publicPreviewIcon( $path, $token ); +} + +/** + * @brief make OC_Helper::humanFileSize available as a simple function + * @param int $bytes size in bytes + * @return string size as string + * + * For further information have a look at OC_Helper::humanFileSize + */ +function human_file_size( $bytes ) { + return OC_Helper::humanFileSize( $bytes ); +} + +function relative_modified_date($timestamp) { + $l=OC_L10N::get('lib'); + $timediff = time() - $timestamp; + $diffminutes = round($timediff/60); + $diffhours = round($diffminutes/60); + $diffdays = round($diffhours/24); + $diffmonths = round($diffdays/31); + + if($timediff < 60) { return $l->t('seconds ago'); } + else if($timediff < 3600) { return $l->n('%n minute ago', '%n minutes ago', $diffminutes); } + else if($timediff < 86400) { return $l->n('%n hour ago', '%n hours ago', $diffhours); } + else if((date('G')-$diffhours) > 0) { return $l->t('today'); } + else if((date('G')-$diffhours) > -24) { return $l->t('yesterday'); } + else if($timediff < 2678400) { return $l->n('%n day go', '%n days ago', $diffdays); } + else if($timediff < 5184000) { return $l->t('last month'); } + else if((date('n')-$diffmonths) > 0) { return $l->n('%n month ago', '%n months ago', $diffmonths); } + else if($timediff < 63113852) { return $l->t('last year'); } + else { return $l->t('years ago'); } +} + +function html_select_options($options, $selected, $params=array()) { + if (!is_array($selected)) { + $selected=array($selected); + } + if (isset($params['combine']) && $params['combine']) { + $options = array_combine($options, $options); + } + $value_name = $label_name = false; + if (isset($params['value'])) { + $value_name = $params['value']; + } + if (isset($params['label'])) { + $label_name = $params['label']; + } + $html = ''; + foreach($options as $value => $label) { + if ($value_name && is_array($label)) { + $value = $label[$value_name]; + } + if ($label_name && is_array($label)) { + $label = $label[$label_name]; + } + $select = in_array($value, $selected) ? ' selected="selected"' : ''; + $html .= '<option value="' . OC_Util::sanitizeHTML($value) . '"' . $select . '>' . OC_Util::sanitizeHTML($label) . '</option>'."\n"; + } + return $html; +} diff --git a/lib/private/template/jsresourcelocator.php b/lib/private/template/jsresourcelocator.php new file mode 100644 index 00000000000..f8fe3817ce6 --- /dev/null +++ b/lib/private/template/jsresourcelocator.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Template; + +class JSResourceLocator extends ResourceLocator { + public function doFind( $script ) { + $theme_dir = 'themes/'.$this->theme.'/'; + if (strpos($script, '3rdparty') === 0 + && $this->appendIfExist($this->thirdpartyroot, $script.'.js') + || $this->appendIfExist($this->serverroot, $theme_dir.'apps/'.$script.$this->form_factor.'.js') + || $this->appendIfExist($this->serverroot, $theme_dir.'apps/'.$script.'.js') + || $this->appendIfExist($this->serverroot, $theme_dir.$script.$this->form_factor.'.js') + || $this->appendIfExist($this->serverroot, $theme_dir.$script.'.js') + || $this->appendIfExist($this->serverroot, $script.$this->form_factor.'.js') + || $this->appendIfExist($this->serverroot, $script.'.js') + || $this->appendIfExist($this->serverroot, $theme_dir.'core/'.$script.$this->form_factor.'.js') + || $this->appendIfExist($this->serverroot, $theme_dir.'core/'.$script.'.js') + || $this->appendIfExist($this->serverroot, 'core/'.$script.$this->form_factor.'.js') + || $this->appendIfExist($this->serverroot, 'core/'.$script.'.js') + ) { + return; + } + $app = substr($script, 0, strpos($script, '/')); + $script = substr($script, strpos($script, '/')+1); + $app_path = \OC_App::getAppPath($app); + $app_url = \OC_App::getAppWebPath($app); + if ($this->appendIfExist($app_path, $script.$this->form_factor.'.js', $app_url) + || $this->appendIfExist($app_path, $script.'.js', $app_url) + ) { + return; + } + throw new \Exception('js file not found: script:'.$script); + } + + public function doFindTheme( $script ) { + } +} diff --git a/lib/private/template/resourcelocator.php b/lib/private/template/resourcelocator.php new file mode 100644 index 00000000000..9f83673664d --- /dev/null +++ b/lib/private/template/resourcelocator.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Template; + +abstract class ResourceLocator { + protected $theme; + protected $form_factor; + + protected $mapping; + protected $serverroot; + protected $thirdpartyroot; + protected $webroot; + + protected $resources = array(); + + public function __construct( $theme, $form_factor, $core_map, $party_map ) { + $this->theme = $theme; + $this->form_factor = $form_factor; + $this->mapping = $core_map + $party_map; + $this->serverroot = key($core_map); + $this->thirdpartyroot = key($party_map); + $this->webroot = $this->mapping[$this->serverroot]; + } + + abstract public function doFind( $resource ); + abstract public function doFindTheme( $resource ); + + public function find( $resources ) { + try { + foreach($resources as $resource) { + $this->doFind($resource); + } + if (!empty($this->theme)) { + foreach($resources as $resource) { + $this->doFindTheme($resource); + } + } + } catch (\Exception $e) { + throw new \Exception($e->getMessage().' formfactor:'.$this->form_factor + .' serverroot:'.$this->serverroot); + } + } + + /* + * @brief append the $file resource if exist at $root + * @param $root path to check + * @param $file the filename + * @param $web base for path, default map $root to $webroot + */ + protected function appendIfExist($root, $file, $webroot = null) { + if (is_file($root.'/'.$file)) { + if (!$webroot) { + $webroot = $this->mapping[$root]; + } + $this->resources[] = array($root, $webroot, $file); + return true; + } + return false; + } + + public function getResources() { + return $this->resources; + } +} diff --git a/lib/private/template/templatefilelocator.php b/lib/private/template/templatefilelocator.php new file mode 100644 index 00000000000..d5a484b1a14 --- /dev/null +++ b/lib/private/template/templatefilelocator.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Template; + +class TemplateFileLocator { + protected $form_factor; + protected $dirs; + private $path; + + public function __construct( $form_factor, $dirs ) { + $this->form_factor = $form_factor; + $this->dirs = $dirs; + } + + public function find( $template ) { + if ($template === '') { + throw new \InvalidArgumentException('Empty template name'); + } + + foreach($this->dirs as $dir) { + $file = $dir.$template.$this->form_factor.'.php'; + if (is_file($file)) { + $this->path = $dir; + return $file; + } + $file = $dir.$template.'.php'; + if (is_file($file)) { + $this->path = $dir; + return $file; + } + } + throw new \Exception('template file not found: template:'.$template.' formfactor:'.$this->form_factor); + } + + public function getPath() { + return $this->path; + } +} diff --git a/lib/private/templatelayout.php b/lib/private/templatelayout.php new file mode 100644 index 00000000000..625f3424a04 --- /dev/null +++ b/lib/private/templatelayout.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_TemplateLayout extends OC_Template { + public function __construct( $renderas ) { + // Decide which page we show + + if( $renderas == 'user' ) { + parent::__construct( 'core', 'layout.user' ); + if(in_array(OC_APP::getCurrentApp(), array('settings','admin', 'help'))!==false) { + $this->assign('bodyid', 'body-settings'); + }else{ + $this->assign('bodyid', 'body-user'); + } + + // Update notification + if(OC_Config::getValue('updatechecker', true) === true) { + $data=OC_Updater::check(); + if(isset($data['version']) && $data['version'] != '' and $data['version'] !== Array() && OC_User::isAdminUser(OC_User::getUser())) { + $this->assign('updateAvailable', true); + $this->assign('updateVersion', $data['versionstring']); + $this->assign('updateLink', $data['web']); + } else { + $this->assign('updateAvailable', false); // No update available or not an admin user + } + } else { + $this->assign('updateAvailable', false); // Update check is disabled + } + + // Add navigation entry + $this->assign( 'application', '', false ); + $navigation = OC_App::getNavigation(); + $this->assign( 'navigation', $navigation); + $this->assign( 'settingsnavigation', OC_App::getSettingsNavigation()); + foreach($navigation as $entry) { + if ($entry['active']) { + $this->assign( 'application', $entry['name'] ); + break; + } + } + $user_displayname = OC_User::getDisplayName(); + $this->assign( 'user_displayname', $user_displayname ); + $this->assign( 'user_uid', OC_User::getUser() ); + $this->assign('enableAvatars', \OC_Config::getValue('enable_avatars', true)); + } else if ($renderas == 'guest' || $renderas == 'error') { + parent::__construct('core', 'layout.guest'); + } else { + parent::__construct('core', 'layout.base'); + } + $versionParameter = '?v=' . md5(implode(OC_Util::getVersion())); + // Add the js files + $jsfiles = self::findJavascriptFiles(OC_Util::$scripts); + $this->assign('jsfiles', array(), false); + if (OC_Config::getValue('installed', false) && $renderas!='error') { + $this->append( 'jsfiles', OC_Helper::linkToRoute('js_config') . $versionParameter); + } + if (!empty(OC_Util::$coreScripts)) { + $this->append( 'jsfiles', OC_Helper::linkToRemoteBase('core.js', false) . $versionParameter); + } + foreach($jsfiles as $info) { + $root = $info[0]; + $web = $info[1]; + $file = $info[2]; + $this->append( 'jsfiles', $web.'/'.$file . $versionParameter); + } + + // Add the css files + $cssfiles = self::findStylesheetFiles(OC_Util::$styles); + $this->assign('cssfiles', array()); + if (!empty(OC_Util::$coreStyles)) { + $this->append( 'cssfiles', OC_Helper::linkToRemoteBase('core.css', false) . $versionParameter); + } + foreach($cssfiles as $info) { + $root = $info[0]; + $web = $info[1]; + $file = $info[2]; + + $this->append( 'cssfiles', $web.'/'.$file . $versionParameter); + } + } + + static public function findStylesheetFiles($styles) { + // Read the selected theme from the config file + $theme = OC_Util::getTheme(); + + // Read the detected formfactor and use the right file name. + $fext = self::getFormFactorExtension(); + + $locator = new \OC\Template\CSSResourceLocator( $theme, $fext, + array( OC::$SERVERROOT => OC::$WEBROOT ), + array( OC::$THIRDPARTYROOT => OC::$THIRDPARTYWEBROOT )); + $locator->find($styles); + return $locator->getResources(); + } + + static public function findJavascriptFiles($scripts) { + // Read the selected theme from the config file + $theme = OC_Util::getTheme(); + + // Read the detected formfactor and use the right file name. + $fext = self::getFormFactorExtension(); + + $locator = new \OC\Template\JSResourceLocator( $theme, $fext, + array( OC::$SERVERROOT => OC::$WEBROOT ), + array( OC::$THIRDPARTYROOT => OC::$THIRDPARTYWEBROOT )); + $locator->find($scripts); + return $locator->getResources(); + } +} diff --git a/lib/private/updater.php b/lib/private/updater.php new file mode 100644 index 00000000000..df7332a96a9 --- /dev/null +++ b/lib/private/updater.php @@ -0,0 +1,159 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC; +use OC\Hooks\BasicEmitter; + +/** + * Class that handles autoupdating of ownCloud + * + * Hooks provided in scope \OC\Updater + * - maintenanceStart() + * - maintenanceEnd() + * - dbUpgrade() + * - filecacheStart() + * - filecacheProgress(int $percentage) + * - filecacheDone() + * - failure(string $message) + */ +class Updater extends BasicEmitter { + + /** + * @var \OC\Log $log + */ + private $log; + + /** + * @param \OC\Log $log + */ + public function __construct($log = null) { + $this->log = $log; + } + + /** + * Check if a new version is available + * @param string $updateUrl the url to check, i.e. 'http://apps.owncloud.com/updater.php' + * @return array | bool + */ + public function check($updaterUrl) { + + // Look up the cache - it is invalidated all 30 minutes + if ((\OC_Appconfig::getValue('core', 'lastupdatedat') + 1800) > time()) { + return json_decode(\OC_Appconfig::getValue('core', 'lastupdateResult'), true); + } + + \OC_Appconfig::setValue('core', 'lastupdatedat', time()); + + if (\OC_Appconfig::getValue('core', 'installedat', '') == '') { + \OC_Appconfig::setValue('core', 'installedat', microtime(true)); + } + + $version = \OC_Util::getVersion(); + $version['installed'] = \OC_Appconfig::getValue('core', 'installedat'); + $version['updated'] = \OC_Appconfig::getValue('core', 'lastupdatedat'); + $version['updatechannel'] = 'stable'; + $version['edition'] = \OC_Util::getEditionString(); + $versionString = implode('x', $version); + + //fetch xml data from updater + $url = $updaterUrl . '?version=' . $versionString; + + // set a sensible timeout of 10 sec to stay responsive even if the update server is down. + $ctx = stream_context_create( + array( + 'http' => array( + 'timeout' => 10 + ) + ) + ); + $xml = @file_get_contents($url, 0, $ctx); + if ($xml == false) { + return array(); + } + $data = @simplexml_load_string($xml); + + $tmp = array(); + $tmp['version'] = $data->version; + $tmp['versionstring'] = $data->versionstring; + $tmp['url'] = $data->url; + $tmp['web'] = $data->web; + + // Cache the result + \OC_Appconfig::setValue('core', 'lastupdateResult', json_encode($data)); + + return $tmp; + } + + /** + * runs the update actions in maintenance mode, does not upgrade the source files + */ + public function upgrade() { + \OC_DB::enableCaching(false); + \OC_Config::setValue('maintenance', true); + $installedVersion = \OC_Config::getValue('version', '0.0.0'); + $currentVersion = implode('.', \OC_Util::getVersion()); + if ($this->log) { + $this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, array('app' => 'core')); + } + $this->emit('\OC\Updater', 'maintenanceStart'); + try { + \OC_DB::updateDbFromStructure(\OC::$SERVERROOT . '/db_structure.xml'); + $this->emit('\OC\Updater', 'dbUpgrade'); + + // do a file cache upgrade for users with files + // this can take loooooooooooooooooooooooong + $this->upgradeFileCache(); + } catch (\Exception $exception) { + $this->emit('\OC\Updater', 'failure', array($exception->getMessage())); + } + \OC_Config::setValue('version', implode('.', \OC_Util::getVersion())); + \OC_App::checkAppsRequirements(); + // load all apps to also upgrade enabled apps + \OC_App::loadApps(); + \OC_Config::setValue('maintenance', false); + $this->emit('\OC\Updater', 'maintenanceEnd'); + } + + private function upgradeFileCache() { + try { + $query = \OC_DB::prepare(' + SELECT DISTINCT `user` + FROM `*PREFIX*fscache` + '); + $result = $query->execute(); + } catch (\Exception $e) { + return; + } + $users = $result->fetchAll(); + if (count($users) == 0) { + return; + } + $step = 100 / count($users); + $percentCompleted = 0; + $lastPercentCompletedOutput = 0; + $startInfoShown = false; + foreach ($users as $userRow) { + $user = $userRow['user']; + \OC\Files\Filesystem::initMountPoints($user); + \OC\Files\Cache\Upgrade::doSilentUpgrade($user); + if (!$startInfoShown) { + //We show it only now, because otherwise Info about upgraded apps + //will appear between this and progress info + $this->emit('\OC\Updater', 'filecacheStart'); + $startInfoShown = true; + } + $percentCompleted += $step; + $out = floor($percentCompleted); + if ($out != $lastPercentCompletedOutput) { + $this->emit('\OC\Updater', 'filecacheProgress', array($out)); + $lastPercentCompletedOutput = $out; + } + } + $this->emit('\OC\Updater', 'filecacheDone'); + } +} diff --git a/lib/private/user.php b/lib/private/user.php new file mode 100644 index 00000000000..15e807088b4 --- /dev/null +++ b/lib/private/user.php @@ -0,0 +1,500 @@ +<?php +/** + * ownCloud + * + * @author Frank Karlitschek + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * This class provides wrapper methods for user management. Multiple backends are + * supported. User management operations are delegated to the configured backend for + * execution. + * + * 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 { + public static function getUserSession() { + return OC::$server->getUserSession(); + } + + /** + * @return \OC\User\Manager + */ + public static function getManager() { + return OC::$server->getUserManager(); + } + + private static $_backends = array(); + + private static $_usedBackends = array(); + + private static $_setupedBackends = array(); + + /** + * @brief registers backend + * @param string $backend name of the backend + * @deprecated Add classes by calling useBackend with a class instance instead + * @return bool + * + * Makes a list of backends that can be used by other modules + */ + public static function registerBackend($backend) { + self::$_backends[] = $backend; + return true; + } + + /** + * @brief gets available backends + * @deprecated + * @returns array of backends + * + * Returns the names of all backends. + */ + public static function getBackends() { + return self::$_backends; + } + + /** + * @brief gets used backends + * @deprecated + * @returns array of backends + * + * Returns the names of all used backends. + */ + public static function getUsedBackends() { + return array_keys(self::$_usedBackends); + } + + /** + * @brief Adds the backend to the list of used backends + * @param string | OC_User_Backend $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 OC_User_Interface) { + self::$_usedBackends[get_class($backend)] = $backend; + self::getManager()->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': + OC_Log::write('core', 'Adding user backend ' . $backend . '.', OC_Log::DEBUG); + self::$_usedBackends[$backend] = new OC_User_Database(); + self::getManager()->registerBackend(self::$_usedBackends[$backend]); + break; + default: + OC_Log::write('core', 'Adding default user backend ' . $backend . '.', OC_Log::DEBUG); + $className = 'OC_USER_' . strToUpper($backend); + self::$_usedBackends[$backend] = new $className(); + self::getManager()->registerBackend(self::$_usedBackends[$backend]); + break; + } + } + return true; + } + + /** + * remove all used backends + */ + public static function clearBackends() { + self::$_usedBackends = array(); + self::getManager()->clearBackends(); + } + + /** + * setup the configured backends in config.php + */ + public static function setupBackends() { + OC_App::loadApps(array('prelogin')); + $backends = OC_Config::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 { + OC_Log::write('core', 'User backend ' . $class . ' already initialized.', OC_Log::DEBUG); + } + } else { + OC_Log::write('core', 'User backend ' . $class . ' not found.', OC_Log::ERROR); + } + } + } + + /** + * @brief Create a new user + * @param string $uid The username of the user to create + * @param string $password The password of the new user + * @throws Exception + * @return bool true/false + * + * Creates a new user. Basic checking of username is done in OC_User + * itself, not in its subclasses. + * + * Allowed characters in the username are: "a-z", "A-Z", "0-9" and "_.@-" + */ + public static function createUser($uid, $password) { + return self::getManager()->createUser($uid, $password); + } + + /** + * @brief delete a user + * @param string $uid The username of the user to delete + * @return bool + * + * Deletes a user + */ + public static function deleteUser($uid) { + $user = self::getManager()->get($uid); + if ($user) { + $user->delete(); + + // We have to delete the user from all groups + foreach (OC_Group::getUserGroups($uid) as $i) { + OC_Group::removeFromGroup($uid, $i); + } + // Delete the user's keys in preferences + OC_Preferences::deleteUser($uid); + + // Delete user files in /data/ + OC_Helper::rmdirr(OC_Config::getValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $uid . '/'); + } + } + + /** + * @brief Try to login a user + * @param $uid The username of the user to log in + * @param $password The password of the user + * @return bool + * + * Log in a user and regenerate a new session - if the password is ok + */ + public static function login($uid, $password) { + return self::getUserSession()->login($uid, $password); + } + + /** + * @brief Sets user id for session and triggers emit + */ + public static function setUserId($uid) { + OC::$session->set('user_id', $uid); + } + + /** + * @brief Sets user display name for session + */ + public static function setDisplayName($uid, $displayName = null) { + if (is_null($displayName)) { + $displayName = $uid; + } + $user = self::getManager()->get($uid); + if ($user) { + return $user->setDisplayName($displayName); + } else { + return false; + } + } + + /** + * @brief Logs the current user out and kills all the session data + * + * Logout, destroys session + */ + public static function logout() { + self::getUserSession()->logout(); + } + + /** + * @brief Check if the user is logged in + * @returns bool + * + * Checks if the user is logged in + */ + public static function isLoggedIn() { + if (\OC::$session->get('user_id')) { + OC_App::loadApps(array('authentication')); + self::setupBackends(); + return self::userExists(\OC::$session->get('user_id')); + } + return false; + } + + /** + * @brief 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')) { + return true; + } + return false; + } + + + /** + * @brief get the user id of the user currently logged in. + * @return string uid or false + */ + public static function getUser() { + $uid = OC::$session ? OC::$session->get('user_id') : null; + if (!is_null($uid)) { + return $uid; + } else { + return false; + } + } + + /** + * @brief 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 = self::getManager()->get($uid); + if ($user) { + return $user->getDisplayName(); + } else { + return $uid; + } + } else { + $user = self::getUserSession()->getUser(); + if ($user) { + return $user->getDisplayName(); + } else { + return false; + } + } + } + + /** + * @brief Autogenerate a password + * @return string + * + * generates a password + */ + public static function generatePassword() { + return OC_Util::generateRandomBytes(30); + } + + /** + * @brief 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 = self::getManager()->get($uid); + if ($user) { + return $user->setPassword($password, $recoveryPassword); + } else { + return false; + } + } + + /** + * @brief 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 = self::getManager()->get($uid); + if ($user) { + return $user->canChangePassword(); + } else { + return false; + } + } + + /** + * @brief 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 = self::getManager()->get($uid); + if ($user) { + return $user->canChangeDisplayName(); + } else { + return false; + } + } + + /** + * @brief Check if the password is correct + * @param string $uid The username + * @param string $password The password + * @return mixed 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 = self::getManager(); + $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 + */ + public static function getHome($uid) { + $user = self::getManager()->get($uid); + if ($user) { + return $user->getHome(); + } else { + return OC_Config::getValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $uid; + } + } + + /** + * @brief Get a list of all users + * @returns array with all uids + * + * Get a list of all users. + */ + public static function getUsers($search = '', $limit = null, $offset = null) { + $users = self::getManager()->search($search, $limit, $offset); + $uids = array(); + foreach ($users as $user) { + $uids[] = $user->getUID(); + } + return $uids; + } + + /** + * @brief 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. + */ + public static function getDisplayNames($search = '', $limit = null, $offset = null) { + $displayNames = array(); + $users = self::getManager()->searchDisplayName($search, $limit, $offset); + foreach ($users as $user) { + $displayNames[$user->getUID()] = $user->getDisplayName(); + } + return $displayNames; + } + + /** + * @brief check if a user exists + * @param string $uid the username + * @return boolean + */ + public static function userExists($uid) { + return self::getManager()->userExists($uid); + } + + /** + * disables a user + * + * @param string $uid the user to disable + */ + public static function disableUser($uid) { + $user = self::getManager()->get($uid); + if ($user) { + $user->setEnabled(false); + } + } + + /** + * enable a user + * + * @param string $uid + */ + public static function enableUser($uid) { + $user = self::getManager()->get($uid); + if ($user) { + $user->setEnabled(true); + } + } + + /** + * checks if a user is enabled + * + * @param string $uid + * @return bool + */ + public static function isEnabled($uid) { + $user = self::getManager()->get($uid); + if ($user) { + return $user->isEnabled(); + } else { + return false; + } + } + + /** + * @brief 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); + } + + /** + * @brief Remove cookie for "remember username" + */ + public static function unsetMagicInCookie() { + self::getUserSession()->unsetMagicInCookie(); + } +} diff --git a/lib/private/user/backend.php b/lib/private/user/backend.php new file mode 100644 index 00000000000..e9be08e429c --- /dev/null +++ b/lib/private/user/backend.php @@ -0,0 +1,159 @@ +<?php + +/** + * ownCloud + * + * @author Frank Karlitschek + * @author Dominik Schmidt + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * @copyright 2011 Dominik Schmidt dev@dominik-schmidt.de + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * error code for functions not provided by the user backend + */ +define('OC_USER_BACKEND_NOT_IMPLEMENTED', -501); + +/** + * actions that user backends can define + */ +define('OC_USER_BACKEND_CREATE_USER', 0x000001); +define('OC_USER_BACKEND_SET_PASSWORD', 0x000010); +define('OC_USER_BACKEND_CHECK_PASSWORD', 0x000100); +define('OC_USER_BACKEND_GET_HOME', 0x001000); +define('OC_USER_BACKEND_GET_DISPLAYNAME', 0x010000); +define('OC_USER_BACKEND_SET_DISPLAYNAME', 0x100000); + + +/** + * Abstract base class for user management. Provides methods for querying backend + * capabilities. + * + * Subclass this for your own backends, and see OC_User_Example for descriptions + */ +abstract class OC_User_Backend implements OC_User_Interface { + + protected $possibleActions = array( + OC_USER_BACKEND_CREATE_USER => 'createUser', + OC_USER_BACKEND_SET_PASSWORD => 'setPassword', + OC_USER_BACKEND_CHECK_PASSWORD => 'checkPassword', + OC_USER_BACKEND_GET_HOME => 'getHome', + OC_USER_BACKEND_GET_DISPLAYNAME => 'getDisplayName', + OC_USER_BACKEND_SET_DISPLAYNAME => 'setDisplayName', + ); + + /** + * @brief Get all supported actions + * @return int bitwise-or'ed actions + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function getSupportedActions() { + $actions = 0; + foreach($this->possibleActions AS $action => $methodName) { + if(method_exists($this, $methodName)) { + $actions |= $action; + } + } + + return $actions; + } + + /** + * @brief Check if backend implements actions + * @param int $actions bitwise-or'ed actions + * @return boolean + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function implementsActions($actions) { + return (bool)($this->getSupportedActions() & $actions); + } + + /** + * @brief delete a user + * @param string $uid The username of the user to delete + * @return bool + * + * Deletes a user + */ + public function deleteUser( $uid ) { + return false; + } + + /** + * @brief Get a list of all users + * @returns array with all uids + * + * Get a list of all users. + */ + public function getUsers($search = '', $limit = null, $offset = null) { + return array(); + } + + /** + * @brief check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid) { + return false; + } + + /** + * @brief get the user's home directory + * @param string $uid the username + * @return boolean + */ + public function getHome($uid) { + return false; + } + + /** + * @brief get display name of the user + * @param string $uid user ID of the user + * @return string display name + */ + public function getDisplayName($uid) { + return $uid; + } + + /** + * @brief Get a list of all display names + * @returns array with all displayNames (value) and the corresponding uids (key) + * + * Get a list of all display names and user ids. + */ + public function getDisplayNames($search = '', $limit = null, $offset = null) { + $displayNames = array(); + $users = $this->getUsers($search, $limit, $offset); + foreach ( $users as $user) { + $displayNames[$user] = $user; + } + return $displayNames; + } + + /** + * @brief Check if a user list is available or not + * @return boolean if users can be listed or not + */ + public function hasUserListings() { + return false; + } +} diff --git a/lib/private/user/database.php b/lib/private/user/database.php new file mode 100644 index 00000000000..9f00a022d9f --- /dev/null +++ b/lib/private/user/database.php @@ -0,0 +1,269 @@ +<?php + +/** + * ownCloud + * + * @author Frank Karlitschek + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +/* + * + * The following SQL statement is just a help for developers and will not be + * executed! + * + * CREATE TABLE `users` ( + * `uid` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + * `password` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + * PRIMARY KEY (`uid`) + * ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + * + */ + +require_once 'phpass/PasswordHash.php'; + +/** + * Class for user management in a SQL Database (e.g. MySQL, SQLite) + */ +class OC_User_Database extends OC_User_Backend { + /** + * @var PasswordHash + */ + static private $hasher=null; + + private function getHasher() { + if(!self::$hasher) { + //we don't want to use DES based crypt(), since it doesn't return a hash with a recognisable prefix + $forcePortable=(CRYPT_BLOWFISH!=1); + self::$hasher=new PasswordHash(8, $forcePortable); + } + return self::$hasher; + + } + + /** + * @brief Create a new user + * @param $uid The username of the user to create + * @param $password The password of the new user + * @returns true/false + * + * Creates a new user. Basic checking of username is done in OC_User + * itself, not in its subclasses. + */ + public function createUser( $uid, $password ) { + if( $this->userExists($uid) ) { + return false; + }else{ + $hasher=$this->getHasher(); + $hash = $hasher->HashPassword($password.OC_Config::getValue('passwordsalt', '')); + $query = OC_DB::prepare( 'INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ?, ? )' ); + $result = $query->execute( array( $uid, $hash)); + + return $result ? true : false; + } + } + + /** + * @brief delete a user + * @param $uid The username of the user to delete + * @returns true/false + * + * Deletes a user + */ + public function deleteUser( $uid ) { + // Delete user-group-relation + $query = OC_DB::prepare( 'DELETE FROM `*PREFIX*users` WHERE `uid` = ?' ); + $query->execute( array( $uid )); + return true; + } + + /** + * @brief Set password + * @param $uid The username + * @param $password The new password + * @returns true/false + * + * Change the password of a user + */ + public function setPassword( $uid, $password ) { + if( $this->userExists($uid) ) { + $hasher=$this->getHasher(); + $hash = $hasher->HashPassword($password.OC_Config::getValue('passwordsalt', '')); + $query = OC_DB::prepare( 'UPDATE `*PREFIX*users` SET `password` = ? WHERE `uid` = ?' ); + $query->execute( array( $hash, $uid )); + + return true; + }else{ + return false; + } + } + + /** + * @brief Set display name + * @param $uid The username + * @param $displayName The new display name + * @returns true/false + * + * Change the display name of a user + */ + public function setDisplayName( $uid, $displayName ) { + if( $this->userExists($uid) ) { + $query = OC_DB::prepare( 'UPDATE `*PREFIX*users` SET `displayname` = ? WHERE `uid` = ?' ); + $query->execute( array( $displayName, $uid )); + return true; + }else{ + return false; + } + } + + /** + * @brief get display name of the user + * @param $uid user ID of the user + * @return display name + */ + public function getDisplayName($uid) { + if( $this->userExists($uid) ) { + $query = OC_DB::prepare( 'SELECT `displayname` FROM `*PREFIX*users` WHERE `uid` = ?' ); + $result = $query->execute( array( $uid ))->fetchAll(); + $displayName = trim($result[0]['displayname'], ' '); + if ( !empty($displayName) ) { + return $displayName; + } else { + return $uid; + } + } + } + + /** + * @brief Get a list of all display names + * @returns array with all displayNames (value) and the correspondig uids (key) + * + * Get a list of all display names and user ids. + */ + public function getDisplayNames($search = '', $limit = null, $offset = null) { + $displayNames = array(); + $query = OC_DB::prepare('SELECT `uid`, `displayname` FROM `*PREFIX*users`' + .' WHERE LOWER(`displayname`) LIKE LOWER(?)', $limit, $offset); + $result = $query->execute(array($search.'%')); + $users = array(); + while ($row = $result->fetchRow()) { + $displayNames[$row['uid']] = $row['displayname']; + } + + // let's see if we can also find some users who don't have a display name yet + $query = OC_DB::prepare('SELECT `uid`, `displayname` FROM `*PREFIX*users`' + .' WHERE LOWER(`uid`) LIKE LOWER(?)', $limit, $offset); + $result = $query->execute(array($search.'%')); + while ($row = $result->fetchRow()) { + $displayName = trim($row['displayname'], ' '); + if ( empty($displayName) ) { + $displayNames[$row['uid']] = $row['uid']; + } + } + + + return $displayNames; + } + + /** + * @brief Check if the password is correct + * @param $uid The username + * @param $password The password + * @returns string + * + * Check if the password is correct without logging in the user + * returns the user id or false + */ + public function checkPassword( $uid, $password ) { + $query = OC_DB::prepare( 'SELECT `uid`, `password` FROM `*PREFIX*users` WHERE LOWER(`uid`) = LOWER(?)' ); + $result = $query->execute( array( $uid)); + + $row=$result->fetchRow(); + if($row) { + $storedHash=$row['password']; + if ($storedHash[0]=='$') {//the new phpass based hashing + $hasher=$this->getHasher(); + if($hasher->CheckPassword($password.OC_Config::getValue('passwordsalt', ''), $storedHash)) { + return $row['uid']; + }else{ + return false; + } + }else{//old sha1 based hashing + if(sha1($password)==$storedHash) { + //upgrade to new hashing + $this->setPassword($row['uid'], $password); + return $row['uid']; + }else{ + return false; + } + } + }else{ + return false; + } + } + + /** + * @brief Get a list of all users + * @returns array with all uids + * + * Get a list of all users. + */ + public function getUsers($search = '', $limit = null, $offset = null) { + $query = OC_DB::prepare('SELECT `uid` FROM `*PREFIX*users` WHERE LOWER(`uid`) LIKE LOWER(?)', $limit, $offset); + $result = $query->execute(array($search.'%')); + $users = array(); + while ($row = $result->fetchRow()) { + $users[] = $row['uid']; + } + return $users; + } + + /** + * @brief check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid) { + $query = OC_DB::prepare( 'SELECT COUNT(*) FROM `*PREFIX*users` WHERE LOWER(`uid`) = LOWER(?)' ); + $result = $query->execute( array( $uid )); + if (OC_DB::isError($result)) { + OC_Log::write('core', OC_DB::getErrorMessage($result), OC_Log::ERROR); + return false; + } + return $result->fetchOne() > 0; + } + + /** + * @brief get the user's home directory + * @param string $uid the username + * @return boolean + */ + public function getHome($uid) { + if($this->userExists($uid)) { + return OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ) . '/' . $uid; + }else{ + return false; + } + } + + /** + * @return bool + */ + public function hasUserListings() { + return true; + } + +} diff --git a/lib/private/user/dummy.php b/lib/private/user/dummy.php new file mode 100644 index 00000000000..b5b7a6c3c7a --- /dev/null +++ b/lib/private/user/dummy.php @@ -0,0 +1,126 @@ +<?php + +/** + * ownCloud + * + * @author Frank Karlitschek + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * dummy user backend, does not keep state, only for testing use + */ +class OC_User_Dummy extends OC_User_Backend { + private $users = array(); + + /** + * @brief Create a new user + * @param string $uid The username of the user to create + * @param string $password The password of the new user + * @return bool + * + * Creates a new user. Basic checking of username is done in OC_User + * itself, not in its subclasses. + */ + public function createUser($uid, $password) { + if (isset($this->users[$uid])) { + return false; + } else { + $this->users[$uid] = $password; + return true; + } + } + + /** + * @brief delete a user + * @param string $uid The username of the user to delete + * @return bool + * + * Deletes a user + */ + public function deleteUser($uid) { + if (isset($this->users[$uid])) { + unset($this->users[$uid]); + return true; + } else { + return false; + } + } + + /** + * @brief Set password + * @param string $uid The username + * @param string $password The new password + * @return bool + * + * Change the password of a user + */ + public function setPassword($uid, $password) { + if (isset($this->users[$uid])) { + $this->users[$uid] = $password; + return true; + } else { + return false; + } + } + + /** + * @brief Check if the password is correct + * @param string $uid The username + * @param string $password The password + * @return string + * + * Check if the password is correct without logging in the user + * returns the user id or false + */ + public function checkPassword($uid, $password) { + if (isset($this->users[$uid])) { + return ($this->users[$uid] == $password); + } else { + return false; + } + } + + /** + * @brief Get a list of all users + * @param string $search + * @param int $limit + * @param int $offset + * @return array with all uids + * + * Get a list of all users. + */ + public function getUsers($search = '', $limit = null, $offset = null) { + return array_keys($this->users); + } + + /** + * @brief check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid) { + return isset($this->users[$uid]); + } + + /** + * @return bool + */ + public function hasUserListings() { + return true; + } +} diff --git a/lib/private/user/example.php b/lib/private/user/example.php new file mode 100644 index 00000000000..b2d0dc25410 --- /dev/null +++ b/lib/private/user/example.php @@ -0,0 +1,70 @@ +<?php + +/** + * ownCloud + * + * @author Frank Karlitschek + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * abstract reference class for user management + * this class should only be used as a reference for method signatures and their descriptions + */ +abstract class OC_User_Example extends OC_User_Backend { + /** + * @brief Create a new user + * @param $uid The username of the user to create + * @param $password The password of the new user + * @returns true/false + * + * Creates a new user. Basic checking of username is done in OC_User + * itself, not in its subclasses. + */ + abstract public function createUser($uid, $password); + + /** + * @brief Set password + * @param $uid The username + * @param $password The new password + * @returns true/false + * + * Change the password of a user + */ + abstract public function setPassword($uid, $password); + + /** + * @brief Check if the password is correct + * @param $uid The username + * @param $password The password + * @returns string + * + * Check if the password is correct without logging in the user + * returns the user id or false + */ + abstract public function checkPassword($uid, $password); + + /** + * @brief get the user's home directory + * @param $uid The username + * @returns string + * + * get the user's home directory + * returns the path or false + */ + abstract public function getHome($uid); +} diff --git a/lib/private/user/http.php b/lib/private/user/http.php new file mode 100644 index 00000000000..e99afe59ba7 --- /dev/null +++ b/lib/private/user/http.php @@ -0,0 +1,110 @@ +<?php + +/** +* ownCloud +* +* @author Frank Karlitschek +* @copyright 2012 Robin Appelman icewind@owncloud.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +/** + * user backend using http auth requests + */ +class OC_User_HTTP extends OC_User_Backend { + /** + * split http://user@host/path into a user and url part + * @param string path + * @return array + */ + private function parseUrl($url) { + $parts=parse_url($url); + $url=$parts['scheme'].'://'.$parts['host']; + if(isset($parts['port'])) { + $url.=':'.$parts['port']; + } + $url.=$parts['path']; + if(isset($parts['query'])) { + $url.='?'.$parts['query']; + } + return array($parts['user'], $url); + + } + + /** + * check if an url is a valid login + * @param string url + * @return boolean + */ + private function matchUrl($url) { + return ! is_null(parse_url($url, PHP_URL_USER)); + } + + /** + * @brief Check if the password is correct + * @param $uid The username + * @param $password The password + * @returns string + * + * Check if the password is correct without logging in the user + * returns the user id or false + */ + public function checkPassword($uid, $password) { + if(!$this->matchUrl($uid)) { + return false; + } + list($user, $url)=$this->parseUrl($uid); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_USERPWD, $user.':'.$password); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + curl_exec($ch); + + $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + curl_close($ch); + + if($status === 200) { + return $uid; + } + + return false; + } + + /** + * @brief check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid) { + return $this->matchUrl($uid); + } + + /** + * @brief get the user's home directory + * @param string $uid the username + * @return boolean + */ + public function getHome($uid) { + if($this->userExists($uid)) { + return OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ) . '/' . $uid; + }else{ + return false; + } + } +} diff --git a/lib/private/user/interface.php b/lib/private/user/interface.php new file mode 100644 index 00000000000..c72bdfaf3fd --- /dev/null +++ b/lib/private/user/interface.php @@ -0,0 +1,80 @@ +<?php + +/** + * ownCloud - user interface + * + * @author Arthur Schiwon + * @copyright 2012 Arthur Schiwon blizzz@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +interface OC_User_Interface { + + /** + * @brief Check if backend implements actions + * @param $actions bitwise-or'ed actions + * @returns boolean + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function implementsActions($actions); + + /** + * @brief delete a user + * @param $uid The username of the user to delete + * @returns true/false + * + * Deletes a user + */ + public function deleteUser($uid); + + /** + * @brief Get a list of all users + * @returns array with all uids + * + * Get a list of all users. + */ + public function getUsers($search = '', $limit = null, $offset = null); + + /** + * @brief check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid); + + /** + * @brief get display name of the user + * @param $uid user ID of the user + * @return display name + */ + public function getDisplayName($uid); + + /** + * @brief Get a list of all display names + * @returns array with all displayNames (value) and the corresponding uids (key) + * + * Get a list of all display names and user ids. + */ + public function getDisplayNames($search = '', $limit = null, $offset = null); + + /** + * @brief Check if a user list is available or not + * @return boolean if users can be listed or not + */ + public function hasUserListings(); +} diff --git a/lib/private/user/manager.php b/lib/private/user/manager.php new file mode 100644 index 00000000000..13286bc28a4 --- /dev/null +++ b/lib/private/user/manager.php @@ -0,0 +1,250 @@ +<?php + +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\User; + +use OC\Hooks\PublicEmitter; + +/** + * Class Manager + * + * Hooks available in scope \OC\User: + * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - preDelete(\OC\User\User $user) + * - postDelete(\OC\User\User $user) + * - preCreateUser(string $uid, string $password) + * - postCreateUser(\OC\User\User $user, string $password) + * + * @package OC\User + */ +class Manager extends PublicEmitter { + /** + * @var \OC_User_Backend[] $backends + */ + private $backends = array(); + + /** + * @var \OC\User\User[] $cachedUsers + */ + private $cachedUsers = array(); + + public function __construct() { + $cachedUsers = $this->cachedUsers; + $this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) { + $i = array_search($user, $cachedUsers); + if ($i !== false) { + unset($cachedUsers[$i]); + } + }); + } + + /** + * register a user backend + * + * @param \OC_User_Backend $backend + */ + public function registerBackend($backend) { + $this->backends[] = $backend; + } + + /** + * remove a user backend + * + * @param \OC_User_Backend $backend + */ + public function removeBackend($backend) { + $this->cachedUsers = array(); + if (($i = array_search($backend, $this->backends)) !== false) { + unset($this->backends[$i]); + } + } + + /** + * remove all user backends + */ + public function clearBackends() { + $this->cachedUsers = array(); + $this->backends = array(); + } + + /** + * get a user by user id + * + * @param string $uid + * @return \OC\User\User + */ + public function get($uid) { + if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends + return $this->cachedUsers[$uid]; + } + foreach ($this->backends as $backend) { + if ($backend->userExists($uid)) { + return $this->getUserObject($uid, $backend); + } + } + return null; + } + + /** + * get or construct the user object + * + * @param string $uid + * @param \OC_User_Backend $backend + * @return \OC\User\User + */ + protected function getUserObject($uid, $backend) { + if (isset($this->cachedUsers[$uid])) { + return $this->cachedUsers[$uid]; + } + $this->cachedUsers[$uid] = new User($uid, $backend, $this); + return $this->cachedUsers[$uid]; + } + + /** + * check if a user exists + * + * @param string $uid + * @return bool + */ + public function userExists($uid) { + $user = $this->get($uid); + return ($user !== null); + } + + /** + * Check if the password is valid for the user + * + * @param $loginname + * @param $password + * @return mixed the User object on success, false otherwise + */ + public function checkPassword($loginname, $password) { + foreach ($this->backends as $backend) { + if($backend->implementsActions(\OC_USER_BACKEND_CHECK_PASSWORD)) { + $uid = $backend->checkPassword($loginname, $password); + if ($uid !== false) { + return $this->getUserObject($uid, $backend); + } + } + } + return false; + } + + /** + * search by user id + * + * @param string $pattern + * @param int $limit + * @param int $offset + * @return \OC\User\User[] + */ + public function search($pattern, $limit = null, $offset = null) { + $users = array(); + foreach ($this->backends as $backend) { + $backendUsers = $backend->getUsers($pattern, $limit, $offset); + if (is_array($backendUsers)) { + foreach ($backendUsers as $uid) { + $users[] = $this->getUserObject($uid, $backend); + if (!is_null($limit)) { + $limit--; + } + if (!is_null($offset) and $offset > 0) { + $offset--; + } + + } + } + } + + usort($users, function ($a, $b) { + /** + * @var \OC\User\User $a + * @var \OC\User\User $b + */ + return strcmp($a->getUID(), $b->getUID()); + }); + return $users; + } + + /** + * search by displayName + * + * @param string $pattern + * @param int $limit + * @param int $offset + * @return \OC\User\User[] + */ + public function searchDisplayName($pattern, $limit = null, $offset = null) { + $users = array(); + foreach ($this->backends as $backend) { + $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset); + if (is_array($backendUsers)) { + foreach ($backendUsers as $uid => $displayName) { + $users[] = $this->getUserObject($uid, $backend); + if (!is_null($limit)) { + $limit--; + } + if (!is_null($offset) and $offset > 0) { + $offset--; + } + + } + } + } + + usort($users, function ($a, $b) { + /** + * @var \OC\User\User $a + * @var \OC\User\User $b + */ + return strcmp($a->getDisplayName(), $b->getDisplayName()); + }); + return $users; + } + + /** + * @param string $uid + * @param string $password + * @throws \Exception + * @return bool | \OC\User\User the created user of false + */ + public function createUser($uid, $password) { + // Check the name for bad characters + // Allowed are: "a-z", "A-Z", "0-9" and "_.@-" + if (preg_match('/[^a-zA-Z0-9 _\.@\-]/', $uid)) { + throw new \Exception('Only the following characters are allowed in a username:' + . ' "a-z", "A-Z", "0-9", and "_.@-"'); + } + // No empty username + if (trim($uid) == '') { + throw new \Exception('A valid username must be provided'); + } + // No empty password + if (trim($password) == '') { + throw new \Exception('A valid password must be provided'); + } + + // Check if user already exists + if ($this->userExists($uid)) { + throw new \Exception('The username is already being used'); + } + + $this->emit('\OC\User', 'preCreateUser', array($uid, $password)); + foreach ($this->backends as $backend) { + if ($backend->implementsActions(\OC_USER_BACKEND_CREATE_USER)) { + $backend->createUser($uid, $password); + $user = $this->getUserObject($uid, $backend); + $this->emit('\OC\User', 'postCreateUser', array($user, $password)); + return $user; + } + } + return false; + } +} diff --git a/lib/private/user/session.php b/lib/private/user/session.php new file mode 100644 index 00000000000..525c65ab8a1 --- /dev/null +++ b/lib/private/user/session.php @@ -0,0 +1,174 @@ +<?php + +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\User; + +use OC\Hooks\Emitter; + +/** + * Class Session + * + * Hooks available in scope \OC\User: + * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - preDelete(\OC\User\User $user) + * - postDelete(\OC\User\User $user) + * - preCreateUser(string $uid, string $password) + * - postCreateUser(\OC\User\User $user) + * - preLogin(string $user, string $password) + * - postLogin(\OC\User\User $user) + * - logout() + * + * @package OC\User + */ +class Session implements Emitter, \OCP\IUserSession { + /** + * @var \OC\User\Manager $manager + */ + private $manager; + + /** + * @var \OC\Session\Session $session + */ + private $session; + + /** + * @var \OC\User\User $activeUser + */ + protected $activeUser; + + /** + * @param \OC\User\Manager $manager + * @param \OC\Session\Session $session + */ + public function __construct($manager, $session) { + $this->manager = $manager; + $this->session = $session; + } + + /** + * @param string $scope + * @param string $method + * @param callable $callback + */ + public function listen($scope, $method, $callback) { + $this->manager->listen($scope, $method, $callback); + } + + /** + * @param string $scope optional + * @param string $method optional + * @param callable $callback optional + */ + public function removeListener($scope = null, $method = null, $callback = null) { + $this->manager->removeListener($scope, $method, $callback); + } + + /** + * get the manager object + * + * @return \OC\User\Manager + */ + public function getManager() { + return $this->manager; + } + + /** + * set the currently active user + * + * @param \OC\User\User $user + */ + public function setUser($user) { + if (is_null($user)) { + $this->session->remove('user_id'); + } else { + $this->session->set('user_id', $user->getUID()); + } + $this->activeUser = $user; + } + + /** + * get the current active user + * + * @return \OC\User\User + */ + public function getUser() { + if ($this->activeUser) { + return $this->activeUser; + } else { + $uid = $this->session->get('user_id'); + if ($uid) { + $this->activeUser = $this->manager->get($uid); + return $this->activeUser; + } else { + return null; + } + } + } + + /** + * try to login with the provided credentials + * + * @param string $uid + * @param string $password + * @return bool + */ + public function login($uid, $password) { + $this->manager->emit('\OC\User', 'preLogin', array($uid, $password)); + $user = $this->manager->checkPassword($uid, $password); + if($user !== false) { + if (!is_null($user)) { + if ($user->isEnabled()) { + $this->setUser($user); + $this->manager->emit('\OC\User', 'postLogin', array($user, $password)); + return true; + } else { + return false; + } + } + } else { + return false; + } + } + + /** + * logout the user from the session + */ + public function logout() { + $this->manager->emit('\OC\User', 'logout'); + $this->setUser(null); + $this->unsetMagicInCookie(); + } + + /** + * Set cookie value to use in next page load + * + * @param string $username username to be set + * @param string $token + */ + public function setMagicInCookie($username, $token) { + $secure_cookie = \OC_Config::getValue("forcessl", false); //TODO: DI for cookies and OC_Config + $expires = time() + \OC_Config::getValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); + setcookie("oc_username", $username, $expires, \OC::$WEBROOT, '', $secure_cookie); + setcookie("oc_token", $token, $expires, \OC::$WEBROOT, '', $secure_cookie, true); + setcookie("oc_remember_login", true, $expires, \OC::$WEBROOT, '', $secure_cookie); + } + + /** + * Remove cookie for "remember username" + */ + public function unsetMagicInCookie() { + unset($_COOKIE["oc_username"]); //TODO: DI + unset($_COOKIE["oc_token"]); + unset($_COOKIE["oc_remember_login"]); + setcookie('oc_username', '', time()-3600, \OC::$WEBROOT); + setcookie('oc_token', '', time()-3600, \OC::$WEBROOT); + setcookie('oc_remember_login', '', time()-3600, \OC::$WEBROOT); + } +} diff --git a/lib/private/user/user.php b/lib/private/user/user.php new file mode 100644 index 00000000000..e5f842944f1 --- /dev/null +++ b/lib/private/user/user.php @@ -0,0 +1,179 @@ +<?php + +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\User; + +use OC\Hooks\Emitter; + +class User { + /** + * @var string $uid + */ + private $uid; + + /** + * @var string $displayName + */ + private $displayName; + + /** + * @var \OC_User_Backend $backend + */ + private $backend; + + /** + * @var bool $enabled + */ + private $enabled; + + /** + * @var Emitter | Manager $emitter + */ + private $emitter; + + /** + * @param string $uid + * @param \OC_User_Backend $backend + * @param Emitter $emitter + */ + public function __construct($uid, $backend, $emitter = null) { + $this->uid = $uid; + if ($backend and $backend->implementsActions(OC_USER_BACKEND_GET_DISPLAYNAME)) { + $this->displayName = $backend->getDisplayName($uid); + } else { + $this->displayName = $uid; + } + $this->backend = $backend; + $this->emitter = $emitter; + $enabled = \OC_Preferences::getValue($uid, 'core', 'enabled', 'true'); //TODO: DI for OC_Preferences + $this->enabled = ($enabled === 'true'); + } + + /** + * get the user id + * + * @return string + */ + public function getUID() { + return $this->uid; + } + + /** + * get the displayname for the user, if no specific displayname is set it will fallback to the user id + * + * @return string + */ + public function getDisplayName() { + return $this->displayName; + } + + /** + * set the displayname for the user + * + * @param string $displayName + * @return bool + */ + public function setDisplayName($displayName) { + if ($this->canChangeDisplayName()) { + $this->displayName = $displayName; + $result = $this->backend->setDisplayName($this->uid, $displayName); + return $result !== false; + } else { + return false; + } + } + + /** + * Delete the user + * + * @return bool + */ + public function delete() { + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'preDelete', array($this)); + } + $result = $this->backend->deleteUser($this->uid); + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'postDelete', array($this)); + } + return !($result === false); + } + + /** + * Set the password of the user + * + * @param string $password + * @param string $recoveryPassword for the encryption app to reset encryption keys + * @return bool + */ + public function setPassword($password, $recoveryPassword) { + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'preSetPassword', array($this, $password, $recoveryPassword)); + } + if ($this->backend->implementsActions(\OC_USER_BACKEND_SET_PASSWORD)) { + $result = $this->backend->setPassword($this->uid, $password); + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'postSetPassword', array($this, $password, $recoveryPassword)); + } + return !($result === false); + } else { + return false; + } + } + + /** + * get the users home folder to mount + * + * @return string + */ + public function getHome() { + if ($this->backend->implementsActions(\OC_USER_BACKEND_GET_HOME) and $home = $this->backend->getHome($this->uid)) { + return $home; + } + return \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data") . '/' . $this->uid; //TODO switch to Config object once implemented + } + + /** + * check if the backend supports changing passwords + * + * @return bool + */ + public function canChangePassword() { + return $this->backend->implementsActions(\OC_USER_BACKEND_SET_PASSWORD); + } + + /** + * check if the backend supports changing display names + * + * @return bool + */ + public function canChangeDisplayName() { + return $this->backend->implementsActions(\OC_USER_BACKEND_SET_DISPLAYNAME); + } + + /** + * check if the user is enabled + * + * @return bool + */ + public function isEnabled() { + return $this->enabled; + } + + /** + * set the enabled status for the user + * + * @param bool $enabled + */ + public function setEnabled($enabled) { + $this->enabled = $enabled; + $enabled = ($enabled) ? 'true' : 'false'; + \OC_Preferences::setValue($this->uid, 'core', 'enabled', $enabled); + } +} diff --git a/lib/private/util.php b/lib/private/util.php new file mode 100755 index 00000000000..6be56d07c9a --- /dev/null +++ b/lib/private/util.php @@ -0,0 +1,1019 @@ +<?php + +/** + * Class for utility functions + * + */ + +class OC_Util { + public static $scripts=array(); + public static $styles=array(); + public static $headers=array(); + private static $rootMounted=false; + private static $fsSetup=false; + public static $coreStyles=array(); + public static $coreScripts=array(); + + /** + * @brief 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; + } + + // If we are not forced to load a specific user we load the one that is logged in + if( $user == "" && OC_User::isLoggedIn()) { + $user = OC_User::getUser(); + } + + // load all filesystem apps before, so no setup-hook gets lost + if(!isset($RUNTIME_NOAPPS) || !$RUNTIME_NOAPPS) { + 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; + } + + $configDataDirectory = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); + //first set up the local "root" storage + \OC\Files\Filesystem::initMounts(); + if(!self::$rootMounted) { + \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir'=>$configDataDirectory), '/'); + self::$rootMounted = true; + } + + //if we aren't logged in, there is no use to set up the filesystem + if( $user != "" ) { + $quota = self::getUserQuota($user); + if ($quota !== \OC\Files\SPACE_UNLIMITED) { + \OC\Files\Filesystem::addStorageWrapper(function($mountPoint, $storage) use ($quota, $user) { + if ($mountPoint === '/' . $user . '/'){ + return new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => $quota)); + } else { + return $storage; + } + }); + } + $userDir = '/'.$user.'/files'; + $userRoot = OC_User::getHome($user); + $userDirectory = $userRoot . '/files'; + if( !is_dir( $userDirectory )) { + mkdir( $userDirectory, 0755, true ); + } + //jail the user into his "home" directory + \OC\Files\Filesystem::init($user, $userDir); + + $fileOperationProxy = new OC_FileProxy_FileOperations(); + OC_FileProxy::register($fileOperationProxy); + + OC_Hook::emit('OC_Filesystem', 'setup', array('user' => $user, 'user_dir' => $userDir)); + } + return true; + } + + public static function getUserQuota($user){ + $userQuota = OC_Preferences::getValue($user, 'files', 'quota', 'default'); + if($userQuota === 'default') { + $userQuota = OC_AppConfig::getValue('files', 'default_quota', 'none'); + } + if($userQuota === 'none') { + return \OC\Files\SPACE_UNLIMITED; + }else{ + return OC_Helper::computerFileSize($userQuota); + } + } + + /** + * @return void + */ + public static function tearDownFS() { + \OC\Files\Filesystem::tearDown(); + self::$fsSetup=false; + self::$rootMounted=false; + } + + /** + * @brief get the current installed version of ownCloud + * @return array + */ + public static function getVersion() { + // hint: We only can count up. Reset minor/patchlevel when + // updating major/minor version number. + return array(5, 80, 07); + } + + /** + * @brief get the current installed version string of ownCloud + * @return string + */ + public static function getVersionString() { + return '6.0 pre alpha'; + } + + /** + * @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() { + return ''; + } + + /** + * @brief add a javascript file + * + * @param string $application + * @param filename $file + * @return void + */ + public static function addScript( $application, $file = null ) { + if ( is_null( $file )) { + $file = $application; + $application = ""; + } + if ( !empty( $application )) { + self::$scripts[] = "$application/js/$file"; + } else { + self::$scripts[] = "js/$file"; + } + } + + /** + * @brief add a css file + * + * @param string $application + * @param filename $file + * @return void + */ + public static function addStyle( $application, $file = null ) { + if ( is_null( $file )) { + $file = $application; + $application = ""; + } + if ( !empty( $application )) { + self::$styles[] = "$application/css/$file"; + } else { + self::$styles[] = "css/$file"; + } + } + + /** + * @brief 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 + * @return void + */ + public static function addHeader( $tag, $attributes, $text='') { + self::$headers[] = array( + 'tag'=>$tag, + 'attributes'=>$attributes, + 'text'=>$text + ); + } + + /** + * @brief formats a timestamp in the "right" way + * + * @param int $timestamp + * @param bool $dateOnly option to omit time from the result + * @return string timestamp + * @description adjust to clients timezone if we know it + */ + public static function formatDate( $timestamp, $dateOnly=false) { + if(\OC::$session->exists('timezone')) { + $systemTimeZone = intval(date('O')); + $systemTimeZone = (round($systemTimeZone/100, 0)*60) + ($systemTimeZone%100); + $clientTimeZone = \OC::$session->get('timezone')*60; + $offset = $clientTimeZone - $systemTimeZone; + $timestamp = $timestamp + $offset*60; + } + $l = OC_L10N::get('lib'); + return $l->l($dateOnly ? 'date' : 'datetime', $timestamp); + } + + /** + * @brief check if the current server configuration is suitable for ownCloud + * @return array arrays with error messages and hints + */ + public static function checkServer() { + // Assume that if checkServer() succeeded before in this session, then all is fine. + if(\OC::$session->exists('checkServer_suceeded') && \OC::$session->get('checkServer_suceeded')) { + return array(); + } + + $errors = array(); + + $defaults = new \OC_Defaults(); + + $webServerRestart = false; + //check for database drivers + if(!(is_callable('sqlite_open') or class_exists('SQLite3')) + and !is_callable('mysql_connect') + and !is_callable('pg_connect') + and !is_callable('oci_connect')) { + $errors[] = array( + 'error'=>'No database drivers (sqlite, mysql, or postgresql) installed.', + 'hint'=>'' //TODO: sane hint + ); + $webServerRestart = true; + } + + //common hint for all file permissions error messages + $permissionsHint = 'Permissions can usually be fixed by ' + .'<a href="' . $defaults->getDocBaseUrl() . '/server/5.0/admin_manual/installation/installation_source.html' + .'#set-the-directory-permissions" target="_blank">giving the webserver write access to the root directory</a>.'; + + // Check if config folder is writable. + if(!is_writable(OC::$SERVERROOT."/config/") or !is_readable(OC::$SERVERROOT."/config/")) { + $errors[] = array( + 'error' => "Can't write into config directory", + 'hint' => 'This can usually be fixed by ' + .'<a href="' . $defaults->getDocBaseUrl() . '/server/5.0/admin_manual/installation/installation_source.html' + .'#set-the-directory-permissions" target="_blank">giving the webserver write access to the config directory</a>.' + ); + } + + // Check if there is a writable install folder. + if(OC_Config::getValue('appstoreenabled', true)) { + if( OC_App::getInstallPath() === null + || !is_writable(OC_App::getInstallPath()) + || !is_readable(OC_App::getInstallPath()) ) { + $errors[] = array( + 'error' => "Can't write into apps directory", + 'hint' => 'This can usually be fixed by ' + .'<a href="' . $defaults->getDocBaseUrl() . '/server/5.0/admin_manual/installation/installation_source.html' + .'#set-the-directory-permissions" target="_blank">giving the webserver write access to the apps directory</a> ' + .'or disabling the appstore in the config file.' + ); + } + } + $CONFIG_DATADIRECTORY = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); + // Create root dir. + if(!is_dir($CONFIG_DATADIRECTORY)) { + $success=@mkdir($CONFIG_DATADIRECTORY); + if ($success) { + $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); + } else { + $errors[] = array( + 'error' => "Can't create data directory (".$CONFIG_DATADIRECTORY.")", + 'hint' => 'This can usually be fixed by ' + .'<a href="' . $defaults->getDocBaseUrl() . '/server/5.0/admin_manual/installation/installation_source.html' + .'#set-the-directory-permissions" target="_blank">giving the webserver write access to the root directory</a>.' + ); + } + } else if(!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) { + $errors[] = array( + 'error'=>'Data directory ('.$CONFIG_DATADIRECTORY.') not writable by ownCloud', + 'hint'=>$permissionsHint + ); + } else { + $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); + } + + $moduleHint = "Please ask your server administrator to install the module."; + // check if all required php modules are present + if(!class_exists('ZipArchive')) { + $errors[] = array( + 'error'=>'PHP module zip not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; + } + if(!class_exists('DOMDocument')) { + $errors[] = array( + 'error' => 'PHP module dom not installed.', + 'hint' => $moduleHint + ); + $webServerRestart =true; + } + if(!function_exists('xml_parser_create')) { + $errors[] = array( + 'error' => 'PHP module libxml not installed.', + 'hint' => $moduleHint + ); + $webServerRestart = true; + } + if(!function_exists('mb_detect_encoding')) { + $errors[] = array( + 'error'=>'PHP module mb multibyte not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; + } + if(!function_exists('ctype_digit')) { + $errors[] = array( + 'error'=>'PHP module ctype is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; + } + if(!function_exists('json_encode')) { + $errors[] = array( + 'error'=>'PHP module JSON is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; + } + if(!extension_loaded('gd') || !function_exists('gd_info')) { + $errors[] = array( + 'error'=>'PHP module GD is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; + } + if(!function_exists('gzencode')) { + $errors[] = array( + 'error'=>'PHP module zlib is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; + } + if(!function_exists('iconv')) { + $errors[] = array( + 'error'=>'PHP module iconv is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; + } + if(!function_exists('simplexml_load_string')) { + $errors[] = array( + 'error'=>'PHP module SimpleXML is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; + } + if(floatval(phpversion()) < 5.3) { + $errors[] = array( + 'error'=>'PHP 5.3 is required.', + 'hint'=>'Please ask your server administrator to update PHP to version 5.3 or higher.' + .' PHP 5.2 is no longer supported by ownCloud and the PHP community.' + ); + $webServerRestart = true; + } + if(!defined('PDO::ATTR_DRIVER_NAME')) { + $errors[] = array( + 'error'=>'PHP PDO module is not installed.', + 'hint'=>$moduleHint + ); + $webServerRestart = true; + } + if (((strtolower(@ini_get('safe_mode')) == 'on') + || (strtolower(@ini_get('safe_mode')) == 'yes') + || (strtolower(@ini_get('safe_mode')) == 'true') + || (ini_get("safe_mode") == 1 ))) { + $errors[] = array( + 'error'=>'PHP Safe Mode is enabled. ownCloud requires that it is disabled to work properly.', + 'hint'=>'PHP Safe Mode is a deprecated and mostly useless setting that should be disabled. ' + .'Please ask your server administrator to disable it in php.ini or in your webserver config.' + ); + $webServerRestart = true; + } + if (get_magic_quotes_gpc() == 1 ) { + $errors[] = array( + 'error'=>'Magic Quotes is enabled. ownCloud requires that it is disabled to work properly.', + 'hint'=>'Magic Quotes is a deprecated and mostly useless setting that should be disabled. ' + .'Please ask your server administrator to disable it in php.ini or in your webserver config.' + ); + $webServerRestart = true; + } + + if($webServerRestart) { + $errors[] = array( + 'error'=>'PHP modules have been installed, but they are still listed as missing?', + 'hint'=>'Please ask your server administrator to restart the web server.' + ); + } + + // Cache the result of this function + \OC::$session->set('checkServer_suceeded', count($errors) == 0); + + return $errors; + } + + /** + * @brief check if there are still some encrypted files stored + * @return boolean + */ + public static function encryptedFiles() { + //check if encryption was enabled in the past + $encryptedFiles = false; + if (OC_App::isEnabled('files_encryption') === false) { + $view = new OC\Files\View('/' . OCP\User::getUser()); + $keyfilePath = '/files_encryption/keyfiles'; + if ($view->is_dir($keyfilePath)) { + $dircontent = $view->getDirectoryContent($keyfilePath); + if (!empty($dircontent)) { + $encryptedFiles = true; + } + } + } + + return $encryptedFiles; + } + + /** + * @brief Check for correct file permissions of data directory + * @paran string $dataDirectory + * @return array arrays with error messages and hints + */ + public static function checkDataDirectoryPermissions($dataDirectory) { + $errors = array(); + if (self::runningOnWindows()) { + //TODO: permissions checks for windows hosts + } else { + $permissionsModHint = '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') { + OC_Helper::chmodr($dataDirectory, 0770); + clearstatcache(); + $perms = substr(decoct(@fileperms($dataDirectory)), -3); + if (substr($perms, 2, 1) != '0') { + $errors[] = array( + 'error' => 'Data directory ('.$dataDirectory.') is readable for other users', + 'hint' => $permissionsModHint + ); + } + } + } + return $errors; + } + + /** + * @return void + */ + public static function displayLoginPage($errors = array()) { + $parameters = array(); + foreach( $errors as $key => $value ) { + $parameters[$value] = true; + } + if (!empty($_POST['user'])) { + $parameters["username"] = $_POST['user']; + $parameters['user_autofocus'] = false; + } else { + $parameters["username"] = ''; + $parameters['user_autofocus'] = true; + } + if (isset($_REQUEST['redirect_url'])) { + $redirectUrl = $_REQUEST['redirect_url']; + $parameters['redirect_url'] = urlencode($redirectUrl); + } + + $parameters['alt_login'] = OC_App::getAlternativeLogIns(); + OC_Template::printGuestPage("", "login", $parameters); + } + + + /** + * @brief Check if the app is enabled, redirects to home if not + * @return void + */ + public static function checkAppEnabled($app) { + if( !OC_App::isEnabled($app)) { + header( 'Location: '.OC_Helper::linkToAbsolute( '', 'index.php' )); + exit(); + } + } + + /** + * 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_Helper::linkToAbsolute( '', 'index.php', + array('redirectUrl' => OC_Request::requestUri()) + )); + exit(); + } + } + + /** + * @brief Check if the user is a admin, redirects to home if not + * @return void + */ + public static function checkAdminUser() { + if( !OC_User::isAdminUser(OC_User::getUser())) { + header( 'Location: '.OC_Helper::linkToAbsolute( '', 'index.php' )); + exit(); + } + } + + /** + * @brief Check if the user is a subadmin, redirects to home if not + * @return array $groups where the current user is subadmin + */ + public static function checkSubAdminUser() { + if(!OC_SubAdmin::isSubAdmin(OC_User::getUser())) { + header( 'Location: '.OC_Helper::linkToAbsolute( '', 'index.php' )); + exit(); + } + return true; + } + + /** + * @brief Redirect to the user default page + * @return void + */ + public static function redirectToDefaultPage() { + if(isset($_REQUEST['redirect_url'])) { + $location = OC_Helper::makeURLAbsolute(urldecode($_REQUEST['redirect_url'])); + } + else if (isset(OC::$REQUESTEDAPP) && !empty(OC::$REQUESTEDAPP)) { + $location = OC_Helper::linkToAbsolute( OC::$REQUESTEDAPP, 'index.php' ); + } else { + $defaultPage = OC_Appconfig::getValue('core', 'defaultpage'); + if ($defaultPage) { + $location = OC_Helper::makeURLAbsolute(OC::$WEBROOT.'/'.$defaultPage); + } else { + $location = OC_Helper::linkToAbsolute( 'files', 'index.php' ); + } + } + OC_Log::write('core', 'redirectToDefaultPage: '.$location, OC_Log::DEBUG); + header( 'Location: '.$location ); + exit(); + } + + /** + * @brief get an id unique for this instance + * @return string + */ + public static function getInstanceId() { + $id = OC_Config::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' . self::generateRandomBytes(10); + OC_Config::setValue('instanceid', $id); + } + return $id; + } + + /** + * @brief Static lifespan (in seconds) when a request token expires. + * @see OC_Util::callRegister() + * @see OC_Util::isCallRegistered() + * @description + * Also required for the client side to compute the point in time when to + * request a fresh token. The client will do so when nearly 97% of the + * time span coded here has expired. + */ + public static $callLifespan = 3600; // 3600 secs = 1 hour + + /** + * @brief Register an get/post call. Important to prevent CSRF attacks. + * @todo Write howto: CSRF protection guide + * @return $token Generated token. + * @description + * Creates a 'request token' (random) and stores it inside the session. + * Ever subsequent (ajax) request must use such a valid token to succeed, + * otherwise the request will be denied as a protection against CSRF. + * The tokens expire after a fixed lifespan. + * @see OC_Util::$callLifespan + * @see OC_Util::isCallRegistered() + */ + public static function callRegister() { + // Check if a token exists + if(!\OC::$session->exists('requesttoken')) { + // No valid token found, generate a new one. + $requestToken = self::generateRandomBytes(20); + \OC::$session->set('requesttoken', $requestToken); + } else { + // Valid token already exists, send it + $requestToken = \OC::$session->get('requesttoken'); + } + return($requestToken); + } + + /** + * @brief Check an ajax get/post call if the request token is valid. + * @return boolean False if request token is not set or is invalid. + * @see OC_Util::$callLifespan + * @see OC_Util::callRegister() + */ + public static function isCallRegistered() { + if(!\OC::$session->exists('requesttoken')) { + return false; + } + + if(isset($_GET['requesttoken'])) { + $token = $_GET['requesttoken']; + } elseif(isset($_POST['requesttoken'])) { + $token = $_POST['requesttoken']; + } elseif(isset($_SERVER['HTTP_REQUESTTOKEN'])) { + $token = $_SERVER['HTTP_REQUESTTOKEN']; + } else { + //no token found. + return false; + } + + // Check if the token is valid + if($token !== \OC::$session->get('requesttoken')) { + // Not valid + return false; + } else { + // Valid token + return true; + } + } + + /** + * @brief Check an ajax get/post call if the request token is valid. exit if not. + * @todo Write howto + * @return void + */ + public static function callCheck() { + if(!OC_Util::isCallRegistered()) { + exit(); + } + } + + /** + * @brief 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 of strings + * @return array with sanitized strings or a single sanitized string, depends on the input parameter. + */ + public static function sanitizeHTML( &$value ) { + if (is_array($value)) { + array_walk_recursive($value, 'OC_Util::sanitizeHTML'); + } else { + //Specify encoding for PHP<5.4 + $value = htmlentities((string)$value, ENT_QUOTES, 'UTF-8'); + } + return $value; + } + + /** + * @brief 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; + } + + /** + * @brief Check if the htaccess file is working + * @return bool + * @description Check if the htaccess file is working by creating a test + * file in the data directory and trying to access via http + */ + public static function isHtAccessWorking() { + // testdata + $fileName = '/htaccesstest.txt'; + $testContent = 'testcontent'; + + // creating a test file + $testFile = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ).'/'.$fileName; + + if(file_exists($testFile)) {// already running this test, possible recursive call + return false; + } + + $fp = @fopen($testFile, 'w'); + @fwrite($fp, $testContent); + @fclose($fp); + + // accessing the file via http + $url = OC_Helper::makeURLAbsolute(OC::$WEBROOT.'/data'.$fileName); + $fp = @fopen($url, 'r'); + $content=@fread($fp, 2048); + @fclose($fp); + + // cleanup + @unlink($testFile); + + // does it work ? + if($content==$testContent) { + return false; + } else { + return true; + } + } + + /** + * @brief test if webDAV is working properly + * @return bool + * @description + * The basic assumption is that if the server returns 401/Not Authenticated for an unauthenticated PROPFIND + * the web server it self is setup properly. + * + * Why not an authenticated PROPFIND and other verbs? + * - We don't have the password available + * - We have no idea about other auth methods implemented (e.g. OAuth with Bearer header) + * + */ + public static function isWebDAVWorking() { + if (!function_exists('curl_init')) { + return true; + } + $settings = array( + 'baseUri' => OC_Helper::linkToRemote('webdav'), + ); + + $client = new \Sabre_DAV_Client($settings); + + // for this self test we don't care if the ssl certificate is self signed and the peer cannot be verified. + $client->setVerifyPeer(false); + + $return = true; + try { + // test PROPFIND + $client->propfind('', array('{DAV:}resourcetype')); + } catch (\Sabre_DAV_Exception_NotAuthenticated $e) { + $return = true; + } catch (\Exception $e) { + OC_Log::write('core', 'isWebDAVWorking: NO - Reason: '.$e->getMessage(). ' ('.get_class($e).')', OC_Log::WARN); + $return = false; + } + + return $return; + } + + /** + * 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; + } + + $result = setlocale(LC_ALL, 'en_US.UTF-8', 'en_US.UTF8'); + if($result == false) { + return false; + } + return true; + } + + /** + * @brief Check if the PHP module fileinfo is loaded. + * @return bool + */ + public static function fileInfoLoaded() { + return function_exists('finfo_open'); + } + + /** + * @brief Check if the ownCloud server can connect to the internet + * @return bool + */ + public static function isInternetConnectionWorking() { + // in case there is no internet connection on purpose return false + if (self::isInternetConnectionEnabled() === false) { + return false; + } + + // try to connect to owncloud.org to see if http connections to the internet are possible. + $connected = @fsockopen("www.owncloud.org", 80); + if ($connected) { + fclose($connected); + return true; + } else { + // second try in case one server is down + $connected = @fsockopen("apps.owncloud.com", 80); + if ($connected) { + fclose($connected); + return true; + } else { + return false; + } + } + } + + /** + * @brief Check if the connection to the internet is disabled on purpose + * @return bool + */ + public static function isInternetConnectionEnabled(){ + return \OC_Config::getValue("has_internet_connection", true); + } + + /** + * @brief clear all levels of output buffering + * @return void + */ + public static function obEnd(){ + while (ob_get_level()) { + ob_end_clean(); + } + } + + + /** + * @brief Generates a cryptographic secure pseudo-random string + * @param Int $length of the random string + * @return String + * Please also update secureRNGAvailable if you change something here + */ + public static function generateRandomBytes($length = 30) { + // Try to use openssl_random_pseudo_bytes + if (function_exists('openssl_random_pseudo_bytes')) { + $pseudoByte = bin2hex(openssl_random_pseudo_bytes($length, $strong)); + if($strong == true) { + return substr($pseudoByte, 0, $length); // Truncate it to match the length + } + } + + // Try to use /dev/urandom + if (!self::runningOnWindows()) { + $fp = @file_get_contents('/dev/urandom', false, null, 0, $length); + if ($fp !== false) { + $string = substr(bin2hex($fp), 0, $length); + return $string; + } + } + + // Fallback to mt_rand() + $characters = '0123456789'; + $characters .= 'abcdefghijklmnopqrstuvwxyz'; + $charactersLength = strlen($characters)-1; + $pseudoByte = ""; + + // Select some random characters + for ($i = 0; $i < $length; $i++) { + $pseudoByte .= $characters[mt_rand(0, $charactersLength)]; + } + return $pseudoByte; + } + + /** + * @brief Checks if a secure random number generator is available + * @return bool + */ + public static function secureRNGAvailable() { + // Check openssl_random_pseudo_bytes + if(function_exists('openssl_random_pseudo_bytes')) { + openssl_random_pseudo_bytes(1, $strong); + if($strong == true) { + return true; + } + } + + // Check /dev/urandom + if (!self::runningOnWindows()) { + $fp = @file_get_contents('/dev/urandom', false, null, 0, 1); + if ($fp !== false) { + return true; + } + } + + return false; + } + + /** + * @Brief Get file content via curl. + * @param string $url Url to get content + * @return string of the response or false on error + * This function get the content of a page via curl, if curl is enabled. + * If not, file_get_element is used. + */ + public static function getUrlContent($url){ + if (function_exists('curl_init')) { + $curl = curl_init(); + + curl_setopt($curl, CURLOPT_HEADER, 0); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($curl, CURLOPT_MAXREDIRS, 10); + + curl_setopt($curl, CURLOPT_USERAGENT, "ownCloud Server Crawler"); + if(OC_Config::getValue('proxy', '') != '') { + curl_setopt($curl, CURLOPT_PROXY, OC_Config::getValue('proxy')); + } + if(OC_Config::getValue('proxyuserpwd', '') != '') { + curl_setopt($curl, CURLOPT_PROXYUSERPWD, OC_Config::getValue('proxyuserpwd')); + } + $data = curl_exec($curl); + curl_close($curl); + + } else { + $contextArray = null; + + if(OC_Config::getValue('proxy', '') != '') { + $contextArray = array( + 'http' => array( + 'timeout' => 10, + 'proxy' => OC_Config::getValue('proxy') + ) + ); + } else { + $contextArray = array( + 'http' => array( + 'timeout' => 10 + ) + ); + } + + $ctx = stream_context_create( + $contextArray + ); + $data = @file_get_contents($url, 0, $ctx); + + } + return $data; + } + + /** + * @return bool - well are we running on windows or not + */ + public static function runningOnWindows() { + return (substr(PHP_OS, 0, 3) === "WIN"); + } + + /** + * 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_Config::getValue("theme", ''); + + if($theme === '') { + if(is_dir(OC::$SERVERROOT . '/themes/default')) { + $theme = 'default'; + } + } + + return $theme; + } + + /** + * @brief 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')) { + xcache_clear_cache(XC_TYPE_VAR, 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(class_exists('Patchwork\PHP\Shim\Normalizer')) { + $normalizedValue = \Patchwork\PHP\Shim\Normalizer::normalize($value); + if($normalizedValue === false) { + \OC_Log::write( 'core', 'normalizing failed for "' . $value . '"', \OC_Log::WARN); + } else { + $value = $normalizedValue; + } + } + + return $value; + } + + /** + * @return string + */ + public static function basename($file) { + $file = rtrim($file, '/'); + $t = explode('/', $file); + return array_pop($t); + } +} diff --git a/lib/private/vobject.php b/lib/private/vobject.php new file mode 100644 index 00000000000..267176ebc07 --- /dev/null +++ b/lib/private/vobject.php @@ -0,0 +1,207 @@ +<?php +/** + * ownCloud + * + * @author Bart Visscher + * @copyright 2011 Bart Visscher bartv@thisnet.nl + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * This class provides a streamlined interface to the Sabre VObject classes + */ +class OC_VObject{ + /** @var Sabre\VObject\Component */ + protected $vobject; + + /** + * @returns Sabre\VObject\Component + */ + public function getVObject() { + return $this->vobject; + } + + /** + * @brief Parses the VObject + * @param string VObject as string + * @returns Sabre_VObject or null + */ + public static function parse($data) { + try { + Sabre\VObject\Property::$classMap['LAST-MODIFIED'] = 'Sabre\VObject\Property\DateTime'; + $vobject = Sabre\VObject\Reader::read($data); + if ($vobject instanceof Sabre\VObject\Component) { + $vobject = new OC_VObject($vobject); + } + return $vobject; + } catch (Exception $e) { + OC_Log::write('vobject', $e->getMessage(), OC_Log::ERROR); + return null; + } + } + + /** + * @brief Escapes semicolons + * @param string $value + * @return string + */ + public static function escapeSemicolons($value) { + foreach($value as &$i ) { + $i = implode("\\\\;", explode(';', $i)); + } + return implode(';', $value); + } + + /** + * @brief Creates an array out of a multivalue property + * @param string $value + * @return array + */ + public static function unescapeSemicolons($value) { + $array = explode(';', $value); + for($i=0;$i<count($array);$i++) { + if(substr($array[$i], -2, 2)=="\\\\") { + if(isset($array[$i+1])) { + $array[$i] = substr($array[$i], 0, count($array[$i])-2).';'.$array[$i+1]; + unset($array[$i+1]); + } + else{ + $array[$i] = substr($array[$i], 0, count($array[$i])-2).';'; + } + $i = $i - 1; + } + } + return $array; + } + + /** + * Constuctor + * @param Sabre\VObject\Component or string + */ + public function __construct($vobject_or_name) { + if (is_object($vobject_or_name)) { + $this->vobject = $vobject_or_name; + } else { + $this->vobject = new Sabre\VObject\Component($vobject_or_name); + } + } + + public function add($item, $itemValue = null) { + if ($item instanceof OC_VObject) { + $item = $item->getVObject(); + } + $this->vobject->add($item, $itemValue); + } + + /** + * @brief Add property to vobject + * @param object $name of property + * @param object $value of property + * @param object $parameters of property + * @returns Sabre_VObject_Property newly created + */ + public function addProperty($name, $value, $parameters=array()) { + if(is_array($value)) { + $value = OC_VObject::escapeSemicolons($value); + } + $property = new Sabre\VObject\Property( $name, $value ); + foreach($parameters as $name => $value) { + $property->parameters[] = new Sabre\VObject\Parameter($name, $value); + } + + $this->vobject->add($property); + return $property; + } + + public function setUID() { + $uid = substr(md5(rand().time()), 0, 10); + $this->vobject->add('UID', $uid); + } + + public function setString($name, $string) { + if ($string != '') { + $string = strtr($string, array("\r\n"=>"\n")); + $this->vobject->__set($name, $string); + }else{ + $this->vobject->__unset($name); + } + } + + /** + * Sets or unsets the Date and Time for a property. + * When $datetime is set to 'now', use the current time + * When $datetime is null, unset the property + * + * @param string property name + * @param DateTime $datetime + * @param int $dateType + * @return void + */ + public function setDateTime($name, $datetime, $dateType=Sabre\VObject\Property\DateTime::LOCALTZ) { + if ($datetime == 'now') { + $datetime = new DateTime(); + } + if ($datetime instanceof DateTime) { + $datetime_element = new Sabre\VObject\Property\DateTime($name); + $datetime_element->setDateTime($datetime, $dateType); + $this->vobject->__set($name, $datetime_element); + }else{ + $this->vobject->__unset($name); + } + } + + public function getAsString($name) { + return $this->vobject->__isset($name) ? + $this->vobject->__get($name)->value : + ''; + } + + public function getAsArray($name) { + $values = array(); + if ($this->vobject->__isset($name)) { + $values = explode(',', $this->getAsString($name)); + $values = array_map('trim', $values); + } + return $values; + } + + public function &__get($name) { + if ($name == 'children') { + return $this->vobject->children; + } + $return = $this->vobject->__get($name); + if ($return instanceof Sabre\VObject\Component) { + $return = new OC_VObject($return); + } + return $return; + } + + public function __set($name, $value) { + return $this->vobject->__set($name, $value); + } + + public function __unset($name) { + return $this->vobject->__unset($name); + } + + public function __isset($name) { + return $this->vobject->__isset($name); + } + + public function __call($function, $arguments) { + return call_user_func_array(array($this->vobject, $function), $arguments); + } +} diff --git a/lib/private/vobject/compoundproperty.php b/lib/private/vobject/compoundproperty.php new file mode 100644 index 00000000000..7fe42574bed --- /dev/null +++ b/lib/private/vobject/compoundproperty.php @@ -0,0 +1,70 @@ +<?php +/** + * ownCloud - VObject Compound Property + * + * @author Thomas Tanghus + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\VObject; + +/** + * This class overrides \Sabre\VObject\Property::serialize() to not + * double escape commas and semi-colons in compound properties. +*/ +class CompoundProperty extends \Sabre\VObject\Property\Compound { + + /** + * Turns the object back into a serialized blob. + * + * @return string + */ + public function serialize() { + + $str = $this->name; + if ($this->group) { + $str = $this->group . '.' . $this->name; + } + + foreach($this->parameters as $param) { + $str.=';' . $param->serialize(); + } + $src = array( + "\n", + ); + $out = array( + '\n', + ); + $str.=':' . str_replace($src, $out, $this->value); + + $out = ''; + while(strlen($str) > 0) { + if (strlen($str) > 75) { + $out .= mb_strcut($str, 0, 75, 'utf-8') . "\r\n"; + $str = ' ' . mb_strcut($str, 75, strlen($str), 'utf-8'); + } else { + $out .= $str . "\r\n"; + $str = ''; + break; + } + } + + return $out; + + } + +} diff --git a/lib/private/vobject/stringproperty.php b/lib/private/vobject/stringproperty.php new file mode 100644 index 00000000000..a9d63a0a789 --- /dev/null +++ b/lib/private/vobject/stringproperty.php @@ -0,0 +1,80 @@ +<?php +/** + * ownCloud - VObject String Property + * + * This class adds escaping of simple string properties. + * + * @author Thomas Tanghus + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\VObject; + +/** + * This class overrides \Sabre\VObject\Property::serialize() properly + * escape commas and semi-colons in string properties. +*/ +class StringProperty extends \Sabre\VObject\Property { + + /** + * Turns the object back into a serialized blob. + * + * @return string + */ + public function serialize() { + + $str = $this->name; + if ($this->group) { + $str = $this->group . '.' . $this->name; + } + + foreach($this->parameters as $param) { + $str.=';' . $param->serialize(); + } + + $src = array( + '\\', + "\n", + ';', + ',', + ); + $out = array( + '\\\\', + '\n', + '\;', + '\,', + ); + $value = strtr($this->value, array('\,' => ',', '\;' => ';', '\\\\' => '\\')); + $str.=':' . str_replace($src, $out, $value); + + $out = ''; + while(strlen($str) > 0) { + if (strlen($str) > 75) { + $out .= mb_strcut($str, 0, 75, 'utf-8') . "\r\n"; + $str = ' ' . mb_strcut($str, 75, strlen($str), 'utf-8'); + } else { + $out .= $str . "\r\n"; + $str = ''; + break; + } + } + + return $out; + + } + +} |