diff options
Diffstat (limited to 'apps')
374 files changed, 11450 insertions, 1040 deletions
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml new file mode 100644 index 00000000000..8f378f5e18d --- /dev/null +++ b/apps/dav/appinfo/info.xml @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<info> + <id>dav</id> + <name>WebDAV</name> + <description>ownCloud WebDAV endpoint</description> + <licence>AGPL</licence> + <author>owncloud.org</author> + <version>0.1.1</version> + <requiremin>9.0</requiremin> + <shipped>true</shipped> + <standalone/> + <default_enable/> + <types> + <filesystem/> + </types> + <remote> + <files>appinfo/v1/webdav.php</files> + <webdav>appinfo/v1/webdav.php</webdav> + <dav>appinfo/v2/remote.php</dav> + </remote> + <public> + <webdav>appinfo/v1/publicwebdav.php</webdav> + </public> +</info> diff --git a/apps/files_sharing/publicwebdav.php b/apps/dav/appinfo/v1/publicwebdav.php index 7d96c8e8c97..5bdfd94e658 100644 --- a/apps/files_sharing/publicwebdav.php +++ b/apps/dav/appinfo/v1/publicwebdav.php @@ -23,16 +23,16 @@ */ // load needed apps -$RUNTIME_APPTYPES = array('filesystem', 'authentication', 'logging'); +$RUNTIME_APPTYPES = ['filesystem', 'authentication', 'logging']; OC_App::loadApps($RUNTIME_APPTYPES); OC_Util::obEnd(); // Backends -$authBackend = new OCA\Files_Sharing\Connector\PublicAuth(\OC::$server->getConfig()); +$authBackend = new OCA\DAV\Connector\PublicAuth(\OC::$server->getConfig()); -$serverFactory = new \OC\Connector\Sabre\ServerFactory( +$serverFactory = new OCA\DAV\Connector\Sabre\ServerFactory( \OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), diff --git a/apps/files/appinfo/remote.php b/apps/dav/appinfo/v1/webdav.php index 02f7f3c027f..f28736f1f01 100644 --- a/apps/files/appinfo/remote.php +++ b/apps/dav/appinfo/v1/webdav.php @@ -1,8 +1,9 @@ <?php /** + * @author Bart Visscher <bartv@thisnet.nl> * @author Frank Karlitschek <frank@owncloud.org> * @author Jakob Sack <mail@jakobsack.de> - * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> @@ -32,7 +33,7 @@ set_time_limit(0); // Turn off output buffering to prevent memory problems \OC_Util::obEnd(); -$serverFactory = new \OC\Connector\Sabre\ServerFactory( +$serverFactory = new \OCA\DAV\Connector\Sabre\ServerFactory( \OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), @@ -43,7 +44,10 @@ $serverFactory = new \OC\Connector\Sabre\ServerFactory( ); // Backends -$authBackend = new \OC\Connector\Sabre\Auth(); +$authBackend = new \OCA\DAV\Connector\Sabre\Auth( + \OC::$server->getSession(), + \OC::$server->getUserSession() +); $requestUri = \OC::$server->getRequest()->getRequestUri(); $server = $serverFactory->createServer($baseuri, $requestUri, $authBackend, function() { diff --git a/apps/dav/appinfo/v2/remote.php b/apps/dav/appinfo/v2/remote.php new file mode 100644 index 00000000000..02457bd3ccc --- /dev/null +++ b/apps/dav/appinfo/v2/remote.php @@ -0,0 +1,11 @@ +<?php + +// no php execution timeout for webdav +set_time_limit(0); + +// Turn off output buffering to prevent memory problems +\OC_Util::obEnd(); + +$request = \OC::$server->getRequest(); +$server = new \OCA\DAV\Server($request, $baseuri); +$server->exec(); diff --git a/apps/files_sharing/lib/connector/publicauth.php b/apps/dav/lib/connector/publicauth.php index 4ac355180fc..f37be41402a 100644 --- a/apps/files_sharing/lib/connector/publicauth.php +++ b/apps/dav/lib/connector/publicauth.php @@ -24,7 +24,7 @@ * */ -namespace OCA\Files_Sharing\Connector; +namespace OCA\DAV\Connector; class PublicAuth extends \Sabre\DAV\Auth\Backend\AbstractBasic { diff --git a/apps/dav/lib/connector/sabre/appenabledplugin.php b/apps/dav/lib/connector/sabre/appenabledplugin.php new file mode 100644 index 00000000000..e70512d0fd1 --- /dev/null +++ b/apps/dav/lib/connector/sabre/appenabledplugin.php @@ -0,0 +1,89 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use OCP\App\IAppManager; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\ServerPlugin; + +/** + * Plugin to check if an app is enabled for the current user + */ +class AppEnabledPlugin extends ServerPlugin { + + /** + * Reference to main server object + * + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var string + */ + private $app; + + /** + * @var \OCP\App\IAppManager + */ + private $appManager; + + /** + * @param string $app + * @param \OCP\App\IAppManager $appManager + */ + public function __construct($app, IAppManager $appManager) { + $this->app = $app; + $this->appManager = $appManager; + } + + /** + * 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->on('beforeMethod', array($this, 'checkAppEnabled'), 30); + } + + /** + * This method is called before any HTTP after auth and checks if the user has access to the app + * + * @throws \Sabre\DAV\Exception\Forbidden + * @return bool + */ + public function checkAppEnabled() { + if (!$this->appManager->isEnabledForUser($this->app)) { + throw new Forbidden(); + } + } +} diff --git a/apps/dav/lib/connector/sabre/auth.php b/apps/dav/lib/connector/sabre/auth.php new file mode 100644 index 00000000000..39a7df31b7f --- /dev/null +++ b/apps/dav/lib/connector/sabre/auth.php @@ -0,0 +1,169 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Bart Visscher <bartv@thisnet.nl> + * @author Christian Seiler <christian@iwakd.de> + * @author Jakob Sack <mail@jakobsack.de> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Markus Goetz <markus@woboq.com> + * @author Michael Gapczynski <GapczynskiM@gmail.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\Connector\Sabre; + +use Exception; +use OCP\ISession; +use OCP\IUserSession; +use Sabre\DAV\Auth\Backend\AbstractBasic; +use Sabre\DAV\Exception\NotAuthenticated; +use Sabre\DAV\Exception\ServiceUnavailable; + +class Auth extends AbstractBasic { + const DAV_AUTHENTICATED = 'AUTHENTICATED_TO_DAV_BACKEND'; + + /** @var ISession */ + private $session; + /** @var IUserSession */ + private $userSession; + + /** + * @param ISession $session + * @param IUserSession $userSession + */ + public function __construct(ISession $session, + IUserSession $userSession) { + $this->session = $session; + $this->userSession = $userSession; + } + + /** + * Whether the user has initially authenticated via DAV + * + * This is required for WebDAV clients that resent the cookies even when the + * account was changed. + * + * @see https://github.com/owncloud/core/issues/13245 + * + * @param string $username + * @return bool + */ + protected function isDavAuthenticated($username) { + return !is_null($this->session->get(self::DAV_AUTHENTICATED)) && + $this->session->get(self::DAV_AUTHENTICATED) === $username; + } + + /** + * Validates a username and password + * + * This method should return true or false depending on if login + * succeeded. + * + * @param string $username + * @param string $password + * @return bool + */ + protected function validateUserPass($username, $password) { + if ($this->userSession->isLoggedIn() && + $this->isDavAuthenticated($this->userSession->getUser()->getUID()) + ) { + \OC_Util::setupFS($this->userSession->getUser()->getUID()); + $this->session->close(); + return true; + } else { + \OC_Util::setUpFS(); //login hooks may need early access to the filesystem + if($this->userSession->login($username, $password)) { + \OC_Util::setUpFS($this->userSession->getUser()->getUID()); + $this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID()); + $this->session->close(); + return true; + } else { + $this->session->close(); + 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 = $this->userSession->getUser() ? $this->userSession->getUser()->getUID() : null; + if($user !== null && $this->isDavAuthenticated($user)) { + return $user; + } + + if($user !== null && is_null($this->session->get(self::DAV_AUTHENTICATED))) { + return $user; + } + + return null; + } + + /** + * 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. + * + * @param \Sabre\DAV\Server $server + * @param string $realm + * @return bool + * @throws ServiceUnavailable + * @throws NotAuthenticated + */ + public function authenticate(\Sabre\DAV\Server $server, $realm) { + try { + $result = $this->auth($server, $realm); + return $result; + } catch (NotAuthenticated $e) { + throw $e; + } catch (Exception $e) { + $class = get_class($e); + $msg = $e->getMessage(); + throw new ServiceUnavailable("$class: $msg"); + } + } + + /** + * @param \Sabre\DAV\Server $server + * @param $realm + * @return bool + */ + private function auth(\Sabre\DAV\Server $server, $realm) { + if (\OC_User::handleApacheAuth() || + ($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) + ) { + $user = $this->userSession->getUser()->getUID(); + \OC_Util::setupFS($user); + $this->currentUser = $user; + $this->session->close(); + return true; + } + + return parent::authenticate($server, $realm); + } +} diff --git a/apps/dav/lib/connector/sabre/blocklegacyclientplugin.php b/apps/dav/lib/connector/sabre/blocklegacyclientplugin.php new file mode 100644 index 00000000000..ed61f43a536 --- /dev/null +++ b/apps/dav/lib/connector/sabre/blocklegacyclientplugin.php @@ -0,0 +1,79 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use OCP\IConfig; +use Sabre\HTTP\RequestInterface; +use Sabre\DAV\ServerPlugin; +use Sabre\DAV\Exception; + +/** + * Class BlockLegacyClientPlugin is used to detect old legacy sync clients and + * returns a 403 status to those clients + * + * @package OCA\DAV\Connector\Sabre + */ +class BlockLegacyClientPlugin extends ServerPlugin { + /** @var \Sabre\DAV\Server */ + protected $server; + /** @var IConfig */ + protected $config; + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * @param \Sabre\DAV\Server $server + * @return void + */ + public function initialize(\Sabre\DAV\Server $server) { + $this->server = $server; + $this->server->on('beforeMethod', [$this, 'beforeHandler'], 200); + } + + /** + * Detects all unsupported clients and throws a \Sabre\DAV\Exception\Forbidden + * exception which will result in a 403 to them. + * @param RequestInterface $request + * @throws \Sabre\DAV\Exception\Forbidden If the client version is not supported + */ + public function beforeHandler(RequestInterface $request) { + $userAgent = $request->getHeader('User-Agent'); + if($userAgent === null) { + return; + } + + $minimumSupportedDesktopVersion = $this->config->getSystemValue('minimum.supported.desktop.version', '1.7.0'); + + // Match on the mirall version which is in scheme "Mozilla/5.0 (%1) mirall/%2" or + // "mirall/%1" for older releases + preg_match("/(?:mirall\\/)([\d.]+)/i", $userAgent, $versionMatches); + if(isset($versionMatches[1]) && + version_compare($versionMatches[1], $minimumSupportedDesktopVersion) === -1) { + throw new \Sabre\DAV\Exception\Forbidden('Unsupported client version.'); + } + } +} diff --git a/apps/dav/lib/connector/sabre/copyetagheaderplugin.php b/apps/dav/lib/connector/sabre/copyetagheaderplugin.php new file mode 100644 index 00000000000..b33b208adad --- /dev/null +++ b/apps/dav/lib/connector/sabre/copyetagheaderplugin.php @@ -0,0 +1,56 @@ +<?php +/** + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use \Sabre\HTTP\RequestInterface; +use \Sabre\HTTP\ResponseInterface; + +/** + * Copies the "Etag" header to "OC-Etag" after any request. + * This is a workaround for setups that automatically strip + * or mangle Etag headers. + */ +class CopyEtagHeaderPlugin extends \Sabre\DAV\ServerPlugin { + /** + * This initializes the plugin. + * + * @param \Sabre\DAV\Server $server Sabre server + * + * @return void + */ + public function initialize(\Sabre\DAV\Server $server) { + $server->on('afterMethod', array($this, 'afterMethod')); + } + + /** + * After method, copy the "Etag" header to "OC-Etag" header. + * + * @param RequestInterface $request request + * @param ResponseInterface $response response + */ + public function afterMethod(RequestInterface $request, ResponseInterface $response) { + $eTag = $response->getHeader('Etag'); + if (!empty($eTag)) { + $response->setHeader('OC-ETag', $eTag); + } + } +} diff --git a/apps/dav/lib/connector/sabre/custompropertiesbackend.php b/apps/dav/lib/connector/sabre/custompropertiesbackend.php new file mode 100644 index 00000000000..ff35476319f --- /dev/null +++ b/apps/dav/lib/connector/sabre/custompropertiesbackend.php @@ -0,0 +1,355 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use OCP\IDBConnection; +use OCP\IUser; +use Sabre\DAV\PropertyStorage\Backend\BackendInterface; +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; +use Sabre\DAV\Tree; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Exception\ServiceUnavailable; + +class CustomPropertiesBackend implements BackendInterface { + + /** + * Ignored properties + * + * @var array + */ + private $ignoredProperties = array( + '{DAV:}getcontentlength', + '{DAV:}getcontenttype', + '{DAV:}getetag', + '{DAV:}quota-used-bytes', + '{DAV:}quota-available-bytes', + '{DAV:}quota-available-bytes', + '{http://owncloud.org/ns}permissions', + '{http://owncloud.org/ns}downloadURL', + '{http://owncloud.org/ns}dDC', + '{http://owncloud.org/ns}size', + ); + + /** + * @var Tree + */ + private $tree; + + /** + * @var IDBConnection + */ + private $connection; + + /** + * @var IUser + */ + private $user; + + /** + * Properties cache + * + * @var array + */ + private $cache = []; + + /** + * @param Tree $tree node tree + * @param IDBConnection $connection database connection + * @param IUser $user owner of the tree and properties + */ + public function __construct( + Tree $tree, + IDBConnection $connection, + IUser $user) { + $this->tree = $tree; + $this->connection = $connection; + $this->user = $user->getUID(); + } + + /** + * Fetches properties for a path. + * + * @param string $path + * @param PropFind $propFind + * @return void + */ + public function propFind($path, PropFind $propFind) { + try { + $node = $this->tree->getNodeForPath($path); + if (!($node instanceof Node)) { + return; + } + } catch (ServiceUnavailable $e) { + // might happen for unavailable mount points, skip + return; + } catch (NotFound $e) { + // in some rare (buggy) cases the node might not be found, + // we catch the exception to prevent breaking the whole list with a 404 + // (soft fail) + \OC::$server->getLogger()->warning( + 'Could not get node for path: \"' . $path . '\" : ' . $e->getMessage(), + array('app' => 'files') + ); + return; + } + + $requestedProps = $propFind->get404Properties(); + + // these might appear + $requestedProps = array_diff( + $requestedProps, + $this->ignoredProperties + ); + + if (empty($requestedProps)) { + return; + } + + if ($node instanceof Directory + && $propFind->getDepth() !== 0 + ) { + // note: pre-fetching only supported for depth <= 1 + $this->loadChildrenProperties($node, $requestedProps); + } + + $props = $this->getProperties($node, $requestedProps); + foreach ($props as $propName => $propValue) { + $propFind->set($propName, $propValue); + } + } + + /** + * Updates properties for a path + * + * @param string $path + * @param PropPatch $propPatch + * + * @return void + */ + public function propPatch($path, PropPatch $propPatch) { + $node = $this->tree->getNodeForPath($path); + if (!($node instanceof Node)) { + return; + } + + $propPatch->handleRemaining(function($changedProps) use ($node) { + return $this->updateProperties($node, $changedProps); + }); + } + + /** + * This method is called after a node is deleted. + * + * @param string $path path of node for which to delete properties + */ + public function delete($path) { + $statement = $this->connection->prepare( + 'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?' + ); + $statement->execute(array($this->user, '/' . $path)); + $statement->closeCursor(); + + unset($this->cache[$path]); + } + + /** + * This method is called after a successful MOVE + * + * @param string $source + * @param string $destination + * + * @return void + */ + public function move($source, $destination) { + $statement = $this->connection->prepare( + 'UPDATE `*PREFIX*properties` SET `propertypath` = ?' . + ' WHERE `userid` = ? AND `propertypath` = ?' + ); + $statement->execute(array('/' . $destination, $this->user, '/' . $source)); + $statement->closeCursor(); + } + + /** + * Returns a list of properties for this nodes.; + * @param Node $node + * @param array $requestedProperties requested properties or empty array for "all" + * @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 + */ + private function getProperties(Node $node, array $requestedProperties) { + $path = $node->getPath(); + if (isset($this->cache[$path])) { + return $this->cache[$path]; + } + + // TODO: chunking if more than 1000 properties + $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'; + + $whereValues = array($this->user, $path); + $whereTypes = array(null, null); + + if (!empty($requestedProperties)) { + // request only a subset + $sql .= ' AND `propertyname` in (?)'; + $whereValues[] = $requestedProperties; + $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY; + } + + $result = $this->connection->executeQuery( + $sql, + $whereValues, + $whereTypes + ); + + $props = []; + while ($row = $result->fetch()) { + $props[$row['propertyname']] = $row['propertyvalue']; + } + + $result->closeCursor(); + + $this->cache[$path] = $props; + return $props; + } + + /** + * Update properties + * + * @param Node $node node for which to update properties + * @param array $properties array of properties to update + * + * @return bool + */ + private function updateProperties($node, $properties) { + $path = $node->getPath(); + + $deleteStatement = 'DELETE FROM `*PREFIX*properties`' . + ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'; + + $insertStatement = 'INSERT INTO `*PREFIX*properties`' . + ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)'; + + $updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' . + ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'; + + // TODO: use "insert or update" strategy ? + $existing = $this->getProperties($node, array()); + $this->connection->beginTransaction(); + 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)) { + $this->connection->executeUpdate($deleteStatement, + array( + $this->user, + $path, + $propertyName + ) + ); + } + } else { + if (!array_key_exists($propertyName, $existing)) { + $this->connection->executeUpdate($insertStatement, + array( + $this->user, + $path, + $propertyName, + $propertyValue + ) + ); + } else { + $this->connection->executeUpdate($updateStatement, + array( + $propertyValue, + $this->user, + $path, + $propertyName + ) + ); + } + } + } + + $this->connection->commit(); + unset($this->cache[$path]); + + return true; + } + + /** + * Bulk load properties for directory children + * + * @param Directory $node + * @param array $requestedProperties requested properties + * + * @return void + */ + private function loadChildrenProperties(Directory $node, $requestedProperties) { + $path = $node->getPath(); + if (isset($this->cache[$path])) { + // we already loaded them at some point + return; + } + + $childNodes = $node->getChildren(); + // pre-fill cache + foreach ($childNodes as $childNode) { + $this->cache[$childNode->getPath()] = []; + } + + $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` LIKE ?'; + $sql .= ' AND `propertyname` in (?) ORDER BY `propertypath`, `propertyname`'; + + $result = $this->connection->executeQuery( + $sql, + array($this->user, rtrim($path, '/') . '/%', $requestedProperties), + array(null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY) + ); + + $oldPath = null; + $props = []; + while ($row = $result->fetch()) { + $path = $row['propertypath']; + if ($oldPath !== $path) { + // save previously gathered props + $this->cache[$oldPath] = $props; + $oldPath = $path; + // prepare props for next path + $props = []; + } + $props[$row['propertyname']] = $row['propertyvalue']; + } + if (!is_null($oldPath)) { + // save props from last run + $this->cache[$oldPath] = $props; + } + + $result->closeCursor(); + } + +} diff --git a/apps/dav/lib/connector/sabre/directory.php b/apps/dav/lib/connector/sabre/directory.php new file mode 100644 index 00000000000..8c736ea0108 --- /dev/null +++ b/apps/dav/lib/connector/sabre/directory.php @@ -0,0 +1,276 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Bart Visscher <bartv@thisnet.nl> + * @author Björn Schießle <schiessle@owncloud.com> + * @author Jakob Sack <mail@jakobsack.de> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\Exception\InvalidPath; +use OCA\DAV\Connector\Sabre\Exception\FileLocked; +use OCP\Lock\ILockingProvider; +use OCP\Lock\LockedException; +use Sabre\DAV\Exception\Locked; + +class Directory extends \OCA\DAV\Connector\Sabre\Node + implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota { + + /** + * Cached directory content + * + * @var \OCP\Files\FileInfo[] + */ + private $dirContent; + + /** + * Cached quota info + * + * @var array + */ + private $quotaInfo; + + /** + * 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 successful 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 + * @return null|string + * @throws Exception\EntityTooLarge + * @throws Exception\UnsupportedMediaType + * @throws FileLocked + * @throws InvalidPath + * @throws \Sabre\DAV\Exception + * @throws \Sabre\DAV\Exception\BadRequest + * @throws \Sabre\DAV\Exception\Forbidden + * @throws \Sabre\DAV\Exception\ServiceUnavailable + */ + public function createFile($name, $data = null) { + + try { + // for chunked upload also updating a existing file is a "createFile" + // because we create all the chunks before re-assemble them to the existing file. + if (isset($_SERVER['HTTP_OC_CHUNKED'])) { + + // exit if we can't create a new file and we don't updatable existing file + $info = \OC_FileChunking::decodeName($name); + if (!$this->fileView->isCreatable($this->path) && + !$this->fileView->isUpdatable($this->path . '/' . $info['name']) + ) { + throw new \Sabre\DAV\Exception\Forbidden(); + } + + } else { + // For non-chunked upload it is enough to check if we can create a new file + if (!$this->fileView->isCreatable($this->path)) { + throw new \Sabre\DAV\Exception\Forbidden(); + } + } + + $this->fileView->verifyPath($this->path, $name); + + $path = $this->fileView->getAbsolutePath($this->path) . '/' . $name; + // using a dummy FileInfo is acceptable here since it will be refreshed after the put is complete + $info = new \OC\Files\FileInfo($path, null, null, array(), null); + $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info); + $node->acquireLock(ILockingProvider::LOCK_SHARED); + return $node->put($data); + } catch (\OCP\Files\StorageNotAvailableException $e) { + throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); + } catch (\OCP\Files\InvalidPathException $ex) { + throw new InvalidPath($ex->getMessage()); + } catch (LockedException $e) { + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @throws FileLocked + * @throws InvalidPath + * @throws \Sabre\DAV\Exception\Forbidden + * @throws \Sabre\DAV\Exception\ServiceUnavailable + */ + public function createDirectory($name) { + try { + if (!$this->info->isCreatable()) { + throw new \Sabre\DAV\Exception\Forbidden(); + } + + $this->fileView->verifyPath($this->path, $name); + $newPath = $this->path . '/' . $name; + if (!$this->fileView->mkdir($newPath)) { + throw new \Sabre\DAV\Exception\Forbidden('Could not create directory ' . $newPath); + } + } catch (\OCP\Files\StorageNotAvailableException $e) { + throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); + } catch (\OCP\Files\InvalidPathException $ex) { + throw new InvalidPath($ex->getMessage()); + } catch (LockedException $e) { + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns a specific child node, referenced by its name + * + * @param string $name + * @param \OCP\Files\FileInfo $info + * @return \Sabre\DAV\INode + * @throws InvalidPath + * @throws \Sabre\DAV\Exception\NotFound + * @throws \Sabre\DAV\Exception\ServiceUnavailable + */ + public function getChild($name, $info = null) { + $path = $this->path . '/' . $name; + if (is_null($info)) { + try { + $this->fileView->verifyPath($this->path, $name); + $info = $this->fileView->getFileInfo($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); + } catch (\OCP\Files\InvalidPathException $ex) { + throw new InvalidPath($ex->getMessage()); + } + } + + if (!$info) { + throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located'); + } + + if ($info['mimetype'] == 'httpd/unix-directory') { + $node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info); + } else { + $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info); + } + return $node; + } + + /** + * Returns an array with all the child nodes + * + * @return \Sabre\DAV\INode[] + */ + public function getChildren() { + if (!is_null($this->dirContent)) { + return $this->dirContent; + } + try { + $folderContent = $this->fileView->getDirectoryContent($this->path); + } catch (LockedException $e) { + throw new Locked(); + } + + $nodes = array(); + foreach ($folderContent as $info) { + $node = $this->getChild($info->getName(), $info); + $nodes[] = $node; + } + $this->dirContent = $nodes; + return $this->dirContent; + } + + /** + * Checks if a child exists. + * + * @param string $name + * @return bool + */ + public function childExists($name) { + // note: here we do NOT resolve the chunk file name to the real file name + // to make sure we return false when checking for file existence with a chunk + // file name. + // This is to make sure that "createFile" is still triggered + // (required old code) instead of "updateFile". + // + // TODO: resolve chunk file name here and implement "updateFile" + $path = $this->path . '/' . $name; + return $this->fileView->file_exists($path); + + } + + /** + * Deletes all files in this directory, and then itself + * + * @return void + * @throws FileLocked + * @throws \Sabre\DAV\Exception\Forbidden + */ + public function delete() { + + if ($this->path === '' || $this->path === '/' || !$this->info->isDeletable()) { + throw new \Sabre\DAV\Exception\Forbidden(); + } + + try { + if (!$this->fileView->rmdir($this->path)) { + // assume it wasn't possible to remove due to permission issue + throw new \Sabre\DAV\Exception\Forbidden(); + } + } catch (LockedException $e) { + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns available diskspace information + * + * @return array + */ + public function getQuotaInfo() { + if ($this->quotaInfo) { + return $this->quotaInfo; + } + try { + $storageInfo = \OC_Helper::getStorageInfo($this->info->getPath(), $this->info); + $this->quotaInfo = array( + $storageInfo['used'], + $storageInfo['free'] + ); + return $this->quotaInfo; + } catch (\OCP\Files\StorageNotAvailableException $e) { + return array(0, 0); + } + } + +} diff --git a/apps/dav/lib/connector/sabre/dummygetresponseplugin.php b/apps/dav/lib/connector/sabre/dummygetresponseplugin.php new file mode 100644 index 00000000000..7c7a332fedd --- /dev/null +++ b/apps/dav/lib/connector/sabre/dummygetresponseplugin.php @@ -0,0 +1,69 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; +use Sabre\HTTP\ResponseInterface; +use Sabre\HTTP\RequestInterface; + +/** + * Class DummyGetResponsePlugin is a plugin used to not show a "Not implemented" + * error to clients that rely on verifying the functionality of the ownCloud + * WebDAV backend using a simple GET to /. + * + * This is considered a legacy behaviour and implementers should consider sending + * a PROPFIND request instead to verify whether the WebDAV component is working + * properly. + * + * FIXME: Remove once clients are all compliant. + * + * @package OCA\DAV\Connector\Sabre + */ +class DummyGetResponsePlugin extends \Sabre\DAV\ServerPlugin { + /** @var \Sabre\DAV\Server */ + protected $server; + + /** + * @param \Sabre\DAV\Server $server + * @return void + */ + function initialize(\Sabre\DAV\Server $server) { + $this->server = $server; + $this->server->on('method:GET', [$this, 'httpGet'], 200); + } + + /** + * @param RequestInterface $request + * @param ResponseInterface $response + * @return false + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + $string = 'This is the WebDAV interface. It can only be accessed by ' . + 'WebDAV clients such as the ownCloud desktop sync client.'; + $stream = fopen('php://memory','r+'); + fwrite($stream, $string); + rewind($stream); + + $response->setStatus(200); + $response->setBody($stream); + + return false; + } +} diff --git a/apps/dav/lib/connector/sabre/exception/entitytoolarge.php b/apps/dav/lib/connector/sabre/exception/entitytoolarge.php new file mode 100644 index 00000000000..f5a7aa99c6d --- /dev/null +++ b/apps/dav/lib/connector/sabre/exception/entitytoolarge.php @@ -0,0 +1,44 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\Connector\Sabre\Exception; + +/** + * Entity Too Large + * + * This exception is thrown whenever a user tries to upload a file which exceeds hard limitations + * + */ +class EntityTooLarge extends \Sabre\DAV\Exception { + + /** + * Returns the HTTP status code for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 413; + + } + +} diff --git a/apps/dav/lib/connector/sabre/exception/filelocked.php b/apps/dav/lib/connector/sabre/exception/filelocked.php new file mode 100644 index 00000000000..1e1585edbda --- /dev/null +++ b/apps/dav/lib/connector/sabre/exception/filelocked.php @@ -0,0 +1,47 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Owen Winkler <a_github@midnightcircus.com> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre\Exception; + +use Exception; + +class FileLocked extends \Sabre\DAV\Exception { + + public function __construct($message = "", $code = 0, Exception $previous = null) { + if($previous instanceof \OCP\Files\LockNotAcquiredException) { + $message = sprintf('Target file %s is locked by another process.', $previous->path); + } + parent::__construct($message, $code, $previous); + } + + /** + * Returns the HTTP status code for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 423; + } +} diff --git a/apps/dav/lib/connector/sabre/exception/invalidpath.php b/apps/dav/lib/connector/sabre/exception/invalidpath.php new file mode 100644 index 00000000000..608e427a5aa --- /dev/null +++ b/apps/dav/lib/connector/sabre/exception/invalidpath.php @@ -0,0 +1,77 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre\Exception; + +use Sabre\DAV\Exception; + +class InvalidPath extends Exception { + + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + + /** + * @var bool + */ + private $retry; + + /** + * @param string $message + * @param bool $retry + */ + public function __construct($message, $retry = false) { + parent::__construct($message); + $this->retry = $retry; + } + + /** + * Returns the HTTP status code for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 400; + + } + + /** + * This method allows the exception to include additional information + * into the WebDAV error response + * + * @param \Sabre\DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + public function serialize(\Sabre\DAV\Server $server,\DOMElement $errorNode) { + + // set ownCloud namespace + $errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD); + + // adding the retry node + $error = $errorNode->ownerDocument->createElementNS('o:','o:retry', var_export($this->retry, true)); + $errorNode->appendChild($error); + + // adding the message node + $error = $errorNode->ownerDocument->createElementNS('o:','o:reason', $this->getMessage()); + $errorNode->appendChild($error); + } + +} diff --git a/apps/dav/lib/connector/sabre/exception/unsupportedmediatype.php b/apps/dav/lib/connector/sabre/exception/unsupportedmediatype.php new file mode 100644 index 00000000000..96b9b8332de --- /dev/null +++ b/apps/dav/lib/connector/sabre/exception/unsupportedmediatype.php @@ -0,0 +1,44 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\Connector\Sabre\Exception; + +/** + * Unsupported Media Type + * + * This exception is thrown whenever a user tries to upload a file which holds content which is not allowed + * + */ +class UnsupportedMediaType extends \Sabre\DAV\Exception { + + /** + * Returns the HTTP status code for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 415; + + } + +} diff --git a/apps/dav/lib/connector/sabre/exceptionloggerplugin.php b/apps/dav/lib/connector/sabre/exceptionloggerplugin.php new file mode 100644 index 00000000000..64ec5cfda82 --- /dev/null +++ b/apps/dav/lib/connector/sabre/exceptionloggerplugin.php @@ -0,0 +1,107 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use OCP\ILogger; +use Sabre\DAV\Exception; +use Sabre\HTTP\Response; + +class ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin { + protected $nonFatalExceptions = array( + 'Sabre\DAV\Exception\NotAuthenticated' => true, + // the sync client uses this to find out whether files exist, + // so it is not always an error, log it as debug + 'Sabre\DAV\Exception\NotFound' => true, + // this one mostly happens when the same file is uploaded at + // exactly the same time from two clients, only one client + // wins, the second one gets "Precondition failed" + 'Sabre\DAV\Exception\PreconditionFailed' => true, + // forbidden can be expected when trying to upload to + // read-only folders for example + 'Sabre\DAV\Exception\Forbidden' => true, + ); + + /** @var string */ + private $appName; + + /** @var ILogger */ + private $logger; + + /** + * @param string $loggerAppName app name to use when logging + * @param ILogger $logger + */ + public function __construct($loggerAppName, $logger) { + $this->appName = $loggerAppName; + $this->logger = $logger; + } + + /** + * 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) { + + $server->on('exception', array($this, 'logException'), 10); + } + + /** + * Log exception + * + */ + public function logException(\Exception $ex) { + $exceptionClass = get_class($ex); + $level = \OCP\Util::FATAL; + if (isset($this->nonFatalExceptions[$exceptionClass])) { + $level = \OCP\Util::DEBUG; + } + + $message = $ex->getMessage(); + if ($ex instanceof Exception) { + if (empty($message)) { + $response = new Response($ex->getHTTPCode()); + $message = $response->getStatusText(); + } + $message = "HTTP/1.1 {$ex->getHTTPCode()} $message"; + } + + $exception = [ + 'Message' => $message, + 'Exception' => $exceptionClass, + 'Code' => $ex->getCode(), + 'Trace' => $ex->getTraceAsString(), + 'File' => $ex->getFile(), + 'Line' => $ex->getLine(), + ]; + $this->logger->log($level, 'Exception: ' . json_encode($exception), ['app' => $this->appName]); + } +} diff --git a/apps/dav/lib/connector/sabre/file.php b/apps/dav/lib/connector/sabre/file.php new file mode 100644 index 00000000000..961532daf50 --- /dev/null +++ b/apps/dav/lib/connector/sabre/file.php @@ -0,0 +1,504 @@ +<?php +/** + * @author Bart Visscher <bartv@thisnet.nl> + * @author Björn Schießle <schiessle@owncloud.com> + * @author Jakob Sack <mail@jakobsack.de> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Owen Winkler <a_github@midnightcircus.com> + * @author Robin Appelman <icewind@owncloud.com> + * @author Robin McCorkell <rmccorkell@karoshi.org.uk> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Thomas Tanghus <thomas@tanghus.net> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use OC\Files\Filesystem; +use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge; +use OCA\DAV\Connector\Sabre\Exception\FileLocked; +use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType; +use OCP\Encryption\Exceptions\GenericEncryptionException; +use OCP\Files\EntityTooLargeException; +use OCP\Files\InvalidContentException; +use OCP\Files\InvalidPathException; +use OCP\Files\LockNotAcquiredException; +use OCP\Files\NotPermittedException; +use OCP\Files\StorageNotAvailableException; +use OCP\Lock\ILockingProvider; +use OCP\Lock\LockedException; +use Sabre\DAV\Exception; +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotImplemented; +use Sabre\DAV\Exception\ServiceUnavailable; +use Sabre\DAV\IFile; + +class File extends Node implements IFile { + + /** + * Updates the data + * + * The data argument is a readable stream resource. + * + * After a successful 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 Forbidden + * @throws UnsupportedMediaType + * @throws BadRequest + * @throws Exception + * @throws EntityTooLarge + * @throws ServiceUnavailable + * @throws FileLocked + * @return string|null + */ + public function put($data) { + try { + $exists = $this->fileView->file_exists($this->path); + if ($this->info && $exists && !$this->info->isUpdateable()) { + throw new Forbidden(); + } + } catch (StorageNotAvailableException $e) { + throw new ServiceUnavailable("File is not updatable: " . $e->getMessage()); + } + + // verify path of the target + $this->verifyPath(); + + // chunked handling + if (isset($_SERVER['HTTP_OC_CHUNKED'])) { + try { + return $this->createFileChunked($data); + } catch (\Exception $e) { + $this->convertToSabreException($e); + } + } + + list($partStorage) = $this->fileView->resolvePath($this->path); + $needsPartFile = $this->needsPartFile($partStorage) && (strlen($this->path) > 1); + + if ($needsPartFile) { + // mark file as partial while uploading (ignored by the scanner) + $partFilePath = $this->path . '.ocTransferId' . rand() . '.part'; + } else { + // upload file directly as the final path + $partFilePath = $this->path; + } + + // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share) + /** @var \OC\Files\Storage\Storage $partStorage */ + list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath); + /** @var \OC\Files\Storage\Storage $storage */ + list($storage, $internalPath) = $this->fileView->resolvePath($this->path); + try { + $target = $partStorage->fopen($internalPartPath, 'wb'); + if ($target === false) { + \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::fopen() failed', \OCP\Util::ERROR); + // because we have no clue about the cause we can only throw back a 500/Internal Server Error + throw new Exception('Could not write file contents'); + } + list($count,) = \OC_Helper::streamCopy($data, $target); + fclose($target); + + // if content length is sent by client: + // double check if the file was fully received + // compare expected and actual size + if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] !== 'LOCK') { + $expected = $_SERVER['CONTENT_LENGTH']; + if ($count != $expected) { + throw new BadRequest('expected filesize ' . $expected . ' got ' . $count); + } + } + + } catch (\Exception $e) { + if ($needsPartFile) { + $partStorage->unlink($internalPartPath); + } + $this->convertToSabreException($e); + } + + try { + $view = \OC\Files\Filesystem::getView(); + if ($view) { + $run = $this->emitPreHooks($exists); + } else { + $run = true; + } + + try { + $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE); + } catch (LockedException $e) { + if ($needsPartFile) { + $partStorage->unlink($internalPartPath); + } + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + + if ($needsPartFile) { + // rename to correct path + try { + if ($run) { + $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath); + $fileExists = $storage->file_exists($internalPath); + } + if (!$run || $renameOkay === false || $fileExists === false) { + \OCP\Util::writeLog('webdav', 'renaming part file to final file failed', \OCP\Util::ERROR); + throw new Exception('Could not rename part file to final file'); + } + } catch (\Exception $e) { + $partStorage->unlink($internalPartPath); + $this->convertToSabreException($e); + } + } + + try { + $this->changeLock(ILockingProvider::LOCK_SHARED); + } catch (LockedException $e) { + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + + // since we skipped the view we need to scan and emit the hooks ourselves + $this->fileView->getUpdater()->update($this->path); + + if ($view) { + $this->emitPostHooks($exists); + } + + // allow sync clients to send the mtime along in a header + $request = \OC::$server->getRequest(); + if (isset($request->server['HTTP_X_OC_MTIME'])) { + if ($this->fileView->touch($this->path, $request->server['HTTP_X_OC_MTIME'])) { + header('X-OC-MTime: accepted'); + } + } + $this->refreshInfo(); + } catch (StorageNotAvailableException $e) { + throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage()); + } + + return '"' . $this->info->getEtag() . '"'; + } + + private function emitPreHooks($exists, $path = null) { + if (is_null($path)) { + $path = $this->path; + } + $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path)); + $run = true; + + if (!$exists) { + \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, array( + \OC\Files\Filesystem::signal_param_path => $hookPath, + \OC\Files\Filesystem::signal_param_run => &$run, + )); + } else { + \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, array( + \OC\Files\Filesystem::signal_param_path => $hookPath, + \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 => $hookPath, + \OC\Files\Filesystem::signal_param_run => &$run, + )); + return $run; + } + + private function emitPostHooks($exists, $path = null) { + if (is_null($path)) { + $path = $this->path; + } + $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path)); + if (!$exists) { + \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, array( + \OC\Files\Filesystem::signal_param_path => $hookPath + )); + } else { + \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, array( + \OC\Files\Filesystem::signal_param_path => $hookPath + )); + } + \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, array( + \OC\Files\Filesystem::signal_param_path => $hookPath + )); + } + + /** + * Returns the data + * + * @return string|resource + * @throws Forbidden + * @throws ServiceUnavailable + */ + public function get() { + //throw exception if encryption is disabled but files are still encrypted + try { + $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb'); + if ($res === false) { + throw new ServiceUnavailable("Could not open file"); + } + return $res; + } catch (GenericEncryptionException $e) { + // returning 503 will allow retry of the operation at a later point in time + throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage()); + } catch (StorageNotAvailableException $e) { + throw new ServiceUnavailable("Failed to open file: " . $e->getMessage()); + } catch (LockedException $e) { + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Delete the current file + * + * @throws Forbidden + * @throws ServiceUnavailable + */ + public function delete() { + if (!$this->info->isDeletable()) { + throw new Forbidden(); + } + + try { + if (!$this->fileView->unlink($this->path)) { + // assume it wasn't possible to delete due to permissions + throw new Forbidden(); + } + } catch (StorageNotAvailableException $e) { + throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage()); + } catch (LockedException $e) { + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return mixed + */ + public function getContentType() { + $mimeType = $this->info->getMimetype(); + + // PROPFIND needs to return the correct mime type, for consistency with the web UI + if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') { + return $mimeType; + } + return \OC_Helper::getSecureMimeType($mimeType); + } + + /** + * @return array|false + */ + public function getDirectDownload() { + if (\OCP\App::isEnabled('encryption')) { + return []; + } + /** @var \OCP\Files\Storage $storage */ + list($storage, $internalPath) = $this->fileView->resolvePath($this->path); + if (is_null($storage)) { + return []; + } + + return $storage->getDirectDownload($internalPath); + } + + /** + * @param resource $data + * @return null|string + * @throws Exception + * @throws BadRequest + * @throws NotImplemented + * @throws ServiceUnavailable + */ + private function createFileChunked($data) { + list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($this->path); + + $info = \OC_FileChunking::decodeName($name); + if (empty($info)) { + throw new NotImplemented('Invalid chunk name'); + } + + $chunk_handler = new \OC_FileChunking($info); + $bytesWritten = $chunk_handler->store($info['index'], $data); + + //detect aborted upload + if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') { + if (isset($_SERVER['CONTENT_LENGTH'])) { + $expected = $_SERVER['CONTENT_LENGTH']; + if ($bytesWritten != $expected) { + $chunk_handler->remove($info['index']); + throw new BadRequest( + 'expected filesize ' . $expected . ' got ' . $bytesWritten); + } + } + } + + if ($chunk_handler->isComplete()) { + list($storage,) = $this->fileView->resolvePath($path); + $needsPartFile = $this->needsPartFile($storage); + $partFile = null; + + $targetPath = $path . '/' . $info['name']; + /** @var \OC\Files\Storage\Storage $targetStorage */ + list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath); + + $exists = $this->fileView->file_exists($targetPath); + + try { + $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED); + + $this->emitPreHooks($exists, $targetPath); + $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE); + /** @var \OC\Files\Storage\Storage $targetStorage */ + list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath); + + if ($needsPartFile) { + // we first assembly the target file as a part file + $partFile = $path . '/' . $info['name'] . '.ocTransferId' . $info['transferid'] . '.part'; + /** @var \OC\Files\Storage\Storage $targetStorage */ + list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile); + + + $chunk_handler->file_assemble($partStorage, $partInternalPath, $this->fileView->getAbsolutePath($targetPath)); + + // here is the final atomic rename + $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath); + $fileExists = $targetStorage->file_exists($targetInternalPath); + if ($renameOkay === false || $fileExists === false) { + \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::rename() failed', \OCP\Util::ERROR); + // only delete if an error occurred and the target file was already created + if ($fileExists) { + // set to null to avoid double-deletion when handling exception + // stray part file + $partFile = null; + $targetStorage->unlink($targetInternalPath); + } + $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED); + throw new Exception('Could not rename part file assembled from chunks'); + } + } else { + // assemble directly into the final file + $chunk_handler->file_assemble($targetStorage, $targetInternalPath, $this->fileView->getAbsolutePath($targetPath)); + } + + // allow sync clients to send the mtime along in a header + $request = \OC::$server->getRequest(); + if (isset($request->server['HTTP_X_OC_MTIME'])) { + if ($targetStorage->touch($targetInternalPath, $request->server['HTTP_X_OC_MTIME'])) { + header('X-OC-MTime: accepted'); + } + } + + $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED); + + // since we skipped the view we need to scan and emit the hooks ourselves + $this->fileView->getUpdater()->update($targetPath); + + $this->emitPostHooks($exists, $targetPath); + + $info = $this->fileView->getFileInfo($targetPath); + + $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED); + + return $info->getEtag(); + } catch (\Exception $e) { + if ($partFile !== null) { + $targetStorage->unlink($targetInternalPath); + } + $this->convertToSabreException($e); + } + } + + return null; + } + + /** + * Returns whether a part file is needed for the given storage + * or whether the file can be assembled/uploaded directly on the + * target storage. + * + * @param \OCP\Files\Storage $storage + * @return bool true if the storage needs part file handling + */ + private function needsPartFile($storage) { + // TODO: in the future use ChunkHandler provided by storage + // and/or add method on Storage called "needsPartFile()" + return !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') && + !$storage->instanceOfStorage('OC\Files\Storage\OwnCloud'); + } + + /** + * Convert the given exception to a SabreException instance + * + * @param \Exception $e + * + * @throws \Sabre\DAV\Exception + */ + private function convertToSabreException(\Exception $e) { + if ($e instanceof \Sabre\DAV\Exception) { + throw $e; + } + if ($e instanceof NotPermittedException) { + // a more general case - due to whatever reason the content could not be written + throw new Forbidden($e->getMessage(), 0, $e); + } + if ($e instanceof EntityTooLargeException) { + // the file is too big to be stored + throw new EntityTooLarge($e->getMessage(), 0, $e); + } + if ($e instanceof InvalidContentException) { + // the file content is not permitted + throw new UnsupportedMediaType($e->getMessage(), 0, $e); + } + if ($e instanceof InvalidPathException) { + // the path for the file was not valid + // TODO: find proper http status code for this case + throw new Forbidden($e->getMessage(), 0, $e); + } + if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) { + // the file is currently being written to by another process + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + if ($e instanceof GenericEncryptionException) { + // returning 503 will allow retry of the operation at a later point in time + throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e); + } + if ($e instanceof StorageNotAvailableException) { + throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e); + } + + throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e); + } +} diff --git a/apps/dav/lib/connector/sabre/filesplugin.php b/apps/dav/lib/connector/sabre/filesplugin.php new file mode 100644 index 00000000000..61b5360cac1 --- /dev/null +++ b/apps/dav/lib/connector/sabre/filesplugin.php @@ -0,0 +1,269 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Robin McCorkell <rmccorkell@karoshi.org.uk> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use Sabre\DAV\IFile; +use \Sabre\DAV\PropFind; +use \Sabre\DAV\PropPatch; +use \Sabre\HTTP\RequestInterface; +use \Sabre\HTTP\ResponseInterface; + +class FilesPlugin extends \Sabre\DAV\ServerPlugin { + + // namespace + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id'; + const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions'; + const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL'; + const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size'; + const GETETAG_PROPERTYNAME = '{DAV:}getetag'; + const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified'; + + /** + * Reference to main server object + * + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\Tree + */ + private $tree; + + /** + * Whether this is public webdav. + * If true, some returned information will be stripped off. + * + * @var bool + */ + private $isPublic; + + /** + * @var \OC\Files\View + */ + private $fileView; + + /** + * @param \Sabre\DAV\Tree $tree + * @param \OC\Files\View $view + * @param bool $isPublic + */ + public function __construct(\Sabre\DAV\Tree $tree, + \OC\Files\View $view, + $isPublic = false) { + $this->tree = $tree; + $this->fileView = $view; + $this->isPublic = $isPublic; + } + + /** + * 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) { + + $server->xmlNamespaces[self::NS_OWNCLOUD] = 'oc'; + $server->protectedProperties[] = self::FILEID_PROPERTYNAME; + $server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME; + $server->protectedProperties[] = self::SIZE_PROPERTYNAME; + $server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME; + + // normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH + $allowedProperties = ['{DAV:}getetag']; + $server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties); + + $this->server = $server; + $this->server->on('propFind', array($this, 'handleGetProperties')); + $this->server->on('propPatch', array($this, 'handleUpdateProperties')); + $this->server->on('afterBind', array($this, 'sendFileIdHeader')); + $this->server->on('afterWriteContent', array($this, 'sendFileIdHeader')); + $this->server->on('afterMethod:GET', [$this,'httpGet']); + $this->server->on('afterResponse', function($request, ResponseInterface $response) { + $body = $response->getBody(); + if (is_resource($body)) { + fclose($body); + } + }); + $this->server->on('beforeMove', [$this, 'checkMove']); + } + + /** + * Plugin that checks if a move can actually be performed. + * @param string $source source path + * @param string $destination destination path + * @throws \Sabre\DAV\Exception\Forbidden + */ + function checkMove($source, $destination) { + list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($source); + list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination); + + if ($sourceDir !== $destinationDir) { + $sourceFileInfo = $this->fileView->getFileInfo($source); + + if ($sourceFileInfo === false) { + throw new \Sabre\DAV\Exception\NotFound($source . ' does not exist'); + } + + if (!$sourceFileInfo->isDeletable()) { + throw new \Sabre\DAV\Exception\Forbidden($source . " cannot be deleted"); + } + } + } + + /** + * Plugin that adds a 'Content-Disposition: attachment' header to all files + * delivered by SabreDAV. + * @param RequestInterface $request + * @param ResponseInterface $response + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + // Only handle valid files + $node = $this->tree->getNodeForPath($request->getPath()); + if (!($node instanceof IFile)) return; + + $response->addHeader('Content-Disposition', 'attachment'); + } + + /** + * Adds all ownCloud-specific properties + * + * @param PropFind $propFind + * @param \Sabre\DAV\INode $node + * @return void + */ + public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) { + + if ($node instanceof \OCA\DAV\Connector\Sabre\Node) { + + $propFind->handle(self::FILEID_PROPERTYNAME, function() use ($node) { + return $node->getFileId(); + }); + + $propFind->handle(self::PERMISSIONS_PROPERTYNAME, function() use ($node) { + $perms = $node->getDavPermissions(); + if ($this->isPublic) { + // remove mount information + $perms = str_replace(['S', 'M'], '', $perms); + } + return $perms; + }); + + $propFind->handle(self::GETETAG_PROPERTYNAME, function() use ($node) { + return $node->getEtag(); + }); + } + + if ($node instanceof \OCA\DAV\Connector\Sabre\File) { + $propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function() use ($node) { + /** @var $node \OCA\DAV\Connector\Sabre\File */ + $directDownloadUrl = $node->getDirectDownload(); + if (isset($directDownloadUrl['url'])) { + return $directDownloadUrl['url']; + } + return false; + }); + } + + if ($node instanceof \OCA\DAV\Connector\Sabre\Directory) { + $propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) { + return $node->getSize(); + }); + } + } + + /** + * Update ownCloud-specific properties + * + * @param string $path + * @param PropPatch $propPatch + * + * @return void + */ + public function handleUpdateProperties($path, PropPatch $propPatch) { + $propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function($time) use ($path) { + if (empty($time)) { + return false; + } + $node = $this->tree->getNodeForPath($path); + if (is_null($node)) { + return 404; + } + $node->touch($time); + return true; + }); + $propPatch->handle(self::GETETAG_PROPERTYNAME, function($etag) use ($path) { + if (empty($etag)) { + return false; + } + $node = $this->tree->getNodeForPath($path); + if (is_null($node)) { + return 404; + } + if ($node->setEtag($etag) !== -1) { + return true; + } + return false; + }); + } + + /** + * @param string $filePath + * @param \Sabre\DAV\INode $node + * @throws \Sabre\DAV\Exception\BadRequest + */ + public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) { + // chunked upload handling + if (isset($_SERVER['HTTP_OC_CHUNKED'])) { + list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($filePath); + $info = \OC_FileChunking::decodeName($name); + if (!empty($info)) { + $filePath = $path . '/' . $info['name']; + } + } + + // we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder + if (!$this->server->tree->nodeExists($filePath)) { + return; + } + $node = $this->server->tree->getNodeForPath($filePath); + if ($node instanceof \OCA\DAV\Connector\Sabre\Node) { + $fileId = $node->getFileId(); + if (!is_null($fileId)) { + $this->server->httpResponse->setHeader('OC-FileId', $fileId); + } + } + } + +} diff --git a/apps/dav/lib/connector/sabre/listenerplugin.php b/apps/dav/lib/connector/sabre/listenerplugin.php new file mode 100644 index 00000000000..d537d0577c6 --- /dev/null +++ b/apps/dav/lib/connector/sabre/listenerplugin.php @@ -0,0 +1,68 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use OCP\AppFramework\Http; +use OCP\SabrePluginEvent; +use OCP\SabrePluginException; +use Sabre\DAV\ServerPlugin; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class ListenerPlugin extends ServerPlugin { + /** @var EventDispatcherInterface */ + protected $dispatcher; + + /** + * @param EventDispatcherInterface $dispatcher + */ + public function __construct(EventDispatcherInterface $dispatcher) { + $this->dispatcher = $dispatcher; + } + + /** + * This initialize the plugin + * + * @param \Sabre\DAV\Server $server + */ + public function initialize(\Sabre\DAV\Server $server) { + $server->on('beforeMethod', array($this, 'emitListener'), 15); + } + + /** + * This method is called before any HTTP method and returns http status code 503 + * in case the system is in maintenance mode. + * + * @return bool + * @throws \Exception + */ + public function emitListener() { + $event = new SabrePluginEvent(); + + $this->dispatcher->dispatch('OCA\DAV\Connector\Sabre::beforeMethod', $event); + + if ($event->getStatusCode() !== Http::STATUS_OK) { + throw new SabrePluginException($event->getMessage(), $event->getStatusCode()); + } + + return true; + } +} diff --git a/apps/dav/lib/connector/sabre/lockplugin.php b/apps/dav/lib/connector/sabre/lockplugin.php new file mode 100644 index 00000000000..5840e59854c --- /dev/null +++ b/apps/dav/lib/connector/sabre/lockplugin.php @@ -0,0 +1,95 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\Exception\FileLocked; +use OCA\DAV\Connector\Sabre\Node; +use OCP\Lock\ILockingProvider; +use OCP\Lock\LockedException; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\ServerPlugin; +use Sabre\DAV\Tree; +use Sabre\HTTP\RequestInterface; + +class LockPlugin extends ServerPlugin { + /** + * Reference to main server object + * + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\Tree + */ + private $tree; + + /** + * @param \Sabre\DAV\Tree $tree tree + */ + public function __construct(Tree $tree) { + $this->tree = $tree; + } + + /** + * {@inheritdoc} + */ + public function initialize(\Sabre\DAV\Server $server) { + $this->server = $server; + $this->server->on('beforeMethod', [$this, 'getLock'], 50); + $this->server->on('afterMethod', [$this, 'releaseLock'], 50); + } + + public function getLock(RequestInterface $request) { + // we cant listen on 'beforeMethod:PUT' due to order of operations with setting up the tree + // so instead we limit ourselves to the PUT method manually + if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) { + return; + } + try { + $node = $this->tree->getNodeForPath($request->getPath()); + } catch (NotFound $e) { + return; + } + if ($node instanceof Node) { + try { + $node->acquireLock(ILockingProvider::LOCK_SHARED); + } catch (LockedException $e) { + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + } + } + + public function releaseLock(RequestInterface $request) { + if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) { + return; + } + try { + $node = $this->tree->getNodeForPath($request->getPath()); + } catch (NotFound $e) { + return; + } + if ($node instanceof Node) { + $node->releaseLock(ILockingProvider::LOCK_SHARED); + } + } +} diff --git a/apps/dav/lib/connector/sabre/maintenanceplugin.php b/apps/dav/lib/connector/sabre/maintenanceplugin.php new file mode 100644 index 00000000000..b9b261fbe05 --- /dev/null +++ b/apps/dav/lib/connector/sabre/maintenanceplugin.php @@ -0,0 +1,92 @@ +<?php +/** + * @author Bart Visscher <bartv@thisnet.nl> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use OCP\IConfig; +use Sabre\DAV\Exception\ServiceUnavailable; +use Sabre\DAV\ServerPlugin; + +class MaintenancePlugin extends ServerPlugin { + + /** @var IConfig */ + private $config; + + /** + * Reference to main server object + * + * @var Server + */ + private $server; + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config = null) { + $this->config = $config; + if (is_null($config)) { + $this->config = \OC::$server->getConfig(); + } + } + + + /** + * 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->on('beforeMethod', array($this, 'checkMaintenanceMode'), 1); + } + + /** + * This method is called before any HTTP method and returns http status code 503 + * in case the system is in maintenance mode. + * + * @throws ServiceUnavailable + * @return bool + */ + public function checkMaintenanceMode() { + if ($this->config->getSystemValue('singleuser', false)) { + throw new ServiceUnavailable('System in single user mode.'); + } + if ($this->config->getSystemValue('maintenance', false)) { + throw new ServiceUnavailable('System in maintenance mode.'); + } + if (\OC::checkUpgrade(false)) { + throw new ServiceUnavailable('Upgrade needed'); + } + + return true; + } +} diff --git a/apps/dav/lib/connector/sabre/node.php b/apps/dav/lib/connector/sabre/node.php new file mode 100644 index 00000000000..814aaceb077 --- /dev/null +++ b/apps/dav/lib/connector/sabre/node.php @@ -0,0 +1,270 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Bart Visscher <bartv@thisnet.nl> + * @author Jakob Sack <mail@jakobsack.de> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Klaas Freitag <freitag@owncloud.com> + * @author Markus Goetz <markus@woboq.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\Exception\InvalidPath; + + +abstract class Node implements \Sabre\DAV\INode { + + /** + * @var \OC\Files\View + */ + protected $fileView; + + /** + * The path to the current node + * + * @var string + */ + protected $path; + + /** + * node properties cache + * + * @var array + */ + protected $property_cache = null; + + /** + * @var \OCP\Files\FileInfo + */ + protected $info; + + /** + * Sets up the node, expects a full path name + * + * @param \OC\Files\View $view + * @param \OCP\Files\FileInfo $info + */ + public function __construct($view, $info) { + $this->fileView = $view; + $this->path = $this->fileView->getRelativePath($info->getPath()); + $this->info = $info; + } + + protected function refreshInfo() { + $this->info = $this->fileView->getFileInfo($this->path); + } + + /** + * Returns the name of the node + * + * @return string + */ + public function getName() { + return $this->info->getName(); + } + + /** + * Returns the full path + * + * @return string + */ + public function getPath() { + return $this->path; + } + + /** + * Renames the node + * + * @param string $name The new name + * @throws \Sabre\DAV\Exception\BadRequest + * @throws \Sabre\DAV\Exception\Forbidden + */ + public function setName($name) { + + // rename is only allowed if the update privilege is granted + if (!$this->info->isUpdateable()) { + throw new \Sabre\DAV\Exception\Forbidden(); + } + + list($parentPath,) = \Sabre\HTTP\URLUtil::splitPath($this->path); + list(, $newName) = \Sabre\HTTP\URLUtil::splitPath($name); + + // verify path of the target + $this->verifyPath(); + + $newPath = $parentPath . '/' . $newName; + + $this->fileView->rename($this->path, $newPath); + + $this->path = $newPath; + + $this->refreshInfo(); + } + + public function setPropertyCache($property_cache) { + $this->property_cache = $property_cache; + } + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int timestamp as integer + */ + public function getLastModified() { + $timestamp = $this->info->getMtime(); + if (!empty($timestamp)) { + return (int)$timestamp; + } + return $timestamp; + } + + /** + * 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) { + $this->fileView->touch($this->path, $mtime); + $this->refreshInfo(); + } + + /** + * 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 + * arbitrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * + * @return string + */ + public function getETag() { + return '"' . $this->info->getEtag() . '"'; + } + + /** + * Sets the ETag + * + * @param string $etag + * + * @return int file id of updated file or -1 on failure + */ + public function setETag($etag) { + return $this->fileView->putFileInfo($this->path, array('etag' => $etag)); + } + + /** + * Returns the size of the node, in bytes + * + * @return int|float + */ + public function getSize() { + return $this->info->getSize(); + } + + /** + * Returns the cache's file id + * + * @return int + */ + public function getId() { + return $this->info->getId(); + } + + /** + * @return string|null + */ + public function getFileId() { + if ($this->info->getId()) { + $instanceId = \OC_Util::getInstanceId(); + $id = sprintf('%08d', $this->info->getId()); + return $id . $instanceId; + } + + return null; + } + + /** + * @return string|null + */ + public function getDavPermissions() { + $p = ''; + if ($this->info->isShared()) { + $p .= 'S'; + } + if ($this->info->isShareable()) { + $p .= 'R'; + } + if ($this->info->isMounted()) { + $p .= 'M'; + } + if ($this->info->isDeletable()) { + $p .= 'D'; + } + if ($this->info->isDeletable()) { + $p .= 'NV'; // Renameable, Moveable + } + if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) { + if ($this->info->isUpdateable()) { + $p .= 'W'; + } + } else { + if ($this->info->isCreatable()) { + $p .= 'CK'; + } + } + return $p; + } + + protected function verifyPath() { + try { + $fileName = basename($this->info->getPath()); + $this->fileView->verifyPath($this->path, $fileName); + } catch (\OCP\Files\InvalidPathException $ex) { + throw new InvalidPath($ex->getMessage()); + } + } + + /** + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + */ + public function acquireLock($type) { + $this->fileView->lockFile($this->path, $type); + } + + /** + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + */ + public function releaseLock($type) { + $this->fileView->unlockFile($this->path, $type); + } + + /** + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + */ + public function changeLock($type) { + $this->fileView->changeLock($this->path, $type); + } +} diff --git a/apps/dav/lib/connector/sabre/objecttree.php b/apps/dav/lib/connector/sabre/objecttree.php new file mode 100644 index 00000000000..80c0ef74610 --- /dev/null +++ b/apps/dav/lib/connector/sabre/objecttree.php @@ -0,0 +1,284 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\Exception\InvalidPath; +use OCA\DAV\Connector\Sabre\Exception\FileLocked; +use OC\Files\FileInfo; +use OC\Files\Mount\MoveableMount; +use OCP\Files\StorageInvalidException; +use OCP\Files\StorageNotAvailableException; +use OCP\Lock\LockedException; + +class ObjectTree extends \Sabre\DAV\Tree { + + /** + * @var \OC\Files\View + */ + protected $fileView; + + /** + * @var \OCP\Files\Mount\IMountManager + */ + protected $mountManager; + + /** + * Creates the object + */ + public function __construct() { + } + + /** + * @param \Sabre\DAV\INode $rootNode + * @param \OC\Files\View $view + * @param \OCP\Files\Mount\IMountManager $mountManager + */ + public function init(\Sabre\DAV\INode $rootNode, \OC\Files\View $view, \OCP\Files\Mount\IMountManager $mountManager) { + $this->rootNode = $rootNode; + $this->fileView = $view; + $this->mountManager = $mountManager; + } + + /** + * If the given path is a chunked file name, converts it + * to the real file name. Only applies if the OC-CHUNKED header + * is present. + * + * @param string $path chunk file path to convert + * + * @return string path to real file + */ + private function resolveChunkFile($path) { + if (isset($_SERVER['HTTP_OC_CHUNKED'])) { + // resolve to real file name to find the proper node + list($dir, $name) = \Sabre\HTTP\URLUtil::splitPath($path); + if ($dir == '/' || $dir == '.') { + $dir = ''; + } + + $info = \OC_FileChunking::decodeName($name); + // only replace path if it was really the chunked file + if (isset($info['transferid'])) { + // getNodePath is called for multiple nodes within a chunk + // upload call + $path = $dir . '/' . $info['name']; + $path = ltrim($path, '/'); + } + } + return $path; + } + + /** + * Returns the INode object for the requested path + * + * @param string $path + * @throws \Sabre\DAV\Exception\ServiceUnavailable + * @throws \Sabre\DAV\Exception\NotFound + * @return \Sabre\DAV\INode + */ + public function getNodeForPath($path) { + if (!$this->fileView) { + throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup'); + } + + $path = trim($path, '/'); + if ($path) { + try { + $this->fileView->verifyPath($path, basename($path)); + } catch (\OCP\Files\InvalidPathException $ex) { + throw new InvalidPath($ex->getMessage()); + } + } + + if (isset($this->cache[$path])) { + return $this->cache[$path]; + } + + // Is it the root node? + if (!strlen($path)) { + return $this->rootNode; + } + + if (pathinfo($path, PATHINFO_EXTENSION) === 'part') { + // read from storage + $absPath = $this->fileView->getAbsolutePath($path); + $mount = $this->fileView->getMount($path); + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($absPath); + if ($storage) { + /** + * @var \OC\Files\Storage\Storage $storage + */ + $scanner = $storage->getScanner($internalPath); + // get data directly + $data = $scanner->getData($internalPath); + $info = new FileInfo($absPath, $storage, $internalPath, $data, $mount); + } else { + $info = null; + } + } else { + // resolve chunk file name to real name, if applicable + $path = $this->resolveChunkFile($path); + + // read from cache + try { + $info = $this->fileView->getFileInfo($path); + } catch (StorageNotAvailableException $e) { + throw new \Sabre\DAV\Exception\ServiceUnavailable('Storage not available'); + } catch (StorageInvalidException $e) { + throw new \Sabre\DAV\Exception\NotFound('Storage ' . $path . ' is invalid'); + } catch (LockedException $e) { + throw new \Sabre\DAV\Exception\Locked(); + } + } + + if (!$info) { + throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located'); + } + + if ($info->getType() === 'dir') { + $node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info); + } else { + $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $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\BadRequest + * @throws \Sabre\DAV\Exception\ServiceUnavailable + * @throws \Sabre\DAV\Exception\Forbidden + * @return int + */ + public function move($sourcePath, $destinationPath) { + if (!$this->fileView) { + throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup'); + } + + $targetNodeExists = $this->nodeExists($destinationPath); + $sourceNode = $this->getNodeForPath($sourcePath); + if ($sourceNode instanceof \Sabre\DAV\ICollection && $targetNodeExists) { + throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode . ', target exists'); + } + list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($sourcePath); + list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destinationPath); + + $isMovableMount = false; + $sourceMount = $this->mountManager->find($this->fileView->getAbsolutePath($sourcePath)); + $internalPath = $sourceMount->getInternalPath($this->fileView->getAbsolutePath($sourcePath)); + if ($sourceMount instanceof MoveableMount && $internalPath === '') { + $isMovableMount = true; + } + + try { + $sameFolder = ($sourceDir === $destinationDir); + // if we're overwriting or same folder + if ($targetNodeExists || $sameFolder) { + // note that renaming a share mount point is always allowed + if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) { + throw new \Sabre\DAV\Exception\Forbidden(); + } + } else { + if (!$this->fileView->isCreatable($destinationDir)) { + throw new \Sabre\DAV\Exception\Forbidden(); + } + } + + if (!$sameFolder) { + // moving to a different folder, source will be gone, like a deletion + // note that moving a share mount point is always allowed + if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) { + throw new \Sabre\DAV\Exception\Forbidden(); + } + } + + $fileName = basename($destinationPath); + try { + $this->fileView->verifyPath($destinationDir, $fileName); + } catch (\OCP\Files\InvalidPathException $ex) { + throw new InvalidPath($ex->getMessage()); + } + + $renameOkay = $this->fileView->rename($sourcePath, $destinationPath); + if (!$renameOkay) { + throw new \Sabre\DAV\Exception\Forbidden(''); + } + } catch (StorageNotAvailableException $e) { + throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); + } catch (LockedException $e) { + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + + $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 + * @throws \Sabre\DAV\Exception\ServiceUnavailable + * @return void + */ + public function copy($source, $destination) { + if (!$this->fileView) { + throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup'); + } + + // this will trigger existence check + $this->getNodeForPath($source); + + list($destinationDir, $destinationName) = \Sabre\HTTP\URLUtil::splitPath($destination); + try { + $this->fileView->verifyPath($destinationDir, $destinationName); + } catch (\OCP\Files\InvalidPathException $ex) { + throw new InvalidPath($ex->getMessage()); + } + + try { + $this->fileView->copy($source, $destination); + } catch (StorageNotAvailableException $e) { + throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); + } catch (LockedException $e) { + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + + list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination); + $this->markDirty($destinationDir); + } +} diff --git a/apps/dav/lib/connector/sabre/principal.php b/apps/dav/lib/connector/sabre/principal.php new file mode 100644 index 00000000000..35215e1f63c --- /dev/null +++ b/apps/dav/lib/connector/sabre/principal.php @@ -0,0 +1,205 @@ +<?php +/** + * @author Bart Visscher <bartv@thisnet.nl> + * @author Felix Moeller <mail@felixmoeller.de> + * @author Jakob Sack <mail@jakobsack.de> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Sebastian Döll <sebastian.doell@libasys.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Thomas Tanghus <thomas@tanghus.net> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use OCP\IUserManager; +use OCP\IConfig; +use \Sabre\DAV\PropPatch; + +class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface { + /** @var IConfig */ + private $config; + /** @var IUserManager */ + private $userManager; + + /** + * @param IConfig $config + * @param IUserManager $userManager + */ + public function __construct(IConfig $config, + IUserManager $userManager) { + $this->config = $config; + $this->userManager = $userManager; + } + + /** + * 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 string[] + */ + public function getPrincipalsByPrefix($prefixPath) { + $principals = []; + + if ($prefixPath === 'principals') { + foreach($this->userManager->search('') as $user) { + + $principal = [ + 'uri' => 'principals/' . $user->getUID(), + '{DAV:}displayname' => $user->getUID(), + ]; + + $email = $this->config->getUserValue($user->getUID(), 'settings', 'email'); + if(!empty($email)) { + $principal['{http://sabredav.org/ns}email-address'] = $email; + } + + $principals[] = $principal; + } + } + + 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); + $user = $this->userManager->get($name); + + if ($prefix === 'principals' && !is_null($user)) { + $principal = [ + 'uri' => 'principals/' . $user->getUID(), + '{DAV:}displayname' => $user->getUID(), + ]; + + $email = $this->config->getUserValue($user->getUID(), 'settings', 'email'); + if($email) { + $principal['{http://sabredav.org/ns}email-address'] = $email; + } + + return $principal; + } + + return null; + } + + /** + * Returns the list of members for a group-principal + * + * @param string $principal + * @return string[] + * @throws \Sabre\DAV\Exception + */ + 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 [$principal['uri']]; + } + + /** + * Returns the list of groups a principal is a member of + * + * @param string $principal + * @return array + * @throws \Sabre\DAV\Exception + */ + public function getGroupMembership($principal) { + list($prefix, $name) = \Sabre\HTTP\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 + * @throws \Sabre\DAV\Exception + */ + public function setGroupMemberSet($principal, array $members) { + throw new \Sabre\DAV\Exception('Setting members of the group is not supported yet'); + } + + /** + * @param string $path + * @param PropPatch $propPatch + * @return int + */ + function updatePrincipal($path, PropPatch $propPatch) { + return 0; + } + + /** + * @param string $prefixPath + * @param array $searchProperties + * @param string $test + * @return array + */ + function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { + return []; + } + + /** + * @param string $uri + * @param string $principalPrefix + * @return string + */ + function findByUri($uri, $principalPrefix) { + return ''; + } +} diff --git a/apps/dav/lib/connector/sabre/quotaplugin.php b/apps/dav/lib/connector/sabre/quotaplugin.php new file mode 100644 index 00000000000..8340d489dc0 --- /dev/null +++ b/apps/dav/lib/connector/sabre/quotaplugin.php @@ -0,0 +1,141 @@ +<?php +/** + * @author Felix Moeller <mail@felixmoeller.de> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Robin McCorkell <rmccorkell@karoshi.org.uk> + * @author scambra <sergio@entrecables.com> + * @author Scrutinizer Auto-Fixer <auto-fixer@scrutinizer-ci.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\Connector\Sabre; + +/** + * 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 QuotaPlugin extends \Sabre\DAV\ServerPlugin { + + /** + * @var \OC\Files\View + */ + private $view; + + /** + * Reference to main server object + * + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @param \OC\Files\View $view + */ + public function __construct($view) { + $this->view = $view; + } + + /** + * 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->on('beforeWriteContent', array($this, 'checkQuota'), 10); + $server->on('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 $uri + * @param null $data + * @throws \Sabre\DAV\Exception\InsufficientStorage + * @return bool + */ + public function checkQuota($uri, $data = null) { + $length = $this->getLength(); + if ($length) { + if (substr($uri, 0, 1) !== '/') { + $uri = '/' . $uri; + } + list($parentUri, $newName) = \Sabre\HTTP\URLUtil::splitPath($uri); + if(is_null($parentUri)) { + $parentUri = ''; + } + $req = $this->server->httpRequest; + if ($req->getHeader('OC-Chunked')) { + $info = \OC_FileChunking::decodeName($newName); + $chunkHandler = new \OC_FileChunking($info); + // subtract the already uploaded size to see whether + // there is still enough space for the remaining chunks + $length -= $chunkHandler->getCurrentSize(); + } + $freeSpace = $this->getFreeSpace($parentUri); + if ($freeSpace !== \OCP\Files\FileInfo::SPACE_UNKNOWN && $length > $freeSpace) { + if (isset($chunkHandler)) { + $chunkHandler->cleanup(); + } + 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 string $parentUri + * @return mixed + */ + public function getFreeSpace($parentUri) { + try { + $freeSpace = $this->view->free_space($parentUri); + return $freeSpace; + } catch (\OCP\Files\StorageNotAvailableException $e) { + throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); + } + } +} diff --git a/apps/dav/lib/connector/sabre/server.php b/apps/dav/lib/connector/sabre/server.php new file mode 100644 index 00000000000..eafe1b537f8 --- /dev/null +++ b/apps/dav/lib/connector/sabre/server.php @@ -0,0 +1,44 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author scolebrook <scolebrook@mac.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +/** + * Class \OCA\DAV\Connector\Sabre\Server + * + * This class overrides some methods from @see \Sabre\DAV\Server. + * + * @see \Sabre\DAV\Server + */ +class Server extends \Sabre\DAV\Server { + + /** + * @see \Sabre\DAV\Server + */ + public function __construct($treeOrNode = null) { + parent::__construct($treeOrNode); + self::$exposeVersion = false; + $this->enablePropfindDepthInfinity = true; + } +} diff --git a/apps/dav/lib/connector/sabre/serverfactory.php b/apps/dav/lib/connector/sabre/serverfactory.php new file mode 100644 index 00000000000..b62f90ab802 --- /dev/null +++ b/apps/dav/lib/connector/sabre/serverfactory.php @@ -0,0 +1,113 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use OCP\Files\Mount\IMountManager; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\ITagManager; +use OCP\IUserSession; +use Sabre\DAV\Auth\Backend\BackendInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class ServerFactory { + public function __construct( + IConfig $config, + ILogger $logger, + IDBConnection $databaseConnection, + IUserSession $userSession, + IMountManager $mountManager, + ITagManager $tagManager, + EventDispatcherInterface $dispatcher + ) { + $this->config = $config; + $this->logger = $logger; + $this->databaseConnection = $databaseConnection; + $this->userSession = $userSession; + $this->mountManager = $mountManager; + $this->tagManager = $tagManager; + $this->dispatcher = $dispatcher; + } + + /** + * @param string $baseUri + * @param string $requestUri + * @param BackendInterface $authBackend + * @param callable $viewCallBack callback that should return the view for the dav endpoint + * @return Server + */ + public function createServer($baseUri, $requestUri, BackendInterface $authBackend, callable $viewCallBack) { + // Fire up server + $objectTree = new \OCA\DAV\Connector\Sabre\ObjectTree(); + $server = new \OCA\DAV\Connector\Sabre\Server($objectTree); + // Set URL explicitly due to reverse-proxy situations + $server->httpRequest->setUrl($requestUri); + $server->setBaseUri($baseUri); + + // Load plugins + $defaults = new \OC_Defaults(); + $server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin($this->config)); + $server->addPlugin(new \OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin($this->config)); + $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, $defaults->getName())); + // FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / + $server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin()); + $server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $this->logger)); + $server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin($objectTree)); + $server->addPlugin(new \OCA\DAV\Connector\Sabre\ListenerPlugin($this->dispatcher)); + + // wait with registering these until auth is handled and the filesystem is setup + $server->on('beforeMethod', function () use ($server, $objectTree, $viewCallBack) { + /** @var \OC\Files\View $view */ + $view = $viewCallBack(); + $rootInfo = $view->getFileInfo(''); + + // Create ownCloud Dir + if ($rootInfo->getType() === 'dir') { + $root = new \OCA\DAV\Connector\Sabre\Directory($view, $rootInfo); + } else { + $root = new \OCA\DAV\Connector\Sabre\File($view, $rootInfo); + } + $objectTree->init($root, $view, $this->mountManager); + + $server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesPlugin($objectTree, $view)); + $server->addPlugin(new \OCA\DAV\Connector\Sabre\QuotaPlugin($view)); + + if($this->userSession->isLoggedIn()) { + $server->addPlugin(new \OCA\DAV\Connector\Sabre\TagsPlugin($objectTree, $this->tagManager)); + // custom properties plugin must be the last one + $server->addPlugin( + new \Sabre\DAV\PropertyStorage\Plugin( + new \OCA\DAV\Connector\Sabre\CustomPropertiesBackend( + $objectTree, + $this->databaseConnection, + $this->userSession->getUser() + ) + ) + ); + } + $server->addPlugin(new \OCA\DAV\Connector\Sabre\CopyEtagHeaderPlugin()); + }, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request + return $server; + } +} diff --git a/apps/dav/lib/connector/sabre/taglist.php b/apps/dav/lib/connector/sabre/taglist.php new file mode 100644 index 00000000000..177cc23e805 --- /dev/null +++ b/apps/dav/lib/connector/sabre/taglist.php @@ -0,0 +1,103 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +use Sabre\DAV; + +/** + * TagList property + * + * This property contains multiple "tag" elements, each containing a tag name. + */ +class TagList extends DAV\Property { + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + + /** + * tags + * + * @var array + */ + private $tags; + + /** + * @param array $tags + */ + public function __construct(array $tags) { + $this->tags = $tags; + } + + /** + * Returns the tags + * + * @return array + */ + public function getTags() { + + return $this->tags; + + } + + /** + * Serializes this property. + * + * @param DAV\Server $server + * @param \DOMElement $dom + * @return void + */ + public function serialize(DAV\Server $server,\DOMElement $dom) { + + $prefix = $server->xmlNamespaces[self::NS_OWNCLOUD]; + + foreach($this->tags as $tag) { + + $elem = $dom->ownerDocument->createElement($prefix . ':tag'); + $elem->appendChild($dom->ownerDocument->createTextNode($tag)); + + $dom->appendChild($elem); + } + + } + + /** + * Unserializes this property from a DOM Element + * + * This method returns an instance of this class. + * It will only decode tag values. + * + * @param \DOMElement $dom + * @param array $propertyMap + * @return \OCA\DAV\Connector\Sabre\TagList + */ + static function unserialize(\DOMElement $dom, array $propertyMap) { + + $tags = array(); + foreach($dom->childNodes as $child) { + if (DAV\XMLUtil::toClarkNotation($child)==='{' . self::NS_OWNCLOUD . '}tag') { + $tags[] = $child->textContent; + } + } + return new self($tags); + + } + +} diff --git a/apps/dav/lib/connector/sabre/tagsplugin.php b/apps/dav/lib/connector/sabre/tagsplugin.php new file mode 100644 index 00000000000..7446d97790b --- /dev/null +++ b/apps/dav/lib/connector/sabre/tagsplugin.php @@ -0,0 +1,292 @@ +<?php +/** + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\Connector\Sabre; + +/** + * ownCloud + * + * @author Vincent Petry + * @copyright 2014 Vincent Petry <pvince81@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 \Sabre\DAV\PropFind; +use \Sabre\DAV\PropPatch; + +class TagsPlugin extends \Sabre\DAV\ServerPlugin +{ + + // namespace + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + const TAGS_PROPERTYNAME = '{http://owncloud.org/ns}tags'; + const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite'; + const TAG_FAVORITE = '_$!<Favorite>!$_'; + + /** + * Reference to main server object + * + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \OCP\ITagManager + */ + private $tagManager; + + /** + * @var \OCP\ITags + */ + private $tagger; + + /** + * Array of file id to tags array + * The null value means the cache wasn't initialized. + * + * @var array + */ + private $cachedTags; + + /** + * @var \Sabre\DAV\Tree + */ + private $tree; + + /** + * @param \Sabre\DAV\Tree $tree tree + * @param \OCP\ITagManager $tagManager tag manager + */ + public function __construct(\Sabre\DAV\Tree $tree, \OCP\ITagManager $tagManager) { + $this->tree = $tree; + $this->tagManager = $tagManager; + $this->tagger = null; + $this->cachedTags = array(); + } + + /** + * 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) { + + $server->xmlNamespaces[self::NS_OWNCLOUD] = 'oc'; + $server->propertyMap[self::TAGS_PROPERTYNAME] = 'OC\\Connector\\Sabre\\TagList'; + + $this->server = $server; + $this->server->on('propFind', array($this, 'handleGetProperties')); + $this->server->on('propPatch', array($this, 'handleUpdateProperties')); + } + + /** + * Returns the tagger + * + * @return \OCP\ITags tagger + */ + private function getTagger() { + if (!$this->tagger) { + $this->tagger = $this->tagManager->load('files'); + } + return $this->tagger; + } + + /** + * Returns tags and favorites. + * + * @param integer $fileId file id + * @return array list($tags, $favorite) with $tags as tag array + * and $favorite is a boolean whether the file was favorited + */ + private function getTagsAndFav($fileId) { + $isFav = false; + $tags = $this->getTags($fileId); + if ($tags) { + $favPos = array_search(self::TAG_FAVORITE, $tags); + if ($favPos !== false) { + $isFav = true; + unset($tags[$favPos]); + } + } + return array($tags, $isFav); + } + + /** + * Returns tags for the given file id + * + * @param integer $fileId file id + * @return array list of tags for that file + */ + private function getTags($fileId) { + if (isset($this->cachedTags[$fileId])) { + return $this->cachedTags[$fileId]; + } else { + $tags = $this->getTagger()->getTagsForObjects(array($fileId)); + if ($tags !== false) { + if (empty($tags)) { + return array(); + } + return current($tags); + } + } + return null; + } + + /** + * Updates the tags of the given file id + * + * @param int $fileId + * @param array $tags array of tag strings + */ + private function updateTags($fileId, $tags) { + $tagger = $this->getTagger(); + $currentTags = $this->getTags($fileId); + + $newTags = array_diff($tags, $currentTags); + foreach ($newTags as $tag) { + if ($tag === self::TAG_FAVORITE) { + continue; + } + $tagger->tagAs($fileId, $tag); + } + $deletedTags = array_diff($currentTags, $tags); + foreach ($deletedTags as $tag) { + if ($tag === self::TAG_FAVORITE) { + continue; + } + $tagger->unTag($fileId, $tag); + } + } + + /** + * Adds tags and favorites properties to the response, + * if requested. + * + * @param PropFind $propFind + * @param \Sabre\DAV\INode $node + * @return void + */ + public function handleGetProperties( + PropFind $propFind, + \Sabre\DAV\INode $node + ) { + if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) { + return; + } + + // need prefetch ? + if ($node instanceof \OCA\DAV\Connector\Sabre\Directory + && $propFind->getDepth() !== 0 + && (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME)) + || !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME)) + )) { + // note: pre-fetching only supported for depth <= 1 + $folderContent = $node->getChildren(); + $fileIds[] = (int)$node->getId(); + foreach ($folderContent as $info) { + $fileIds[] = (int)$info->getId(); + } + $tags = $this->getTagger()->getTagsForObjects($fileIds); + if ($tags === false) { + // the tags API returns false on error... + $tags = array(); + } + + $this->cachedTags = $this->cachedTags + $tags; + $emptyFileIds = array_diff($fileIds, array_keys($tags)); + // also cache the ones that were not found + foreach ($emptyFileIds as $fileId) { + $this->cachedTags[$fileId] = []; + } + } + + $tags = null; + $isFav = null; + + $propFind->handle(self::TAGS_PROPERTYNAME, function() use ($tags, &$isFav, $node) { + list($tags, $isFav) = $this->getTagsAndFav($node->getId()); + return new TagList($tags); + }); + + $propFind->handle(self::FAVORITE_PROPERTYNAME, function() use ($isFav, $node) { + if (is_null($isFav)) { + list(, $isFav) = $this->getTagsAndFav($node->getId()); + } + return $isFav; + }); + } + + /** + * Updates tags and favorites properties, if applicable. + * + * @param string $path + * @param PropPatch $propPatch + * + * @return void + */ + public function handleUpdateProperties($path, PropPatch $propPatch) { + $propPatch->handle(self::TAGS_PROPERTYNAME, function($tagList) use ($path) { + $node = $this->tree->getNodeForPath($path); + if (is_null($node)) { + return 404; + } + $this->updateTags($node->getId(), $tagList->getTags()); + return true; + }); + + $propPatch->handle(self::FAVORITE_PROPERTYNAME, function($favState) use ($path) { + $node = $this->tree->getNodeForPath($path); + if (is_null($node)) { + return 404; + } + if ((int)$favState === 1 || $favState === 'true') { + $this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE); + } else { + $this->getTagger()->unTag($node->getId(), self::TAG_FAVORITE); + } + + if (is_null($favState)) { + // confirm deletion + return 204; + } + + return 200; + }); + } +} diff --git a/apps/dav/lib/files/custompropertiesbackend.php b/apps/dav/lib/files/custompropertiesbackend.php new file mode 100644 index 00000000000..83776997a52 --- /dev/null +++ b/apps/dav/lib/files/custompropertiesbackend.php @@ -0,0 +1,271 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Files; + +use OCP\IDBConnection; +use OCP\IUser; +use Sabre\DAV\PropertyStorage\Backend\BackendInterface; +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; +use Sabre\DAV\Tree; + +class CustomPropertiesBackend implements BackendInterface { + + /** + * Ignored properties + * + * @var array + */ + private $ignoredProperties = array( + '{DAV:}getcontentlength', + '{DAV:}getcontenttype', + '{DAV:}getetag', + '{DAV:}quota-used-bytes', + '{DAV:}quota-available-bytes', + '{DAV:}quota-available-bytes', + '{http://owncloud.org/ns}permissions', + '{http://owncloud.org/ns}downloadURL', + '{http://owncloud.org/ns}dDC', + '{http://owncloud.org/ns}size', + ); + + /** + * @var Tree + */ + private $tree; + + /** + * @var IDBConnection + */ + private $connection; + + /** + * @var IUser + */ + private $user; + + /** + * Properties cache + * + * @var array + */ + private $cache = []; + + /** + * @param Tree $tree node tree + * @param IDBConnection $connection database connection + * @param IUser $user owner of the tree and properties + */ + public function __construct( + Tree $tree, + IDBConnection $connection, + IUser $user) { + $this->tree = $tree; + $this->connection = $connection; + $this->user = $user->getUID(); + } + + /** + * Fetches properties for a path. + * + * @param string $path + * @param PropFind $propFind + * @return void + */ + public function propFind($path, PropFind $propFind) { + + $requestedProps = $propFind->get404Properties(); + + // these might appear + $requestedProps = array_diff( + $requestedProps, + $this->ignoredProperties + ); + + if (empty($requestedProps)) { + return; + } + + $props = $this->getProperties($path, $requestedProps); + foreach ($props as $propName => $propValue) { + $propFind->set($propName, $propValue); + } + } + + /** + * Updates properties for a path + * + * @param string $path + * @param PropPatch $propPatch + * + * @return void + */ + public function propPatch($path, PropPatch $propPatch) { + $propPatch->handleRemaining(function($changedProps) use ($path) { + return $this->updateProperties($path, $changedProps); + }); + } + + /** + * This method is called after a node is deleted. + * + * @param string $path path of node for which to delete properties + */ + public function delete($path) { + $statement = $this->connection->prepare( + 'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?' + ); + $statement->execute(array($this->user, $path)); + $statement->closeCursor(); + + unset($this->cache[$path]); + } + + /** + * This method is called after a successful MOVE + * + * @param string $source + * @param string $destination + * + * @return void + */ + public function move($source, $destination) { + $statement = $this->connection->prepare( + 'UPDATE `*PREFIX*properties` SET `propertypath` = ?' . + ' WHERE `userid` = ? AND `propertypath` = ?' + ); + $statement->execute(array($destination, $this->user, $source)); + $statement->closeCursor(); + } + + /** + * Returns a list of properties for this nodes.; + * @param string $path + * @param array $requestedProperties requested properties or empty array for "all" + * @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 + */ + private function getProperties($path, array $requestedProperties) { + if (isset($this->cache[$path])) { + return $this->cache[$path]; + } + + // TODO: chunking if more than 1000 properties + $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'; + + $whereValues = array($this->user, $path); + $whereTypes = array(null, null); + + if (!empty($requestedProperties)) { + // request only a subset + $sql .= ' AND `propertyname` in (?)'; + $whereValues[] = $requestedProperties; + $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY; + } + + $result = $this->connection->executeQuery( + $sql, + $whereValues, + $whereTypes + ); + + $props = []; + while ($row = $result->fetch()) { + $props[$row['propertyname']] = $row['propertyvalue']; + } + + $result->closeCursor(); + + $this->cache[$path] = $props; + return $props; + } + + /** + * Update properties + * + * @param string $path node for which to update properties + * @param array $properties array of properties to update + * + * @return bool + */ + private function updateProperties($path, $properties) { + + $deleteStatement = 'DELETE FROM `*PREFIX*properties`' . + ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'; + + $insertStatement = 'INSERT INTO `*PREFIX*properties`' . + ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)'; + + $updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' . + ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'; + + // TODO: use "insert or update" strategy ? + $existing = $this->getProperties($path, array()); + $this->connection->beginTransaction(); + 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)) { + $this->connection->executeUpdate($deleteStatement, + array( + $this->user, + $path, + $propertyName + ) + ); + } + } else { + if (!array_key_exists($propertyName, $existing)) { + $this->connection->executeUpdate($insertStatement, + array( + $this->user, + $path, + $propertyName, + $propertyValue + ) + ); + } else { + $this->connection->executeUpdate($updateStatement, + array( + $propertyValue, + $this->user, + $path, + $propertyName + ) + ); + } + } + } + + $this->connection->commit(); + unset($this->cache[$path]); + + return true; + } + +} diff --git a/apps/dav/lib/files/fileshome.php b/apps/dav/lib/files/fileshome.php new file mode 100644 index 00000000000..5e145a2b002 --- /dev/null +++ b/apps/dav/lib/files/fileshome.php @@ -0,0 +1,80 @@ +<?php + +namespace OCA\DAV\Files; + +use OCA\DAV\Connector\Sabre\Directory; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\ICollection; +use Sabre\DAV\SimpleCollection; +use Sabre\HTTP\URLUtil; + +class FilesHome implements ICollection { + + /** + * FilesHome constructor. + * + * @param array $principalInfo + */ + public function __construct($principalInfo) { + $this->principalInfo = $principalInfo; + } + + function createFile($name, $data = null) { + return $this->impl()->createFile($name, $data); + } + + function createDirectory($name) { + $this->impl()->createDirectory($name); + } + + function getChild($name) { + return $this->impl()->getChild($name); + } + + function getChildren() { + return $this->impl()->getChildren(); + } + + function childExists($name) { + return $this->impl()->childExists($name); + } + + function delete() { + $this->impl()->delete(); + } + + function getName() { + list(,$name) = URLUtil::splitPath($this->principalInfo['uri']); + return $name; + } + + function setName($name) { + throw new Forbidden('Permission denied to rename this folder'); + } + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int + */ + function getLastModified() { + return $this->impl()->getLastModified(); + } + + /** + * @return Directory + */ + private function impl() { + // + // TODO: we need to mount filesystem of the give user + // + $user = \OC::$server->getUserSession()->getUser(); + if ($this->getName() !== $user->getUID()) { + return new SimpleCollection($this->getName()); + } + $view = \OC\Files\Filesystem::getView(); + $rootInfo = $view->getFileInfo(''); + $impl = new Directory($view, $rootInfo); + return $impl; + } +} diff --git a/apps/dav/lib/files/rootcollection.php b/apps/dav/lib/files/rootcollection.php new file mode 100644 index 00000000000..bbe3c784a53 --- /dev/null +++ b/apps/dav/lib/files/rootcollection.php @@ -0,0 +1,28 @@ +<?php + +namespace OCA\DAV\Files; + +use Sabre\DAVACL\AbstractPrincipalCollection; +use Sabre\DAVACL\IPrincipal; + +class RootCollection extends AbstractPrincipalCollection { + + /** + * This method returns a node for a principal. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principalInfo + * @return IPrincipal + */ + function getChildForPrincipal(array $principalInfo) { + return new FilesHome($principalInfo); + } + + function getName() { + return 'files'; + } + +} diff --git a/apps/dav/lib/rootcollection.php b/apps/dav/lib/rootcollection.php new file mode 100644 index 00000000000..7de2c2aabe3 --- /dev/null +++ b/apps/dav/lib/rootcollection.php @@ -0,0 +1,34 @@ +<?php + +namespace OCA\DAV; + +use OCA\DAV\Connector\Sabre\Principal; +use Sabre\CalDAV\Principal\Collection; +use Sabre\DAV\SimpleCollection; + +class RootCollection extends SimpleCollection { + + public function __construct() { + $config = \OC::$server->getConfig(); + $principalBackend = new Principal( + $config, + \OC::$server->getUserManager() + ); + // as soon as debug mode is enabled we allow listing of principals + $disableListing = !$config->getSystemValue('debug', false); + + // setup the first level of the dav tree + $principalCollection = new Collection($principalBackend); + $principalCollection->disableListing = $disableListing; + $filesCollection = new Files\RootCollection($principalBackend); + $filesCollection->disableListing = $disableListing; + + $children = [ + $principalCollection, + $filesCollection, + ]; + + parent::__construct('root', $children); + } + +} diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php new file mode 100644 index 00000000000..055c5a5fc2c --- /dev/null +++ b/apps/dav/lib/server.php @@ -0,0 +1,57 @@ +<?php + +namespace OCA\DAV; + +use OCA\DAV\Connector\Sabre\Auth; +use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin; +use OCA\DAV\Files\CustomPropertiesBackend; +use OCP\IRequest; +use Sabre\DAV\Auth\Plugin; +use Sabre\HTTP\Util; + +class Server { + + /** @var IRequest */ + private $request; + + public function __construct(IRequest $request, $baseUri) { + $this->request = $request; + $this->baseUri = $baseUri; + $root = new RootCollection(); + $this->server = new \OCA\DAV\Connector\Sabre\Server($root); + + // Backends + $authBackend = new Auth( + \OC::$server->getSession(), + \OC::$server->getUserSession() + ); + + // Set URL explicitly due to reverse-proxy situations + $this->server->httpRequest->setUrl($this->request->getRequestUri()); + $this->server->setBaseUri($this->baseUri); + + $this->server->addPlugin(new BlockLegacyClientPlugin(\OC::$server->getConfig())); + $this->server->addPlugin(new Plugin($authBackend, 'ownCloud')); + + // wait with registering these until auth is handled and the filesystem is setup + $this->server->on('beforeMethod', function () { + // custom properties plugin must be the last one + $user = \OC::$server->getUserSession()->getUser(); + if (!is_null($user)) { + $this->server->addPlugin( + new \Sabre\DAV\PropertyStorage\Plugin( + new CustomPropertiesBackend( + $this->server->tree, + \OC::$server->getDatabaseConnection(), + \OC::$server->getUserSession()->getUser() + ) + ) + ); + } + }); + } + + public function exec() { + $this->server->exec(); + } +} diff --git a/apps/dav/tests/travis/litmus-v1.sh b/apps/dav/tests/travis/litmus-v1.sh new file mode 100644 index 00000000000..ab0690f392e --- /dev/null +++ b/apps/dav/tests/travis/litmus-v1.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +SCRIPT=`realpath $0` +SCRIPTPATH=`dirname $SCRIPT` + + +# start the server +php -S 127.0.0.1:8888 -t "$SCRIPTPATH/../../../.." & + + +# compile litmus +if [ ! -f /tmp/litmus/litmus-0.13.tar.gz ]; then + mkdir -p /tmp/litmus + wget -O /tmp/litmus/litmus-0.13.tar.gz http://www.webdav.org/neon/litmus/litmus-0.13.tar.gz + cd /tmp/litmus + tar -xzf litmus-0.13.tar.gz + cd /tmp/litmus/litmus-0.13 + ./configure + make +fi + +# run the tests +cd /tmp/litmus/litmus-0.13 +make URL=http://127.0.0.1:8888/remote.php/webdav CREDS="admin admin" TESTS="basic copymove props locks" check diff --git a/apps/dav/tests/travis/litmus-v2.sh b/apps/dav/tests/travis/litmus-v2.sh new file mode 100644 index 00000000000..892ad327d3b --- /dev/null +++ b/apps/dav/tests/travis/litmus-v2.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +SCRIPT=`realpath $0` +SCRIPTPATH=`dirname $SCRIPT` + + +# start the server +php -S 127.0.0.1:8888 -t "$SCRIPTPATH/../../../.." & + + +# compile litmus +if [ ! -f /tmp/litmus/litmus-0.13.tar.gz ]; then + mkdir -p /tmp/litmus + wget -O /tmp/litmus/litmus-0.13.tar.gz http://www.webdav.org/neon/litmus/litmus-0.13.tar.gz + cd /tmp/litmus + tar -xzf litmus-0.13.tar.gz + cd /tmp/litmus/litmus-0.13 + ./configure + make +fi + +# run the tests +cd /tmp/litmus/litmus-0.13 +make URL=http://127.0.0.1:8888/remote.php/dav/files/admin CREDS="admin admin" TESTS="basic copymove props locks" check diff --git a/apps/dav/tests/unit/connector/sabre/BlockLegacyClientPluginTest.php b/apps/dav/tests/unit/connector/sabre/BlockLegacyClientPluginTest.php new file mode 100644 index 00000000000..1e390cf15f7 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/BlockLegacyClientPluginTest.php @@ -0,0 +1,129 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace Test\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin; +use Test\TestCase; +use OCP\IConfig; + +/** + * Class BlockLegacyClientPluginTest + * + * @package Test\Connector\Sabre + */ +class BlockLegacyClientPluginTest extends TestCase { + /** @var IConfig */ + private $config; + /** @var BlockLegacyClientPlugin */ + private $blockLegacyClientVersionPlugin; + + public function setUp() { + parent::setUp(); + + $this->config = $this->getMock('\OCP\IConfig'); + $this->blockLegacyClientVersionPlugin = new BlockLegacyClientPlugin($this->config); + } + + /** + * @return array + */ + public function oldDesktopClientProvider() { + return [ + ['Mozilla/5.0 (1.5.0) mirall/1.5.0'], + ['mirall/1.5.0'], + ['mirall/1.5.4'], + ['mirall/1.6.0'], + ['Mozilla/5.0 (Bogus Text) mirall/1.6.9'], + ]; + } + + /** + * @dataProvider oldDesktopClientProvider + * @param string $userAgent + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Unsupported client version. + */ + public function testBeforeHandlerException($userAgent) { + /** @var \Sabre\HTTP\RequestInterface $request */ + $request = $this->getMock('\Sabre\HTTP\RequestInterface'); + $request + ->expects($this->once()) + ->method('getHeader') + ->with('User-Agent') + ->will($this->returnValue($userAgent)); + + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('minimum.supported.desktop.version', '1.7.0') + ->will($this->returnValue('1.7.0')); + + $this->blockLegacyClientVersionPlugin->beforeHandler($request); + } + + /** + * @return array + */ + public function newAndAlternateDesktopClientProvider() { + return [ + ['Mozilla/5.0 (1.7.0) mirall/1.7.0'], + ['mirall/1.8.3'], + ['mirall/1.7.2'], + ['mirall/1.7.0'], + ['Mozilla/5.0 (Bogus Text) mirall/1.9.3'], + ]; + } + + /** + * @dataProvider newAndAlternateDesktopClientProvider + * @param string $userAgent + */ + public function testBeforeHandlerSuccess($userAgent) { + /** @var \Sabre\HTTP\RequestInterface $request */ + $request = $this->getMock('\Sabre\HTTP\RequestInterface'); + $request + ->expects($this->once()) + ->method('getHeader') + ->with('User-Agent') + ->will($this->returnValue($userAgent)); + + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('minimum.supported.desktop.version', '1.7.0') + ->will($this->returnValue('1.7.0')); + + $this->blockLegacyClientVersionPlugin->beforeHandler($request); + } + + public function testBeforeHandlerNoUserAgent() { + /** @var \Sabre\HTTP\RequestInterface $request */ + $request = $this->getMock('\Sabre\HTTP\RequestInterface'); + $request + ->expects($this->once()) + ->method('getHeader') + ->with('User-Agent') + ->will($this->returnValue(null)); + $this->blockLegacyClientVersionPlugin->beforeHandler($request); + } + +} diff --git a/apps/dav/tests/unit/connector/sabre/DummyGetResponsePluginTest.php b/apps/dav/tests/unit/connector/sabre/DummyGetResponsePluginTest.php new file mode 100644 index 00000000000..1fd89c84ff6 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/DummyGetResponsePluginTest.php @@ -0,0 +1,69 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace Test\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\DummyGetResponsePlugin; +use Test\TestCase; + +/** + * Class DummyGetResponsePluginTest + * + * @package Test\Connector\Sabre + */ +class DummyGetResponsePluginTest extends TestCase { + /** @var DummyGetResponsePlugin */ + private $dummyGetResponsePlugin; + + public function setUp() { + parent::setUp(); + + $this->dummyGetResponsePlugin = new DummyGetResponsePlugin(); + } + + public function testInitialize() { + /** @var \Sabre\DAV\Server $server */ + $server = $this->getMock('\Sabre\DAV\Server'); + $server + ->expects($this->once()) + ->method('on') + ->with('method:GET', [$this->dummyGetResponsePlugin, 'httpGet'], 200); + + $this->dummyGetResponsePlugin->initialize($server); + } + + + public function testHttpGet() { + /** @var \Sabre\HTTP\RequestInterface $request */ + $request = $this->getMock('\Sabre\HTTP\RequestInterface'); + /** @var \Sabre\HTTP\ResponseInterface $response */ + $response = $server = $this->getMock('\Sabre\HTTP\ResponseInterface'); + $response + ->expects($this->once()) + ->method('setBody'); + $response + ->expects($this->once()) + ->method('setStatus') + ->with(200); + + $this->assertSame(false, $this->dummyGetResponsePlugin->httpGet($request, $response)); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/MaintenancePluginTest.php b/apps/dav/tests/unit/connector/sabre/MaintenancePluginTest.php new file mode 100644 index 00000000000..c0acd4fc3de --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/MaintenancePluginTest.php @@ -0,0 +1,73 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace Test\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\MaintenancePlugin; +use Test\TestCase; +use OCP\IConfig; + +/** + * Class MaintenancePluginTest + * + * @package Test\Connector\Sabre + */ +class MaintenancePluginTest extends TestCase { + /** @var IConfig */ + private $config; + /** @var MaintenancePlugin */ + private $maintenancePlugin; + + public function setUp() { + parent::setUp(); + + $this->config = $this->getMock('\OCP\IConfig'); + $this->maintenancePlugin = new MaintenancePlugin($this->config); + } + + /** + * @expectedException \Sabre\DAV\Exception\ServiceUnavailable + * @expectedExceptionMessage System in single user mode. + */ + public function testSingleUserMode() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('singleuser', false) + ->will($this->returnValue(true)); + + $this->maintenancePlugin->checkMaintenanceMode(); + } + + /** + * @expectedException \Sabre\DAV\Exception\ServiceUnavailable + * @expectedExceptionMessage System in single user mode. + */ + public function testMaintenanceMode() { + $this->config + ->expects($this->exactly(1)) + ->method('getSystemValue') + ->will($this->onConsecutiveCalls([false, true])); + + $this->maintenancePlugin->checkMaintenanceMode(); + } + +} diff --git a/apps/dav/tests/unit/connector/sabre/auth.php b/apps/dav/tests/unit/connector/sabre/auth.php new file mode 100644 index 00000000000..0466f3aab77 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/auth.php @@ -0,0 +1,356 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace Tests\Connector\Sabre; + +use Test\TestCase; +use OCP\ISession; +use OCP\IUserSession; + +/** + * Class Auth + * + * @package OCA\DAV\Connector\Sabre + */ +class Auth extends TestCase { + /** @var ISession */ + private $session; + /** @var \OCA\DAV\Connector\Sabre\Auth */ + private $auth; + /** @var IUserSession */ + private $userSession; + + public function setUp() { + parent::setUp(); + $this->session = $this->getMockBuilder('\OCP\ISession') + ->disableOriginalConstructor()->getMock(); + $this->userSession = $this->getMockBuilder('\OCP\IUserSession') + ->disableOriginalConstructor()->getMock(); + $this->auth = new \OCA\DAV\Connector\Sabre\Auth($this->session, $this->userSession); + } + + public function testIsDavAuthenticatedWithoutDavSession() { + $this->session + ->expects($this->once()) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue(null)); + + $this->assertFalse($this->invokePrivate($this->auth, 'isDavAuthenticated', ['MyTestUser'])); + } + + public function testIsDavAuthenticatedWithWrongDavSession() { + $this->session + ->expects($this->exactly(2)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('AnotherUser')); + + $this->assertFalse($this->invokePrivate($this->auth, 'isDavAuthenticated', ['MyTestUser'])); + } + + public function testIsDavAuthenticatedWithCorrectDavSession() { + $this->session + ->expects($this->exactly(2)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('MyTestUser')); + + $this->assertTrue($this->invokePrivate($this->auth, 'isDavAuthenticated', ['MyTestUser'])); + } + + public function testValidateUserPassOfAlreadyDAVAuthenticatedUser() { + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->exactly(2)) + ->method('getUID') + ->will($this->returnValue('MyTestUser')); + $this->userSession + ->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->userSession + ->expects($this->exactly(2)) + ->method('getUser') + ->will($this->returnValue($user)); + $this->session + ->expects($this->exactly(2)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('MyTestUser')); + $this->session + ->expects($this->once()) + ->method('close'); + + $this->assertTrue($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); + } + + public function testValidateUserPassOfInvalidDAVAuthenticatedUser() { + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('MyTestUser')); + $this->userSession + ->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->userSession + ->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)); + $this->session + ->expects($this->exactly(2)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('AnotherUser')); + $this->session + ->expects($this->once()) + ->method('close'); + + $this->assertFalse($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); + } + + public function testValidateUserPassOfInvalidDAVAuthenticatedUserWithValidPassword() { + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('MyTestUser')); + $this->userSession + ->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->userSession + ->expects($this->exactly(3)) + ->method('getUser') + ->will($this->returnValue($user)); + $this->session + ->expects($this->exactly(2)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('AnotherUser')); + $this->userSession + ->expects($this->once()) + ->method('login') + ->with('MyTestUser', 'MyTestPassword') + ->will($this->returnValue(true)); + $this->session + ->expects($this->once()) + ->method('set') + ->with('AUTHENTICATED_TO_DAV_BACKEND', 'MyTestUser'); + $this->session + ->expects($this->once()) + ->method('close'); + + $this->assertTrue($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); + } + + public function testValidateUserPassWithInvalidPassword() { + $this->userSession + ->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(false)); + $this->userSession + ->expects($this->once()) + ->method('login') + ->with('MyTestUser', 'MyTestPassword') + ->will($this->returnValue(false)); + $this->session + ->expects($this->once()) + ->method('close'); + + $this->assertFalse($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); + } + + public function testGetCurrentUserWithoutBeingLoggedIn() { + $this->assertSame(null, $this->auth->getCurrentUser()); + } + + public function testGetCurrentUserWithValidDAVLogin() { + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('MyTestUser')); + $this->userSession + ->expects($this->exactly(2)) + ->method('getUser') + ->will($this->returnValue($user)); + $this->session + ->expects($this->exactly(2)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('MyTestUser')); + + $this->assertSame('MyTestUser', $this->auth->getCurrentUser()); + } + + public function testGetCurrentUserWithoutAnyDAVLogin() { + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('MyTestUser')); + $this->userSession + ->expects($this->exactly(2)) + ->method('getUser') + ->will($this->returnValue($user)); + $this->session + ->expects($this->exactly(2)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue(null)); + + $this->assertSame('MyTestUser', $this->auth->getCurrentUser()); + } + + public function testGetCurrentUserWithWrongDAVUser() { + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('MyWrongDavUser')); + $this->userSession + ->expects($this->exactly(2)) + ->method('getUser') + ->will($this->returnValue($user)); + $this->session + ->expects($this->exactly(3)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('AnotherUser')); + + $this->assertSame(null, $this->auth->getCurrentUser()); + } + + public function testAuthenticateAlreadyLoggedIn() { + $server = $this->getMockBuilder('\Sabre\DAV\Server') + ->disableOriginalConstructor() + ->getMock(); + $this->userSession + ->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->session + ->expects($this->once()) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue(null)); + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('MyWrongDavUser')); + $this->userSession + ->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)); + $this->session + ->expects($this->once()) + ->method('close'); + + $this->assertTrue($this->auth->authenticate($server, 'TestRealm')); + } + + /** + * @expectedException \Sabre\DAV\Exception\NotAuthenticated + * @expectedExceptionMessage No basic authentication headers were found + */ + public function testAuthenticateNoBasicAuthenticateHeadersProvided() { + $server = $this->getMockBuilder('\Sabre\DAV\Server') + ->disableOriginalConstructor() + ->getMock(); + $server->httpRequest = $this->getMockBuilder('\Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $server->httpResponse = $this->getMockBuilder('\Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->auth->authenticate($server, 'TestRealm'); + } + + public function testAuthenticateValidCredentials() { + $server = $this->getMockBuilder('\Sabre\DAV\Server') + ->disableOriginalConstructor() + ->getMock(); + $server->httpRequest = $this->getMockBuilder('\Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $server->httpRequest + ->expects($this->once()) + ->method('getHeader') + ->with('Authorization') + ->will($this->returnValue('basic dXNlcm5hbWU6cGFzc3dvcmQ=')); + $server->httpResponse = $this->getMockBuilder('\Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->userSession + ->expects($this->once()) + ->method('login') + ->with('username', 'password') + ->will($this->returnValue(true)); + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->exactly(2)) + ->method('getUID') + ->will($this->returnValue('MyTestUser')); + $this->userSession + ->expects($this->exactly(2)) + ->method('getUser') + ->will($this->returnValue($user)); + $this->assertTrue($this->auth->authenticate($server, 'TestRealm')); + } + + /** + * @expectedException \Sabre\DAV\Exception\NotAuthenticated + * @expectedExceptionMessage Username or password does not match + */ + public function testAuthenticateInvalidCredentials() { + $server = $this->getMockBuilder('\Sabre\DAV\Server') + ->disableOriginalConstructor() + ->getMock(); + $server->httpRequest = $this->getMockBuilder('\Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $server->httpRequest + ->expects($this->once()) + ->method('getHeader') + ->with('Authorization') + ->will($this->returnValue('basic dXNlcm5hbWU6cGFzc3dvcmQ=')); + $server->httpResponse = $this->getMockBuilder('\Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->userSession + ->expects($this->once()) + ->method('login') + ->with('username', 'password') + ->will($this->returnValue(false)); + $this->auth->authenticate($server, 'TestRealm'); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/copyetagheaderplugintest.php b/apps/dav/tests/unit/connector/sabre/copyetagheaderplugintest.php new file mode 100644 index 00000000000..2080755cd51 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/copyetagheaderplugintest.php @@ -0,0 +1,43 @@ +<?php + +namespace Tests\Connector\Sabre; + +/** + * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class CopyEtagPluginTest extends \Test\TestCase { + + /** + * @var \OCA\DAV\Connector\Sabre\CopyEtagHeaderPlugin + */ + private $plugin; + + public function setUp() { + parent::setUp(); + $this->server = new \Sabre\DAV\Server(); + $this->plugin = new \OCA\DAV\Connector\Sabre\CopyEtagHeaderPlugin(); + $this->plugin->initialize($this->server); + } + + public function testCopyEtag() { + $request = new \Sabre\Http\Request(); + $response = new \Sabre\Http\Response(); + $response->setHeader('Etag', 'abcd'); + + $this->plugin->afterMethod($request, $response); + + $this->assertEquals('abcd', $response->getHeader('OC-Etag')); + } + + public function testNoopWhenEmpty() { + $request = new \Sabre\Http\Request(); + $response = new \Sabre\Http\Response(); + + $this->plugin->afterMethod($request, $response); + + $this->assertNull($response->getHeader('OC-Etag')); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/custompropertiesbackend.php b/apps/dav/tests/unit/connector/sabre/custompropertiesbackend.php new file mode 100644 index 00000000000..973a5d4c27b --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/custompropertiesbackend.php @@ -0,0 +1,286 @@ +<?php + +namespace Tests\Connector\Sabre; + +/** + * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class CustomPropertiesBackend extends \Test\TestCase { + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\ObjectTree + */ + private $tree; + + /** + * @var \OCA\DAV\Connector\Sabre\CustomPropertiesBackend + */ + private $plugin; + + /** + * @var \OCP\IUser + */ + private $user; + + public function setUp() { + parent::setUp(); + $this->server = new \Sabre\DAV\Server(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + + $userId = $this->getUniqueID('testcustompropertiesuser'); + + $this->user = $this->getMock('\OCP\IUser'); + $this->user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue($userId)); + + $this->plugin = new \OCA\DAV\Connector\Sabre\CustomPropertiesBackend( + $this->tree, + \OC::$server->getDatabaseConnection(), + $this->user + ); + } + + public function tearDown() { + $connection = \OC::$server->getDatabaseConnection(); + $deleteStatement = $connection->prepare( + 'DELETE FROM `*PREFIX*properties`' . + ' WHERE `userid` = ?' + ); + $deleteStatement->execute( + array( + $this->user->getUID(), + ) + ); + $deleteStatement->closeCursor(); + } + + private function createTestNode($class) { + $node = $this->getMockBuilder($class) + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $node->expects($this->any()) + ->method('getPath') + ->will($this->returnValue('/dummypath')); + + return $node; + } + + private function applyDefaultProps($path = '/dummypath') { + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + 'customprop' => 'value1', + 'customprop2' => 'value2', + )); + + $this->plugin->propPatch( + $path, + $propPatch + ); + + $propPatch->commit(); + + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(200, $result['customprop']); + $this->assertEquals(200, $result['customprop2']); + } + + /** + * Test that propFind on a missing file soft fails + */ + public function testPropFindMissingFileSoftFail() { + $this->tree->expects($this->at(0)) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->throwException(new \Sabre\DAV\Exception\NotFound())); + + $this->tree->expects($this->at(1)) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->throwException(new \Sabre\DAV\Exception\ServiceUnavailable())); + + $propFind = new \Sabre\DAV\PropFind( + '/dummypath', + array( + 'customprop', + 'customprop2', + 'unsetprop', + ), + 0 + ); + + $this->plugin->propFind( + '/dummypath', + $propFind + ); + + $this->plugin->propFind( + '/dummypath', + $propFind + ); + + // no exception, soft fail + $this->assertTrue(true); + } + + /** + * Test setting/getting properties + */ + public function testSetGetPropertiesForFile() { + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $this->applyDefaultProps(); + + $propFind = new \Sabre\DAV\PropFind( + '/dummypath', + array( + 'customprop', + 'customprop2', + 'unsetprop', + ), + 0 + ); + + $this->plugin->propFind( + '/dummypath', + $propFind + ); + + $this->assertEquals('value1', $propFind->get('customprop')); + $this->assertEquals('value2', $propFind->get('customprop2')); + $this->assertEquals(array('unsetprop'), $propFind->get404Properties()); + } + + /** + * Test getting properties from directory + */ + public function testGetPropertiesForDirectory() { + $rootNode = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory'); + + $nodeSub = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $nodeSub->expects($this->any()) + ->method('getId') + ->will($this->returnValue(456)); + + $nodeSub->expects($this->any()) + ->method('getPath') + ->will($this->returnValue('/dummypath/test.txt')); + + $rootNode->expects($this->once()) + ->method('getChildren') + ->will($this->returnValue(array($nodeSub))); + + $this->tree->expects($this->at(0)) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($rootNode)); + + $this->tree->expects($this->at(1)) + ->method('getNodeForPath') + ->with('/dummypath/test.txt') + ->will($this->returnValue($nodeSub)); + + $this->tree->expects($this->at(2)) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($rootNode)); + + $this->tree->expects($this->at(3)) + ->method('getNodeForPath') + ->with('/dummypath/test.txt') + ->will($this->returnValue($nodeSub)); + + $this->applyDefaultProps('/dummypath'); + $this->applyDefaultProps('/dummypath/test.txt'); + + $propNames = array( + 'customprop', + 'customprop2', + 'unsetprop', + ); + + $propFindRoot = new \Sabre\DAV\PropFind( + '/dummypath', + $propNames, + 1 + ); + + $propFindSub = new \Sabre\DAV\PropFind( + '/dummypath/test.txt', + $propNames, + 0 + ); + + $this->plugin->propFind( + '/dummypath', + $propFindRoot + ); + + $this->plugin->propFind( + '/dummypath/test.txt', + $propFindSub + ); + + // TODO: find a way to assert that no additional SQL queries were + // run while doing the second propFind + + $this->assertEquals('value1', $propFindRoot->get('customprop')); + $this->assertEquals('value2', $propFindRoot->get('customprop2')); + $this->assertEquals(array('unsetprop'), $propFindRoot->get404Properties()); + + $this->assertEquals('value1', $propFindSub->get('customprop')); + $this->assertEquals('value2', $propFindSub->get('customprop2')); + $this->assertEquals(array('unsetprop'), $propFindSub->get404Properties()); + } + + /** + * Test delete property + */ + public function testDeleteProperty() { + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $this->applyDefaultProps(); + + $propPatch = new \Sabre\DAV\PropPatch(array( + 'customprop' => null, + )); + + $this->plugin->propPatch( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(204, $result['customprop']); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/directory.php b/apps/dav/tests/unit/connector/sabre/directory.php new file mode 100644 index 00000000000..d85290df80a --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/directory.php @@ -0,0 +1,192 @@ +<?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. + */ +class Test_OC_Connector_Sabre_Directory extends \Test\TestCase { + + /** @var OC\Files\View | PHPUnit_Framework_MockObject_MockObject */ + private $view; + /** @var OC\Files\FileInfo | PHPUnit_Framework_MockObject_MockObject */ + private $info; + + protected function setUp() { + parent::setUp(); + + $this->view = $this->getMock('OC\Files\View', array(), array(), '', false); + $this->info = $this->getMock('OC\Files\FileInfo', array(), array(), '', false); + } + + private function getDir($path = '/') { + $this->view->expects($this->once()) + ->method('getRelativePath') + ->will($this->returnValue($path)); + + $this->info->expects($this->once()) + ->method('getPath') + ->will($this->returnValue($path)); + + return new \OCA\DAV\Connector\Sabre\Directory($this->view, $this->info); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testDeleteRootFolderFails() { + $this->info->expects($this->any()) + ->method('isDeletable') + ->will($this->returnValue(true)); + $this->view->expects($this->never()) + ->method('rmdir'); + $dir = $this->getDir(); + $dir->delete(); + } + + /** + * + */ + public function testDeleteFolderWhenAllowed() { + // deletion allowed + $this->info->expects($this->once()) + ->method('isDeletable') + ->will($this->returnValue(true)); + + // but fails + $this->view->expects($this->once()) + ->method('rmdir') + ->with('sub') + ->will($this->returnValue(true)); + + $dir = $this->getDir('sub'); + $dir->delete(); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testDeleteFolderFailsWhenNotAllowed() { + $this->info->expects($this->once()) + ->method('isDeletable') + ->will($this->returnValue(false)); + + $dir = $this->getDir('sub'); + $dir->delete(); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testDeleteFolderThrowsWhenDeletionFailed() { + // deletion allowed + $this->info->expects($this->once()) + ->method('isDeletable') + ->will($this->returnValue(true)); + + // but fails + $this->view->expects($this->once()) + ->method('rmdir') + ->with('sub') + ->will($this->returnValue(false)); + + $dir = $this->getDir('sub'); + $dir->delete(); + } + + public function testGetChildren() { + $info1 = $this->getMockBuilder('OC\Files\FileInfo') + ->disableOriginalConstructor() + ->getMock(); + $info2 = $this->getMockBuilder('OC\Files\FileInfo') + ->disableOriginalConstructor() + ->getMock(); + $info1->expects($this->any()) + ->method('getName') + ->will($this->returnValue('first')); + $info1->expects($this->any()) + ->method('getEtag') + ->will($this->returnValue('abc')); + $info2->expects($this->any()) + ->method('getName') + ->will($this->returnValue('second')); + $info2->expects($this->any()) + ->method('getEtag') + ->will($this->returnValue('def')); + + $this->view->expects($this->once()) + ->method('getDirectoryContent') + ->with('') + ->will($this->returnValue(array($info1, $info2))); + + $this->view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnValue('')); + + $dir = new \OCA\DAV\Connector\Sabre\Directory($this->view, $this->info); + $nodes = $dir->getChildren(); + + $this->assertEquals(2, count($nodes)); + + // calling a second time just returns the cached values, + // does not call getDirectoryContents again + $dir->getChildren(); + } + + /** + * @expectedException \Sabre\DAV\Exception\ServiceUnavailable + */ + public function testGetChildThrowStorageNotAvailableException() { + $this->view->expects($this->once()) + ->method('getFileInfo') + ->willThrowException(new \OCP\Files\StorageNotAvailableException()); + + $dir = new \OCA\DAV\Connector\Sabre\Directory($this->view, $this->info); + $dir->getChild('.'); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\InvalidPath + */ + public function testGetChildThrowInvalidPath() { + $this->view->expects($this->once()) + ->method('verifyPath') + ->willThrowException(new \OCP\Files\InvalidPathException()); + $this->view->expects($this->never()) + ->method('getFileInfo'); + + $dir = new \OCA\DAV\Connector\Sabre\Directory($this->view, $this->info); + $dir->getChild('.'); + } + + public function testGetQuotaInfo() { + $storage = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Quota') + ->disableOriginalConstructor() + ->getMock(); + + $storage->expects($this->once()) + ->method('instanceOfStorage') + ->with('\OC\Files\Storage\Wrapper\Quota') + ->will($this->returnValue(true)); + + $storage->expects($this->once()) + ->method('getQuota') + ->will($this->returnValue(1000)); + + $storage->expects($this->once()) + ->method('free_space') + ->will($this->returnValue(800)); + + $this->info->expects($this->once()) + ->method('getSize') + ->will($this->returnValue(200)); + + $this->info->expects($this->once()) + ->method('getStorage') + ->will($this->returnValue($storage)); + + $dir = new \OCA\DAV\Connector\Sabre\Directory($this->view, $this->info); + $this->assertEquals([200, 800], $dir->getQuotaInfo()); //200 used, 800 free + } +} diff --git a/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php b/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php new file mode 100644 index 00000000000..4c0af58ffea --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php @@ -0,0 +1,44 @@ +<?php + +namespace Test\Connector\Sabre\Exception; + +use OCA\DAV\Connector\Sabre\Exception\InvalidPath; + +/** + * Copyright (c) 2015 Thomas Müller <deepdiver@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class InvalidPathTest extends \Test\TestCase { + + public function testSerialization() { + + // create xml doc + $DOM = new \DOMDocument('1.0','utf-8'); + $DOM->formatOutput = true; + $error = $DOM->createElementNS('DAV:','d:error'); + $error->setAttribute('xmlns:s', \Sabre\DAV\Server::NS_SABREDAV); + $DOM->appendChild($error); + + // serialize the exception + $message = "1234567890"; + $retry = false; + $expectedXml = <<<EOD +<?xml version="1.0" encoding="utf-8"?> +<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:o="http://owncloud.org/ns"> + <o:retry xmlns:o="o:">false</o:retry> + <o:reason xmlns:o="o:">1234567890</o:reason> +</d:error> + +EOD; + + $ex = new InvalidPath($message, $retry); + $server = $this->getMock('Sabre\DAV\Server'); + $ex->serialize($server, $error); + + // assert + $xml = $DOM->saveXML(); + $this->assertEquals($expectedXml, $xml); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/exceptionloggerplugin.php b/apps/dav/tests/unit/connector/sabre/exceptionloggerplugin.php new file mode 100644 index 00000000000..d85aa5a9cc3 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/exceptionloggerplugin.php @@ -0,0 +1,71 @@ +<?php + +/** + * Copyright (c) 2015 Thomas Müller <deepdiver@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\Exception\InvalidPath; +use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin as PluginToTest; +use OC\Log; +use OCP\ILogger; +use PHPUnit_Framework_MockObject_MockObject; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Server; +use Test\TestCase; + +class TestLogger extends Log { + public $message; + public $level; + + public function __construct($logger = null) { + //disable original constructor + } + + public function log($level, $message, array $context = array()) { + $this->level = $level; + $this->message = $message; + } +} + +class ExceptionLoggerPlugin extends TestCase { + + /** @var Server */ + private $server; + + /** @var PluginToTest */ + private $plugin; + + /** @var TestLogger | PHPUnit_Framework_MockObject_MockObject */ + private $logger; + + private function init() { + $this->server = new Server(); + $this->logger = new TestLogger(); + $this->plugin = new PluginToTest('unit-test', $this->logger); + $this->plugin->initialize($this->server); + } + + /** + * @dataProvider providesExceptions + */ + public function testLogging($expectedLogLevel, $expectedMessage, $exception) { + $this->init(); + $this->plugin->logException($exception); + + $this->assertEquals($expectedLogLevel, $this->logger->level); + $this->assertStringStartsWith('Exception: {"Message":"' . $expectedMessage, $this->logger->message); + } + + public function providesExceptions() { + return [ + [0, 'HTTP\/1.1 404 Not Found', new NotFound()], + [4, 'HTTP\/1.1 400 This path leads to nowhere', new InvalidPath('This path leads to nowhere')] + ]; + } + +} diff --git a/apps/dav/tests/unit/connector/sabre/file.php b/apps/dav/tests/unit/connector/sabre/file.php new file mode 100644 index 00000000000..d874b7f33c2 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/file.php @@ -0,0 +1,838 @@ +<?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 Test\Connector\Sabre; + +use OC\Files\Storage\Local; +use Test\HookHelper; +use OC\Files\Filesystem; +use OCP\Lock\ILockingProvider; + +class File extends \Test\TestCase { + + /** + * @var string + */ + private $user; + + public function setUp() { + parent::setUp(); + + \OC_Hook::clear(); + + $this->user = $this->getUniqueID('user_'); + $userManager = \OC::$server->getUserManager(); + $userManager->createUser($this->user, 'pass'); + + $this->loginAsUser($this->user); + } + + public function tearDown() { + $userManager = \OC::$server->getUserManager(); + $userManager->get($this->user)->delete(); + unset($_SERVER['HTTP_OC_CHUNKED']); + + parent::tearDown(); + } + + private function getStream($string) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $string); + fseek($stream, 0); + return $stream; + } + + + public function fopenFailuresProvider() { + return [ + [ + // return false + null, + '\Sabre\Dav\Exception', + false + ], + [ + new \OCP\Files\NotPermittedException(), + 'Sabre\DAV\Exception\Forbidden' + ], + [ + new \OCP\Files\EntityTooLargeException(), + 'OCA\DAV\Connector\Sabre\Exception\EntityTooLarge' + ], + [ + new \OCP\Files\InvalidContentException(), + 'OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType' + ], + [ + new \OCP\Files\InvalidPathException(), + 'Sabre\DAV\Exception\Forbidden' + ], + [ + new \OCP\Files\LockNotAcquiredException('/test.txt', 1), + 'OCA\DAV\Connector\Sabre\Exception\FileLocked' + ], + [ + new \OCP\Lock\LockedException('/test.txt'), + 'OCA\DAV\Connector\Sabre\Exception\FileLocked' + ], + [ + new \OCP\Encryption\Exceptions\GenericEncryptionException(), + 'Sabre\DAV\Exception\ServiceUnavailable' + ], + [ + new \OCP\Files\StorageNotAvailableException(), + 'Sabre\DAV\Exception\ServiceUnavailable' + ], + [ + new \Sabre\DAV\Exception('Generic sabre exception'), + 'Sabre\DAV\Exception', + false + ], + [ + new \Exception('Generic exception'), + 'Sabre\DAV\Exception' + ], + ]; + } + + /** + * @dataProvider fopenFailuresProvider + */ + public function testSimplePutFails($thrownException, $expectedException, $checkPreviousClass = true) { + // setup + $storage = $this->getMock( + '\OC\Files\Storage\Local', + ['fopen'], + [['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]] + ); + \OC\Files\Filesystem::mount($storage, [], $this->user . '/'); + $view = $this->getMock('\OC\Files\View', array('getRelativePath', 'resolvePath'), array()); + $view->expects($this->atLeastOnce()) + ->method('resolvePath') + ->will($this->returnCallback( + function ($path) use ($storage) { + return [$storage, $path]; + } + )); + + if ($thrownException !== null) { + $storage->expects($this->once()) + ->method('fopen') + ->will($this->throwException($thrownException)); + } else { + $storage->expects($this->once()) + ->method('fopen') + ->will($this->returnValue(false)); + } + + $view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnArgument(0)); + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $caughtException = null; + try { + $file->put('test data'); + } catch (\Exception $e) { + $caughtException = $e; + } + + $this->assertInstanceOf($expectedException, $caughtException); + if ($checkPreviousClass) { + $this->assertInstanceOf(get_class($thrownException), $caughtException->getPrevious()); + } + + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * Test putting a file using chunking + * + * @dataProvider fopenFailuresProvider + */ + public function testChunkedPutFails($thrownException, $expectedException, $checkPreviousClass = false) { + // setup + $storage = $this->getMock( + '\OC\Files\Storage\Local', + ['fopen'], + [['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]] + ); + \OC\Files\Filesystem::mount($storage, [], $this->user . '/'); + $view = $this->getMock('\OC\Files\View', ['getRelativePath', 'resolvePath'], []); + $view->expects($this->atLeastOnce()) + ->method('resolvePath') + ->will($this->returnCallback( + function ($path) use ($storage) { + return [$storage, $path]; + } + )); + + if ($thrownException !== null) { + $storage->expects($this->once()) + ->method('fopen') + ->will($this->throwException($thrownException)); + } else { + $storage->expects($this->once()) + ->method('fopen') + ->will($this->returnValue(false)); + } + + $view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnArgument(0)); + + $_SERVER['HTTP_OC_CHUNKED'] = true; + + $info = new \OC\Files\FileInfo('/test.txt-chunking-12345-2-0', null, null, [ + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // put first chunk + $file->acquireLock(ILockingProvider::LOCK_SHARED); + $this->assertNull($file->put('test data one')); + $file->releaseLock(ILockingProvider::LOCK_SHARED); + + $info = new \OC\Files\FileInfo('/test.txt-chunking-12345-2-1', null, null, [ + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $caughtException = null; + try { + // last chunk + $file->acquireLock(ILockingProvider::LOCK_SHARED); + $file->put('test data two'); + $file->releaseLock(ILockingProvider::LOCK_SHARED); + } catch (\Exception $e) { + $caughtException = $e; + } + + $this->assertInstanceOf($expectedException, $caughtException); + if ($checkPreviousClass) { + $this->assertInstanceOf(get_class($thrownException), $caughtException->getPrevious()); + } + + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * Simulate putting a file to the given path. + * + * @param string $path path to put the file into + * @param string $viewRoot root to use for the view + * + * @return result of the PUT operaiton which is usually the etag + */ + private function doPut($path, $viewRoot = null) { + $view = \OC\Files\Filesystem::getView(); + if (!is_null($viewRoot)) { + $view = new \OC\Files\View($viewRoot); + } else { + $viewRoot = '/' . $this->user . '/files'; + } + + $info = new \OC\Files\FileInfo( + $viewRoot . '/' . ltrim($path, '/'), + null, + null, + ['permissions' => \OCP\Constants::PERMISSION_ALL], + null + ); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // beforeMethod locks + $view->lockFile($path, ILockingProvider::LOCK_SHARED); + + $result = $file->put($this->getStream('test data')); + + // afterMethod unlocks + $view->unlockFile($path, ILockingProvider::LOCK_SHARED); + + return $result; + } + + /** + * Test putting a single file + */ + public function testPutSingleFile() { + $this->assertNotEmpty($this->doPut('/foo.txt')); + } + + /** + * Test putting a file using chunking + */ + public function testChunkedPut() { + $_SERVER['HTTP_OC_CHUNKED'] = true; + $this->assertNull($this->doPut('/test.txt-chunking-12345-2-0')); + $this->assertNotEmpty($this->doPut('/test.txt-chunking-12345-2-1')); + } + + /** + * Test that putting a file triggers create hooks + */ + public function testPutSingleFileTriggersHooks() { + HookHelper::setUpHooks(); + + $this->assertNotEmpty($this->doPut('/foo.txt')); + + $this->assertCount(4, HookHelper::$hookCalls); + $this->assertHookCall( + HookHelper::$hookCalls[0], + Filesystem::signal_create, + '/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[1], + Filesystem::signal_write, + '/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[2], + Filesystem::signal_post_create, + '/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[3], + Filesystem::signal_post_write, + '/foo.txt' + ); + } + + /** + * Test that putting a file triggers update hooks + */ + public function testPutOverwriteFileTriggersHooks() { + $view = \OC\Files\Filesystem::getView(); + $view->file_put_contents('/foo.txt', 'some content that will be replaced'); + + HookHelper::setUpHooks(); + + $this->assertNotEmpty($this->doPut('/foo.txt')); + + $this->assertCount(4, HookHelper::$hookCalls); + $this->assertHookCall( + HookHelper::$hookCalls[0], + Filesystem::signal_update, + '/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[1], + Filesystem::signal_write, + '/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[2], + Filesystem::signal_post_update, + '/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[3], + Filesystem::signal_post_write, + '/foo.txt' + ); + } + + /** + * Test that putting a file triggers hooks with the correct path + * if the passed view was chrooted (can happen with public webdav + * where the root is the share root) + */ + public function testPutSingleFileTriggersHooksDifferentRoot() { + $view = \OC\Files\Filesystem::getView(); + $view->mkdir('noderoot'); + + HookHelper::setUpHooks(); + + // happens with public webdav where the view root is the share root + $this->assertNotEmpty($this->doPut('/foo.txt', '/' . $this->user . '/files/noderoot')); + + $this->assertCount(4, HookHelper::$hookCalls); + $this->assertHookCall( + HookHelper::$hookCalls[0], + Filesystem::signal_create, + '/noderoot/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[1], + Filesystem::signal_write, + '/noderoot/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[2], + Filesystem::signal_post_create, + '/noderoot/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[3], + Filesystem::signal_post_write, + '/noderoot/foo.txt' + ); + } + + public static function cancellingHook($params) { + self::$hookCalls[] = array( + 'signal' => Filesystem::signal_post_create, + 'params' => $params + ); + } + + /** + * Test put file with cancelled hook + */ + public function testPutSingleFileCancelPreHook() { + \OCP\Util::connectHook( + Filesystem::CLASSNAME, + Filesystem::signal_create, + '\Test\HookHelper', + 'cancellingCallback' + ); + + // action + $thrown = false; + try { + $this->doPut('/foo.txt'); + } catch (\Sabre\DAV\Exception $e) { + $thrown = true; + } + + $this->assertTrue($thrown); + $this->assertEmpty($this->listPartFiles(), 'No stray part files'); + } + + /** + * Test exception when the uploaded size did not match + */ + public function testSimplePutFailsSizeCheck() { + // setup + $view = $this->getMock('\OC\Files\View', + array('rename', 'getRelativePath', 'filesize')); + $view->expects($this->any()) + ->method('rename') + ->withAnyParameters() + ->will($this->returnValue(false)); + $view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnArgument(0)); + + $view->expects($this->any()) + ->method('filesize') + ->will($this->returnValue(123456)); + + $_SERVER['CONTENT_LENGTH'] = 123456; + $_SERVER['REQUEST_METHOD'] = 'PUT'; + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $thrown = false; + try { + // beforeMethod locks + $file->acquireLock(ILockingProvider::LOCK_SHARED); + + $file->put($this->getStream('test data')); + + // afterMethod unlocks + $file->releaseLock(ILockingProvider::LOCK_SHARED); + } catch (\Sabre\DAV\Exception\BadRequest $e) { + $thrown = true; + } + + $this->assertTrue($thrown); + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * Test exception during final rename in simple upload mode + */ + public function testSimplePutFailsMoveFromStorage() { + $view = new \OC\Files\View('/' . $this->user . '/files'); + + // simulate situation where the target file is locked + $view->lockFile('/test.txt', ILockingProvider::LOCK_EXCLUSIVE); + + $info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $thrown = false; + try { + // beforeMethod locks + $view->lockFile($info->getPath(), ILockingProvider::LOCK_SHARED); + + $file->put($this->getStream('test data')); + + // afterMethod unlocks + $view->unlockFile($info->getPath(), ILockingProvider::LOCK_SHARED); + } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { + $thrown = true; + } + + $this->assertTrue($thrown); + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * Test exception during final rename in chunk upload mode + */ + public function testChunkedPutFailsFinalRename() { + $view = new \OC\Files\View('/' . $this->user . '/files'); + + // simulate situation where the target file is locked + $view->lockFile('/test.txt', ILockingProvider::LOCK_EXCLUSIVE); + + $_SERVER['HTTP_OC_CHUNKED'] = true; + + $info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt-chunking-12345-2-0', null, null, [ + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file->acquireLock(ILockingProvider::LOCK_SHARED); + $this->assertNull($file->put('test data one')); + $file->releaseLock(ILockingProvider::LOCK_SHARED); + + $info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt-chunking-12345-2-1', null, null, [ + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $thrown = false; + try { + $file->acquireLock(ILockingProvider::LOCK_SHARED); + $file->put($this->getStream('test data')); + $file->releaseLock(ILockingProvider::LOCK_SHARED); + } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { + $thrown = true; + } + + $this->assertTrue($thrown); + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * Test put file with invalid chars + */ + public function testSimplePutInvalidChars() { + // setup + $view = $this->getMock('\OC\Files\View', array('getRelativePath')); + $view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnArgument(0)); + + $info = new \OC\Files\FileInfo('/*', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $thrown = false; + try { + // beforeMethod locks + $view->lockFile($info->getPath(), ILockingProvider::LOCK_SHARED); + + $file->put($this->getStream('test data')); + + // afterMethod unlocks + $view->unlockFile($info->getPath(), ILockingProvider::LOCK_SHARED); + } catch (\OCA\DAV\Connector\Sabre\Exception\InvalidPath $e) { + $thrown = true; + } + + $this->assertTrue($thrown); + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * Test setting name with setName() with invalid chars + * + * @expectedException \OCA\DAV\Connector\Sabre\Exception\InvalidPath + */ + public function testSetNameInvalidChars() { + // setup + $view = $this->getMock('\OC\Files\View', array('getRelativePath')); + + $view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnArgument(0)); + + $info = new \OC\Files\FileInfo('/*', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file->setName('/super*star.txt'); + } + + /** + */ + public function testUploadAbort() { + // setup + $view = $this->getMock('\OC\Files\View', + array('rename', 'getRelativePath', 'filesize')); + $view->expects($this->any()) + ->method('rename') + ->withAnyParameters() + ->will($this->returnValue(false)); + $view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnArgument(0)); + $view->expects($this->any()) + ->method('filesize') + ->will($this->returnValue(123456)); + + $_SERVER['CONTENT_LENGTH'] = 12345; + $_SERVER['REQUEST_METHOD'] = 'PUT'; + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $thrown = false; + try { + // beforeMethod locks + $view->lockFile($info->getPath(), ILockingProvider::LOCK_SHARED); + + $file->put($this->getStream('test data')); + + // afterMethod unlocks + $view->unlockFile($info->getPath(), ILockingProvider::LOCK_SHARED); + } catch (\Sabre\DAV\Exception\BadRequest $e) { + $thrown = true; + } + + $this->assertTrue($thrown); + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * + */ + public function testDeleteWhenAllowed() { + // setup + $view = $this->getMock('\OC\Files\View', + array()); + + $view->expects($this->once()) + ->method('unlink') + ->will($this->returnValue(true)); + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $file->delete(); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testDeleteThrowsWhenDeletionNotAllowed() { + // setup + $view = $this->getMock('\OC\Files\View', + array()); + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => 0 + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $file->delete(); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testDeleteThrowsWhenDeletionFailed() { + // setup + $view = $this->getMock('\OC\Files\View', + array()); + + // but fails + $view->expects($this->once()) + ->method('unlink') + ->will($this->returnValue(false)); + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $file->delete(); + } + + /** + * Asserts hook call + * + * @param array $callData hook call data to check + * @param string $signal signal name + * @param string $hookPath hook path + */ + protected function assertHookCall($callData, $signal, $hookPath) { + $this->assertEquals($signal, $callData['signal']); + $params = $callData['params']; + $this->assertEquals( + $hookPath, + $params[Filesystem::signal_param_path] + ); + } + + /** + * Test whether locks are set before and after the operation + */ + public function testPutLocking() { + $view = new \OC\Files\View('/' . $this->user . '/files/'); + + $path = 'test-locking.txt'; + $info = new \OC\Files\FileInfo( + '/' . $this->user . '/files/' . $path, + null, + null, + ['permissions' => \OCP\Constants::PERMISSION_ALL], + null + ); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + $this->assertFalse( + $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED), + 'File unlocked before put' + ); + $this->assertFalse( + $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE), + 'File unlocked before put' + ); + + $wasLockedPre = false; + $wasLockedPost = false; + $eventHandler = $this->getMockBuilder('\stdclass') + ->setMethods(['writeCallback', 'postWriteCallback']) + ->getMock(); + + // both pre and post hooks might need access to the file, + // so only shared lock is acceptable + $eventHandler->expects($this->once()) + ->method('writeCallback') + ->will($this->returnCallback( + function () use ($view, $path, &$wasLockedPre) { + $wasLockedPre = $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED); + $wasLockedPre = $wasLockedPre && !$this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE); + } + )); + $eventHandler->expects($this->once()) + ->method('postWriteCallback') + ->will($this->returnCallback( + function () use ($view, $path, &$wasLockedPost) { + $wasLockedPost = $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED); + $wasLockedPost = $wasLockedPost && !$this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE); + } + )); + + \OCP\Util::connectHook( + Filesystem::CLASSNAME, + Filesystem::signal_write, + $eventHandler, + 'writeCallback' + ); + \OCP\Util::connectHook( + Filesystem::CLASSNAME, + Filesystem::signal_post_write, + $eventHandler, + 'postWriteCallback' + ); + + // beforeMethod locks + $view->lockFile($path, ILockingProvider::LOCK_SHARED); + + $this->assertNotEmpty($file->put($this->getStream('test data'))); + + // afterMethod unlocks + $view->unlockFile($path, ILockingProvider::LOCK_SHARED); + + $this->assertTrue($wasLockedPre, 'File was locked during pre-hooks'); + $this->assertTrue($wasLockedPost, 'File was locked during post-hooks'); + + $this->assertFalse( + $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED), + 'File unlocked after put' + ); + $this->assertFalse( + $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE), + 'File unlocked after put' + ); + } + + /** + * Returns part files in the given path + * + * @param \OC\Files\View view which root is the current user's "files" folder + * @param string $path path for which to list part files + * + * @return array list of part files + */ + private function listPartFiles(\OC\Files\View $userView = null, $path = '') { + if ($userView === null) { + $userView = \OC\Files\Filesystem::getView(); + } + $files = []; + list($storage, $internalPath) = $userView->resolvePath($path); + if($storage instanceof Local) { + $realPath = $storage->getSourcePath($internalPath); + $dh = opendir($realPath); + while (($file = readdir($dh)) !== false) { + if (substr($file, strlen($file) - 5, 5) === '.part') { + $files[] = $file; + } + } + closedir($dh); + } + return $files; + } + + /** + * @expectedException \Sabre\DAV\Exception\ServiceUnavailable + */ + public function testGetFopenFails() { + $view = $this->getMock('\OC\Files\View', ['fopen'], array()); + $view->expects($this->atLeastOnce()) + ->method('fopen') + ->will($this->returnValue(false)); + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + $file->get(); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/filesplugin.php b/apps/dav/tests/unit/connector/sabre/filesplugin.php new file mode 100644 index 00000000000..db3bbabefd0 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/filesplugin.php @@ -0,0 +1,267 @@ +<?php + +namespace Tests\Connector\Sabre; + +/** + * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class FilesPlugin extends \Test\TestCase { + const GETETAG_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::GETETAG_PROPERTYNAME; + const FILEID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::FILEID_PROPERTYNAME; + const SIZE_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::SIZE_PROPERTYNAME; + const PERMISSIONS_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::PERMISSIONS_PROPERTYNAME; + const LASTMODIFIED_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::LASTMODIFIED_PROPERTYNAME; + const DOWNLOADURL_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::DOWNLOADURL_PROPERTYNAME; + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\Tree + */ + private $tree; + + /** + * @var \OCA\DAV\Connector\Sabre\FilesPlugin + */ + private $plugin; + + /** + * @var \OC\Files\View + */ + private $view; + + public function setUp() { + parent::setUp(); + $this->server = $this->getMockBuilder('\Sabre\DAV\Server') + ->disableOriginalConstructor() + ->getMock(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + $this->view = $this->getMockBuilder('\OC\Files\View') + ->disableOriginalConstructor() + ->getMock(); + + $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view); + $this->plugin->initialize($this->server); + } + + private function createTestNode($class) { + $node = $this->getMockBuilder($class) + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $node->expects($this->any()) + ->method('getFileId') + ->will($this->returnValue(123)); + $node->expects($this->any()) + ->method('getEtag') + ->will($this->returnValue('"abc"')); + $node->expects($this->any()) + ->method('getDavPermissions') + ->will($this->returnValue('DWCKMSR')); + + return $node; + } + + /** + */ + public function testGetPropertiesForFile() { + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + array( + self::GETETAG_PROPERTYNAME, + self::FILEID_PROPERTYNAME, + self::SIZE_PROPERTYNAME, + self::PERMISSIONS_PROPERTYNAME, + self::DOWNLOADURL_PROPERTYNAME, + ), + 0 + ); + + $node->expects($this->once()) + ->method('getDirectDownload') + ->will($this->returnValue(array('url' => 'http://example.com/'))); + $node->expects($this->never()) + ->method('getSize'); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME)); + $this->assertEquals(123, $propFind->get(self::FILEID_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::SIZE_PROPERTYNAME)); + $this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); + $this->assertEquals('http://example.com/', $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); + $this->assertEquals(array(self::SIZE_PROPERTYNAME), $propFind->get404Properties()); + } + + public function testGetPublicPermissions() { + $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view, true); + $this->plugin->initialize($this->server); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + [ + self::PERMISSIONS_PROPERTYNAME, + ], + 0 + ); + + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + $node->expects($this->any()) + ->method('getDavPermissions') + ->will($this->returnValue('DWCKMSR')); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals('DWCKR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); + } + + public function testGetPropertiesForDirectory() { + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory'); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + array( + self::GETETAG_PROPERTYNAME, + self::FILEID_PROPERTYNAME, + self::SIZE_PROPERTYNAME, + self::PERMISSIONS_PROPERTYNAME, + self::DOWNLOADURL_PROPERTYNAME, + ), + 0 + ); + + $node->expects($this->never()) + ->method('getDirectDownload'); + $node->expects($this->once()) + ->method('getSize') + ->will($this->returnValue(1025)); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME)); + $this->assertEquals(123, $propFind->get(self::FILEID_PROPERTYNAME)); + $this->assertEquals(1025, $propFind->get(self::SIZE_PROPERTYNAME)); + $this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); + $this->assertEquals(array(self::DOWNLOADURL_PROPERTYNAME), $propFind->get404Properties()); + } + + public function testUpdateProps() { + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + + $testDate = 'Fri, 13 Feb 2015 00:01:02 GMT'; + + $node->expects($this->once()) + ->method('touch') + ->with($testDate); + + $node->expects($this->once()) + ->method('setEtag') + ->with('newetag') + ->will($this->returnValue(true)); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::GETETAG_PROPERTYNAME => 'newetag', + self::LASTMODIFIED_PROPERTYNAME => $testDate + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(200, $result[self::LASTMODIFIED_PROPERTYNAME]); + $this->assertEquals(200, $result[self::GETETAG_PROPERTYNAME]); + } + + /** + * Testcase from https://github.com/owncloud/core/issues/5251 + * + * |-FolderA + * |-text.txt + * |-test.txt + * + * FolderA is an incomming shared folder and there are no delete permissions. + * Thus moving /FolderA/test.txt to /test.txt should fail already on that check + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage FolderA/test.txt cannot be deleted + */ + public function testMoveSrcNotDeletable() { + $fileInfoFolderATestTXT = $this->getMockBuilder('\OCP\Files\FileInfo') + ->disableOriginalConstructor() + ->getMock(); + $fileInfoFolderATestTXT->expects($this->once()) + ->method('isDeletable') + ->willReturn(false); + + $this->view->expects($this->once()) + ->method('getFileInfo') + ->with('FolderA/test.txt') + ->willReturn($fileInfoFolderATestTXT); + + $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); + } + + public function testMoveSrcDeletable() { + $fileInfoFolderATestTXT = $this->getMockBuilder('\OCP\Files\FileInfo') + ->disableOriginalConstructor() + ->getMock(); + $fileInfoFolderATestTXT->expects($this->once()) + ->method('isDeletable') + ->willReturn(true); + + $this->view->expects($this->once()) + ->method('getFileInfo') + ->with('FolderA/test.txt') + ->willReturn($fileInfoFolderATestTXT); + + $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); + } + + /** + * @expectedException \Sabre\DAV\Exception\NotFound + * @expectedExceptionMessage FolderA/test.txt does not exist + */ + public function testMoveSrcNotExist() { + $this->view->expects($this->once()) + ->method('getFileInfo') + ->with('FolderA/test.txt') + ->willReturn(false); + + $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/node.php b/apps/dav/tests/unit/connector/sabre/node.php new file mode 100644 index 00000000000..a9610fd84b3 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/node.php @@ -0,0 +1,52 @@ +<?php + +/** + * Copyright (c) 2014 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 Test\Connector\Sabre; + +class Node extends \Test\TestCase { + public function davPermissionsProvider() { + return array( + array(\OCP\Constants::PERMISSION_ALL, 'file', false, false, 'RDNVW'), + array(\OCP\Constants::PERMISSION_ALL, 'dir', false, false, 'RDNVCK'), + array(\OCP\Constants::PERMISSION_ALL, 'file', true, false, 'SRDNVW'), + array(\OCP\Constants::PERMISSION_ALL, 'file', true, true, 'SRMDNVW'), + array(\OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_SHARE, 'file', true, false, 'SDNVW'), + array(\OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_UPDATE, 'file', false, false, 'RDNV'), + array(\OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_DELETE, 'file', false, false, 'RW'), + array(\OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, 'file', false, false, 'RDNVW'), + array(\OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, 'dir', false, false, 'RDNV'), + ); + } + + /** + * @dataProvider davPermissionsProvider + */ + public function testDavPermissions($permissions, $type, $shared, $mounted, $expected) { + $info = $this->getMockBuilder('\OC\Files\FileInfo') + ->disableOriginalConstructor() + ->setMethods(array('getPermissions', 'isShared', 'isMounted', 'getType')) + ->getMock(); + $info->expects($this->any()) + ->method('getPermissions') + ->will($this->returnValue($permissions)); + $info->expects($this->any()) + ->method('isShared') + ->will($this->returnValue($shared)); + $info->expects($this->any()) + ->method('isMounted') + ->will($this->returnValue($mounted)); + $info->expects($this->any()) + ->method('getType') + ->will($this->returnValue($type)); + $view = $this->getMock('\OC\Files\View'); + + $node = new \OCA\DAV\Connector\Sabre\File($view, $info); + $this->assertEquals($expected, $node->getDavPermissions()); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/objecttree.php b/apps/dav/tests/unit/connector/sabre/objecttree.php new file mode 100644 index 00000000000..2691385c1c1 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/objecttree.php @@ -0,0 +1,291 @@ +<?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 Test\OCA\DAV\Connector\Sabre; + + +use OC\Files\FileInfo; +use OCA\DAV\Connector\Sabre\Directory; +use OC\Files\Storage\Temporary; + +class TestDoubleFileView extends \OC\Files\View { + + public function __construct($updatables, $deletables, $canRename = true) { + $this->updatables = $updatables; + $this->deletables = $deletables; + $this->canRename = $canRename; + } + + public function isUpdatable($path) { + return $this->updatables[$path]; + } + + public function isCreatable($path) { + return $this->updatables[$path]; + } + + public function isDeletable($path) { + return $this->deletables[$path]; + } + + public function rename($path1, $path2) { + return $this->canRename; + } + + public function getRelativePath($path) { + return $path; + } +} + +class ObjectTree extends \Test\TestCase { + + /** + * @dataProvider moveFailedProvider + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testMoveFailed($source, $destination, $updatables, $deletables) { + $this->moveTest($source, $destination, $updatables, $deletables); + } + + /** + * @dataProvider moveSuccessProvider + */ + public function testMoveSuccess($source, $destination, $updatables, $deletables) { + $this->moveTest($source, $destination, $updatables, $deletables); + $this->assertTrue(true); + } + + /** + * @dataProvider moveFailedInvalidCharsProvider + * @expectedException \OCA\DAV\Connector\Sabre\Exception\InvalidPath + */ + public function testMoveFailedInvalidChars($source, $destination, $updatables, $deletables) { + $this->moveTest($source, $destination, $updatables, $deletables); + } + + function moveFailedInvalidCharsProvider() { + return array( + array('a/b', 'a/*', array('a' => true, 'a/b' => true, 'a/c*' => false), array()), + ); + } + + function moveFailedProvider() { + return array( + array('a/b', 'a/c', array('a' => false, 'a/b' => false, 'a/c' => false), array()), + array('a/b', 'b/b', array('a' => false, 'a/b' => false, 'b' => false, 'b/b' => false), array()), + array('a/b', 'b/b', array('a' => false, 'a/b' => true, 'b' => false, 'b/b' => false), array()), + array('a/b', 'b/b', array('a' => true, 'a/b' => true, 'b' => false, 'b/b' => false), array()), + array('a/b', 'b/b', array('a' => true, 'a/b' => true, 'b' => true, 'b/b' => false), array('a/b' => false)), + array('a/b', 'a/c', array('a' => false, 'a/b' => true, 'a/c' => false), array()), + ); + } + + function moveSuccessProvider() { + return array( + array('a/b', 'b/b', array('a' => true, 'a/b' => true, 'b' => true, 'b/b' => false), array('a/b' => true)), + // older files with special chars can still be renamed to valid names + array('a/b*', 'b/b', array('a' => true, 'a/b*' => true, 'b' => true, 'b/b' => false), array('a/b*' => true)), + ); + } + + /** + * @param $source + * @param $destination + * @param $updatables + */ + private function moveTest($source, $destination, $updatables, $deletables) { + $view = new TestDoubleFileView($updatables, $deletables); + + $info = new FileInfo('', null, null, array(), null); + + $rootDir = new Directory($view, $info); + $objectTree = $this->getMock('\OCA\DAV\Connector\Sabre\ObjectTree', + array('nodeExists', 'getNodeForPath'), + array($rootDir, $view)); + + $objectTree->expects($this->once()) + ->method('getNodeForPath') + ->with($this->identicalTo($source)) + ->will($this->returnValue(false)); + + /** @var $objectTree \OCA\DAV\Connector\Sabre\ObjectTree */ + $mountManager = \OC\Files\Filesystem::getMountManager(); + $objectTree->init($rootDir, $view, $mountManager); + $objectTree->move($source, $destination); + } + + /** + * @dataProvider nodeForPathProvider + */ + public function testGetNodeForPath( + $inputFileName, + $fileInfoQueryPath, + $outputFileName, + $type, + $enableChunkingHeader + ) { + + if ($enableChunkingHeader) { + $_SERVER['HTTP_OC_CHUNKED'] = true; + } + + $rootNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $mountManager = $this->getMock('\OC\Files\Mount\Manager'); + $view = $this->getMock('\OC\Files\View'); + $fileInfo = $this->getMock('\OCP\Files\FileInfo'); + $fileInfo->expects($this->once()) + ->method('getType') + ->will($this->returnValue($type)); + $fileInfo->expects($this->once()) + ->method('getName') + ->will($this->returnValue($outputFileName)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with($fileInfoQueryPath) + ->will($this->returnValue($fileInfo)); + + $tree = new \OCA\DAV\Connector\Sabre\ObjectTree(); + $tree->init($rootNode, $view, $mountManager); + + $node = $tree->getNodeForPath($inputFileName); + + $this->assertNotNull($node); + $this->assertEquals($outputFileName, $node->getName()); + + if ($type === 'file') { + $this->assertTrue($node instanceof \OCA\DAV\Connector\Sabre\File); + } else { + $this->assertTrue($node instanceof \OCA\DAV\Connector\Sabre\Directory); + } + + unset($_SERVER['HTTP_OC_CHUNKED']); + } + + function nodeForPathProvider() { + return array( + // regular file + array( + 'regularfile.txt', + 'regularfile.txt', + 'regularfile.txt', + 'file', + false + ), + // regular directory + array( + 'regulardir', + 'regulardir', + 'regulardir', + 'dir', + false + ), + // regular file with chunking + array( + 'regularfile.txt', + 'regularfile.txt', + 'regularfile.txt', + 'file', + true + ), + // regular directory with chunking + array( + 'regulardir', + 'regulardir', + 'regulardir', + 'dir', + true + ), + // file with chunky file name + array( + 'regularfile.txt-chunking-123566789-10-1', + 'regularfile.txt', + 'regularfile.txt', + 'file', + true + ), + // regular file in subdir + array( + 'subdir/regularfile.txt', + 'subdir/regularfile.txt', + 'regularfile.txt', + 'file', + false + ), + // regular directory in subdir + array( + 'subdir/regulardir', + 'subdir/regulardir', + 'regulardir', + 'dir', + false + ), + // file with chunky file name in subdir + array( + 'subdir/regularfile.txt-chunking-123566789-10-1', + 'subdir/regularfile.txt', + 'regularfile.txt', + 'file', + true + ), + ); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\InvalidPath + */ + public function testGetNodeForPathInvalidPath() { + $path = '/foo\bar'; + + + $storage = new Temporary([]); + + $view = $this->getMock('\OC\Files\View', ['resolvePath']); + $view->expects($this->once()) + ->method('resolvePath') + ->will($this->returnCallback(function($path) use ($storage){ + return [$storage, ltrim($path, '/')]; + })); + + $rootNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $mountManager = $this->getMock('\OC\Files\Mount\Manager'); + + $tree = new \OCA\DAV\Connector\Sabre\ObjectTree(); + $tree->init($rootNode, $view, $mountManager); + + $tree->getNodeForPath($path); + } + + public function testGetNodeForPathRoot() { + $path = '/'; + + + $storage = new Temporary([]); + + $view = $this->getMock('\OC\Files\View', ['resolvePath']); + $view->expects($this->any()) + ->method('resolvePath') + ->will($this->returnCallback(function ($path) use ($storage) { + return [$storage, ltrim($path, '/')]; + })); + + $rootNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $mountManager = $this->getMock('\OC\Files\Mount\Manager'); + + $tree = new \OCA\DAV\Connector\Sabre\ObjectTree(); + $tree->init($rootNode, $view, $mountManager); + + $this->assertInstanceOf('\Sabre\DAV\INode', $tree->getNodeForPath($path)); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/principal.php b/apps/dav/tests/unit/connector/sabre/principal.php new file mode 100644 index 00000000000..3c0abeac3f1 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/principal.php @@ -0,0 +1,250 @@ +<?php +/** + * @author Lukas Reschke + * @copyright 2014 Lukas Reschke lukas@owncloud.com + * + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Connector\Sabre; + +use \Sabre\DAV\PropPatch; +use OCP\IUserManager; +use OCP\IConfig; + +class Principal extends \Test\TestCase { + /** @var IUserManager */ + private $userManager; + /** @var IConfig */ + private $config; + /** @var \OCA\DAV\Connector\Sabre\Principal */ + private $connector; + + public function setUp() { + $this->userManager = $this->getMockBuilder('\OCP\IUserManager') + ->disableOriginalConstructor()->getMock(); + $this->config = $this->getMockBuilder('\OCP\IConfig') + ->disableOriginalConstructor()->getMock(); + + $this->connector = new \OCA\DAV\Connector\Sabre\Principal($this->config, $this->userManager); + parent::setUp(); + } + + public function testGetPrincipalsByPrefixWithoutPrefix() { + $response = $this->connector->getPrincipalsByPrefix(''); + $this->assertSame([], $response); + } + + public function testGetPrincipalsByPrefixWithUsers() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('foo')); + $barUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $barUser + ->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('bar')); + $this->userManager + ->expects($this->once()) + ->method('search') + ->with('') + ->will($this->returnValue([$fooUser, $barUser])); + $this->config + ->expects($this->at(0)) + ->method('getUserValue') + ->with('foo', 'settings', 'email') + ->will($this->returnValue('')); + $this->config + ->expects($this->at(1)) + ->method('getUserValue') + ->with('bar', 'settings', 'email') + ->will($this->returnValue('bar@owncloud.org')); + + $expectedResponse = [ + 0 => [ + 'uri' => 'principals/foo', + '{DAV:}displayname' => 'foo' + ], + 1 => [ + 'uri' => 'principals/bar', + '{DAV:}displayname' => 'bar', + '{http://sabredav.org/ns}email-address' => 'bar@owncloud.org' + ] + ]; + $response = $this->connector->getPrincipalsByPrefix('principals'); + $this->assertSame($expectedResponse, $response); + } + + public function testGetPrincipalsByPrefixEmpty() { + $this->userManager + ->expects($this->once()) + ->method('search') + ->with('') + ->will($this->returnValue([])); + + $response = $this->connector->getPrincipalsByPrefix('principals'); + $this->assertSame([], $response); + } + + public function testGetPrincipalsByPathWithoutMail() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('foo')); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($fooUser)); + $this->config + ->expects($this->once()) + ->method('getUserValue') + ->with('foo', 'settings', 'email') + ->will($this->returnValue('')); + + $expectedResponse = [ + 'uri' => 'principals/foo', + '{DAV:}displayname' => 'foo' + ]; + $response = $this->connector->getPrincipalByPath('principals/foo'); + $this->assertSame($expectedResponse, $response); + } + + public function testGetPrincipalsByPathWithMail() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('foo')); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($fooUser)); + $this->config + ->expects($this->once()) + ->method('getUserValue') + ->with('foo', 'settings', 'email') + ->will($this->returnValue('foo@owncloud.org')); + + $expectedResponse = [ + 'uri' => 'principals/foo', + '{DAV:}displayname' => 'foo', + '{http://sabredav.org/ns}email-address' => 'foo@owncloud.org' + ]; + $response = $this->connector->getPrincipalByPath('principals/foo'); + $this->assertSame($expectedResponse, $response); + } + + public function testGetPrincipalsByPathEmpty() { + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue(null)); + + $response = $this->connector->getPrincipalByPath('principals/foo'); + $this->assertSame(null, $response); + } + + public function testGetGroupMemberSet() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('foo')); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($fooUser)); + $this->config + ->expects($this->once()) + ->method('getUserValue') + ->with('foo', 'settings', 'email') + ->will($this->returnValue('foo@owncloud.org')); + + $response = $this->connector->getGroupMemberSet('principals/foo'); + $this->assertSame(['principals/foo'], $response); + } + + /** + * @expectedException \Sabre\DAV\Exception + * @expectedExceptionMessage Principal not found + */ + public function testGetGroupMemberSetEmpty() { + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue(null)); + + $this->connector->getGroupMemberSet('principals/foo'); + } + + public function testGetGroupMembership() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('foo')); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($fooUser)); + $this->config + ->expects($this->once()) + ->method('getUserValue') + ->with('foo', 'settings', 'email') + ->will($this->returnValue('foo@owncloud.org')); + + $expectedResponse = [ + 'principals/foo/calendar-proxy-read', + 'principals/foo/calendar-proxy-write' + ]; + $response = $this->connector->getGroupMembership('principals/foo'); + $this->assertSame($expectedResponse, $response); + } + + /** + * @expectedException \Sabre\DAV\Exception + * @expectedExceptionMessage Principal not found + */ + public function testGetGroupMembershipEmpty() { + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue(null)); + + $this->connector->getGroupMembership('principals/foo'); + } + + /** + * @expectedException \Sabre\DAV\Exception + * @expectedExceptionMessage Setting members of the group is not supported yet + */ + public function testSetGroupMembership() { + $this->connector->setGroupMemberSet('principals/foo', ['foo']); + } + + public function testUpdatePrincipal() { + $this->assertSame(0, $this->connector->updatePrincipal('foo', new PropPatch(array()))); + } + + public function testSearchPrincipals() { + $this->assertSame([], $this->connector->searchPrincipals('principals', [])); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/quotaplugin.php b/apps/dav/tests/unit/connector/sabre/quotaplugin.php new file mode 100644 index 00000000000..5d3364e1f8c --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/quotaplugin.php @@ -0,0 +1,103 @@ +<?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. + */ +class Test_OC_Connector_Sabre_QuotaPlugin extends \Test\TestCase { + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \OCA\DAV\Connector\Sabre\QuotaPlugin + */ + private $plugin; + + private function init($quota) { + $view = $this->buildFileViewMock($quota); + $this->server = new \Sabre\DAV\Server(); + $this->plugin = new \OCA\DAV\Connector\Sabre\QuotaPlugin($view); + $this->plugin->initialize($this->server); + } + + /** + * @dataProvider lengthProvider + */ + public function testLength($expected, $headers) { + $this->init(0); + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); + $length = $this->plugin->getLength(); + $this->assertEquals($expected, $length); + } + + /** + * @dataProvider quotaOkayProvider + */ + public function testCheckQuota($quota, $headers) { + $this->init($quota); + + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); + $result = $this->plugin->checkQuota(''); + $this->assertTrue($result); + } + + /** + * @expectedException \Sabre\DAV\Exception\InsufficientStorage + * @dataProvider quotaExceededProvider + */ + public function testCheckExceededQuota($quota, $headers) { + $this->init($quota); + + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); + $this->plugin->checkQuota(''); + } + + public function quotaOkayProvider() { + return array( + array(1024, array()), + array(1024, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(1024, array('CONTENT-LENGTH' => '512')), + array(1024, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')), + // \OCP\Files\FileInfo::SPACE-UNKNOWN = -2 + array(-2, array()), + array(-2, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(-2, array('CONTENT-LENGTH' => '512')), + array(-2, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')), + ); + } + + public function quotaExceededProvider() { + return array( + array(1023, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(511, array('CONTENT-LENGTH' => '512')), + array(2047, array('OC-TOTAL-LENGTH' => '2048', 'CONTENT-LENGTH' => '1024')), + ); + } + + public function lengthProvider() { + return array( + array(null, array()), + array(1024, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(512, array('CONTENT-LENGTH' => '512')), + array(2048, array('OC-TOTAL-LENGTH' => '2048', 'CONTENT-LENGTH' => '1024')), + array(4096, array('OC-TOTAL-LENGTH' => '2048', 'X-EXPECTED-ENTITY-LENGTH' => '4096')), + ); + } + + private function buildFileViewMock($quota) { + // mock filesysten + $view = $this->getMock('\OC\Files\View', array('free_space'), array(), '', false); + $view->expects($this->any()) + ->method('free_space') + ->with($this->identicalTo('')) + ->will($this->returnValue($quota)); + + return $view; + } + +} diff --git a/apps/dav/tests/unit/connector/sabre/requesttest/auth.php b/apps/dav/tests/unit/connector/sabre/requesttest/auth.php new file mode 100644 index 00000000000..41b554d11db --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/requesttest/auth.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright (c) 2015 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 OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest; + +use Sabre\DAV\Auth\Backend\BackendInterface; + +class Auth implements BackendInterface { + /** + * @var string + */ + private $user; + + /** + * @var string + */ + private $password; + + /** + * Auth constructor. + * + * @param string $user + * @param string $password + */ + public function __construct($user, $password) { + $this->user = $user; + $this->password = $password; + } + + + /** + * Authenticates the user based on the current request. + * + * If authentication is successful, true must be returned. + * If authentication fails, an exception must be thrown. + * + * @param \Sabre\DAV\Server $server + * @param string $realm + * @return bool + */ + function authenticate(\Sabre\DAV\Server $server, $realm) { + $userSession = \OC::$server->getUserSession(); + $result = $userSession->login($this->user, $this->password); + if ($result) { + //we need to pass the user name, which may differ from login name + $user = $userSession->getUser()->getUID(); + \OC_Util::setupFS($user); + //trigger creation of user home and /files folder + \OC::$server->getUserFolder($user); + } + return $result; + } + + /** + * Returns information about the currently logged in username. + * + * If nobody is currently logged in, this method should return null. + * + * @return string|null + */ + function getCurrentUser() { + return $this->user; + } +} diff --git a/apps/dav/tests/unit/connector/sabre/requesttest/downloadtest.php b/apps/dav/tests/unit/connector/sabre/requesttest/downloadtest.php new file mode 100644 index 00000000000..245deff3b31 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/requesttest/downloadtest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright (c) 2015 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 OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest; + +use OCP\AppFramework\Http; +use OCP\Lock\ILockingProvider; + +class DownloadTest extends RequestTest { + public function testDownload() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + + $response = $this->request($view, $user, 'pass', 'GET', '/foo.txt'); + $this->assertEquals(Http::STATUS_OK, $response->getStatus()); + $this->assertEquals(stream_get_contents($response->getBody()), 'bar'); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\FileLocked + */ + public function testDownloadWriteLocked() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + + $view->lockFile('/foo.txt', ILockingProvider::LOCK_EXCLUSIVE); + + $this->request($view, $user, 'pass', 'GET', '/foo.txt', 'asd'); + } + + public function testDownloadReadLocked() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + + $view->lockFile('/foo.txt', ILockingProvider::LOCK_SHARED); + + $response = $this->request($view, $user, 'pass', 'GET', '/foo.txt', 'asd'); + $this->assertEquals(Http::STATUS_OK, $response->getStatus()); + $this->assertEquals(stream_get_contents($response->getBody()), 'bar'); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/requesttest/encryptionuploadtest.php b/apps/dav/tests/unit/connector/sabre/requesttest/encryptionuploadtest.php new file mode 100644 index 00000000000..ed1d6046d75 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/requesttest/encryptionuploadtest.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright (c) 2015 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 OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest; + +use OC\Files\View; +use Test\Traits\EncryptionTrait; + +class EncryptionUploadTest extends UploadTest { + use EncryptionTrait; + + protected function setupUser($name, $password) { + $this->createUser($name, $password); + $tmpFolder = \OC::$server->getTempManager()->getTemporaryFolder(); + $this->registerMount($name, '\OC\Files\Storage\Local', '/' . $name, ['datadir' => $tmpFolder]); + $this->setupForUser($name, $password); + $this->loginWithEncryption($name); + return new View('/' . $name . '/files'); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/requesttest/exceptionplugin.php b/apps/dav/tests/unit/connector/sabre/requesttest/exceptionplugin.php new file mode 100644 index 00000000000..53cd186bbc8 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/requesttest/exceptionplugin.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright (c) 2015 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 OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest; + +use Sabre\DAV\Exception; + +class ExceptionPlugin extends \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin { + /** + * @var \Exception[] + */ + protected $exceptions = []; + + public function logException(\Exception $ex) { + $exceptionClass = get_class($ex); + if (!isset($this->nonFatalExceptions[$exceptionClass])) { + $this->exceptions[] = $ex; + } + } + + /** + * @return \Exception[] + */ + public function getExceptions() { + return $this->exceptions; + } +} diff --git a/apps/dav/tests/unit/connector/sabre/requesttest/requesttest.php b/apps/dav/tests/unit/connector/sabre/requesttest/requesttest.php new file mode 100644 index 00000000000..d90cf6e19bc --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/requesttest/requesttest.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright (c) 2015 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 OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest; + +use OCA\DAV\Connector\Sabre\Server; +use OCA\DAV\Connector\Sabre\ServerFactory; +use OC\Files\Mount\MountPoint; +use OC\Files\Storage\StorageFactory; +use OC\Files\Storage\Temporary; +use OC\Files\View; +use OCP\IUser; +use Sabre\HTTP\Request; +use Test\TestCase; +use Test\Traits\MountProviderTrait; +use Test\Traits\UserTrait; + +abstract class RequestTest extends TestCase { + use UserTrait; + use MountProviderTrait; + + /** + * @var \OCA\DAV\Connector\Sabre\ServerFactory + */ + protected $serverFactory; + + protected function getStream($string) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $string); + fseek($stream, 0); + return $stream; + } + + protected function setUp() { + parent::setUp(); + + $this->serverFactory = new ServerFactory( + \OC::$server->getConfig(), + \OC::$server->getLogger(), + \OC::$server->getDatabaseConnection(), + \OC::$server->getUserSession(), + \OC::$server->getMountManager(), + \OC::$server->getTagManager(), + \OC::$server->getEventDispatcher() + ); + } + + protected function setupUser($name, $password) { + $this->createUser($name, $password); + $tmpFolder = \OC::$server->getTempManager()->getTemporaryFolder(); + $this->registerMount($name, '\OC\Files\Storage\Local', '/' . $name, ['datadir' => $tmpFolder]); + $this->loginAsUser($name); + return new View('/' . $name . '/files'); + } + + /** + * @param \OC\Files\View $view the view to run the webdav server against + * @param string $user + * @param string $password + * @param string $method + * @param string $url + * @param resource|string|null $body + * @param array|null $headers + * @return \Sabre\HTTP\Response + */ + protected function request($view, $user, $password, $method, $url, $body = null, $headers = null) { + if (is_string($body)) { + $body = $this->getStream($body); + } + $this->logout(); + $exceptionPlugin = new ExceptionPlugin('webdav', null); + $server = $this->getSabreServer($view, $user, $password, $exceptionPlugin); + $request = new Request($method, $url, $headers, $body); + + // since sabre catches all exceptions we need to save them and throw them from outside the sabre server + + $originalServer = $_SERVER; + + if (is_array($headers)) { + foreach ($headers as $header => $value) { + $_SERVER['HTTP_' . strtoupper(str_replace('-', '_', $header))] = $value; + } + } + + $result = $this->makeRequest($server, $request); + + foreach ($exceptionPlugin->getExceptions() as $exception) { + throw $exception; + } + $_SERVER = $originalServer; + return $result; + } + + /** + * @param Server $server + * @param Request $request + * @return \Sabre\HTTP\Response + */ + protected function makeRequest(Server $server, Request $request) { + $sapi = new Sapi($request); + $server->sapi = $sapi; + $server->httpRequest = $request; + $server->exec(); + return $sapi->getResponse(); + } + + /** + * @param View $view + * @param string $user + * @param string $password + * @param ExceptionPlugin $exceptionPlugin + * @return Server + */ + protected function getSabreServer(View $view, $user, $password, ExceptionPlugin $exceptionPlugin) { + $authBackend = new Auth($user, $password); + + $server = $this->serverFactory->createServer('/', 'dummy', $authBackend, function () use ($view) { + return $view; + }); + $server->addPlugin($exceptionPlugin); + + return $server; + } +} diff --git a/apps/dav/tests/unit/connector/sabre/requesttest/sapi.php b/apps/dav/tests/unit/connector/sabre/requesttest/sapi.php new file mode 100644 index 00000000000..3af94010288 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/requesttest/sapi.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright (c) 2015 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 OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +class Sapi { + /** + * @var \Sabre\HTTP\Request + */ + private $request; + + /** + * @var \Sabre\HTTP\Response + */ + private $response; + + /** + * This static method will create a new Request object, based on the + * current PHP request. + * + * @return \Sabre\HTTP\Request + */ + public function getRequest() { + return $this->request; + } + + public function __construct(Request $request) { + $this->request = $request; + } + + /** + * @param \Sabre\HTTP\Response $response + * @return void + */ + public function sendResponse(Response $response) { + // we need to copy the body since we close the source stream + $copyStream = fopen('php://temp', 'r+'); + if (is_string($response->getBody())) { + fwrite($copyStream, $response->getBody()); + } else if (is_resource($response->getBody())) { + stream_copy_to_stream($response->getBody(), $copyStream); + } + rewind($copyStream); + $this->response = new Response($response->getStatus(), $response->getHeaders(), $copyStream); + } + + /** + * @return \Sabre\HTTP\Response + */ + public function getResponse() { + return $this->response; + } +} diff --git a/apps/dav/tests/unit/connector/sabre/requesttest/uploadtest.php b/apps/dav/tests/unit/connector/sabre/requesttest/uploadtest.php new file mode 100644 index 00000000000..a2a8326f4ff --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/requesttest/uploadtest.php @@ -0,0 +1,190 @@ +<?php +/** + * Copyright (c) 2015 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 OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest; + +use OC\Connector\Sabre\Exception\FileLocked; +use OCP\AppFramework\Http; +use OCP\Lock\ILockingProvider; + +class UploadTest extends RequestTest { + public function testBasicUpload() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt', 'asd'); + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertTrue($view->file_exists('foo.txt')); + $this->assertEquals('asd', $view->file_get_contents('foo.txt')); + + $info = $view->getFileInfo('foo.txt'); + $this->assertInstanceOf('\OC\Files\FileInfo', $info); + $this->assertEquals(3, $info->getSize()); + } + + public function testUploadOverWrite() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'foobar'); + + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt', 'asd'); + + $this->assertEquals(Http::STATUS_NO_CONTENT, $response->getStatus()); + $this->assertEquals('asd', $view->file_get_contents('foo.txt')); + + $info = $view->getFileInfo('foo.txt'); + $this->assertInstanceOf('\OC\Files\FileInfo', $info); + $this->assertEquals(3, $info->getSize()); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\FileLocked + */ + public function testUploadOverWriteReadLocked() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + + $view->lockFile('/foo.txt', ILockingProvider::LOCK_SHARED); + + $this->request($view, $user, 'pass', 'PUT', '/foo.txt', 'asd'); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\FileLocked + */ + public function testUploadOverWriteWriteLocked() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + + $view->lockFile('/foo.txt', ILockingProvider::LOCK_EXCLUSIVE); + + $this->request($view, $user, 'pass', 'PUT', '/foo.txt', 'asd'); + } + + public function testChunkedUpload() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + + $this->assertEquals(201, $response->getStatus()); + $this->assertFalse($view->file_exists('foo.txt')); + + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertTrue($view->file_exists('foo.txt')); + + $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); + + $info = $view->getFileInfo('foo.txt'); + $this->assertInstanceOf('\OC\Files\FileInfo', $info); + $this->assertEquals(6, $info->getSize()); + } + + public function testChunkedUploadOverWrite() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertEquals('bar', $view->file_get_contents('foo.txt')); + + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + + $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); + + $info = $view->getFileInfo('foo.txt'); + $this->assertInstanceOf('\OC\Files\FileInfo', $info); + $this->assertEquals(6, $info->getSize()); + } + + public function testChunkedUploadOutOfOrder() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertFalse($view->file_exists('foo.txt')); + + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + + $this->assertEquals(201, $response->getStatus()); + $this->assertTrue($view->file_exists('foo.txt')); + + $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); + + $info = $view->getFileInfo('foo.txt'); + $this->assertInstanceOf('\OC\Files\FileInfo', $info); + $this->assertEquals(6, $info->getSize()); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\FileLocked + */ + public function testChunkedUploadOutOfOrderReadLocked() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + + $view->lockFile('/foo.txt', ILockingProvider::LOCK_SHARED); + + try { + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { + $this->fail('Didn\'t expect locked error for the first chunk on read lock'); + return; + } + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertFalse($view->file_exists('foo.txt')); + + // last chunk should trigger the locked error since it tries to assemble + $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\FileLocked + */ + public function testChunkedUploadOutOfOrderWriteLocked() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + + $view->lockFile('/foo.txt', ILockingProvider::LOCK_EXCLUSIVE); + + try { + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { + $this->fail('Didn\'t expect locked error for the first chunk on write lock'); // maybe forbid this in the future for write locks only? + return; + } + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertFalse($view->file_exists('foo.txt')); + + // last chunk should trigger the locked error since it tries to assemble + $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/tagsplugin.php b/apps/dav/tests/unit/connector/sabre/tagsplugin.php new file mode 100644 index 00000000000..4731e770cfa --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/tagsplugin.php @@ -0,0 +1,398 @@ +<?php + +namespace Tests\Connector\Sabre; + +/** + * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class TagsPlugin extends \Test\TestCase { + + const TAGS_PROPERTYNAME = \OCA\DAV\Connector\Sabre\TagsPlugin::TAGS_PROPERTYNAME; + const FAVORITE_PROPERTYNAME = \OCA\DAV\Connector\Sabre\TagsPlugin::FAVORITE_PROPERTYNAME; + const TAG_FAVORITE = \OCA\DAV\Connector\Sabre\TagsPlugin::TAG_FAVORITE; + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\ObjectTree + */ + private $tree; + + /** + * @var \OCP\ITagManager + */ + private $tagManager; + + /** + * @var \OCP\ITags + */ + private $tagger; + + /** + * @var \OCA\DAV\Connector\Sabre\TagsPlugin + */ + private $plugin; + + public function setUp() { + parent::setUp(); + $this->server = new \Sabre\DAV\Server(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + $this->tagger = $this->getMock('\OCP\ITags'); + $this->tagManager = $this->getMock('\OCP\ITagManager'); + $this->tagManager->expects($this->any()) + ->method('load') + ->with('files') + ->will($this->returnValue($this->tagger)); + $this->plugin = new \OCA\DAV\Connector\Sabre\TagsPlugin($this->tree, $this->tagManager); + $this->plugin->initialize($this->server); + } + + /** + * @dataProvider tagsGetPropertiesDataProvider + */ + public function testGetProperties($tags, $requestedProperties, $expectedProperties) { + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Node') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $expectedCallCount = 0; + if (count($requestedProperties) > 0) { + $expectedCallCount = 1; + } + + $this->tagger->expects($this->exactly($expectedCallCount)) + ->method('getTagsForObjects') + ->with($this->equalTo(array(123))) + ->will($this->returnValue(array(123 => $tags))); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + $requestedProperties, + 0 + ); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $result = $propFind->getResultForMultiStatus(); + + $this->assertEmpty($result[404]); + unset($result[404]); + $this->assertEquals($expectedProperties, $result); + } + + /** + * @dataProvider tagsGetPropertiesDataProvider + */ + public function testPreloadThenGetProperties($tags, $requestedProperties, $expectedProperties) { + $node1 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $node1->expects($this->any()) + ->method('getId') + ->will($this->returnValue(111)); + $node2 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $node2->expects($this->any()) + ->method('getId') + ->will($this->returnValue(222)); + + $expectedCallCount = 0; + if (count($requestedProperties) > 0) { + // this guarantees that getTagsForObjects + // is only called once and then the tags + // are cached + $expectedCallCount = 1; + } + + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + $node->expects($this->exactly($expectedCallCount)) + ->method('getChildren') + ->will($this->returnValue(array($node1, $node2))); + + $this->tagger->expects($this->exactly($expectedCallCount)) + ->method('getTagsForObjects') + ->with($this->equalTo(array(123, 111, 222))) + ->will($this->returnValue( + array( + 111 => $tags, + 123 => $tags + ) + )); + + // simulate sabre recursive PROPFIND traversal + $propFindRoot = new \Sabre\DAV\PropFind( + '/subdir', + $requestedProperties, + 1 + ); + $propFind1 = new \Sabre\DAV\PropFind( + '/subdir/test.txt', + $requestedProperties, + 0 + ); + $propFind2 = new \Sabre\DAV\PropFind( + '/subdir/test2.txt', + $requestedProperties, + 0 + ); + + $this->plugin->handleGetProperties( + $propFindRoot, + $node + ); + $this->plugin->handleGetProperties( + $propFind1, + $node1 + ); + $this->plugin->handleGetProperties( + $propFind2, + $node2 + ); + + $result = $propFind1->getResultForMultiStatus(); + + $this->assertEmpty($result[404]); + unset($result[404]); + $this->assertEquals($expectedProperties, $result); + } + + function tagsGetPropertiesDataProvider() { + return array( + // request both, receive both + array( + array('tag1', 'tag2', self::TAG_FAVORITE), + array(self::TAGS_PROPERTYNAME, self::FAVORITE_PROPERTYNAME), + array( + 200 => array( + self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(array('tag1', 'tag2')), + self::FAVORITE_PROPERTYNAME => true, + ) + ) + ), + // request tags alone + array( + array('tag1', 'tag2', self::TAG_FAVORITE), + array(self::TAGS_PROPERTYNAME), + array( + 200 => array( + self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(array('tag1', 'tag2')), + ) + ) + ), + // request fav alone + array( + array('tag1', 'tag2', self::TAG_FAVORITE), + array(self::FAVORITE_PROPERTYNAME), + array( + 200 => array( + self::FAVORITE_PROPERTYNAME => true, + ) + ) + ), + // request none + array( + array('tag1', 'tag2', self::TAG_FAVORITE), + array(), + array( + 200 => array() + ), + ), + // request both with none set, receive both + array( + array(), + array(self::TAGS_PROPERTYNAME, self::FAVORITE_PROPERTYNAME), + array( + 200 => array( + self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(array()), + self::FAVORITE_PROPERTYNAME => false, + ) + ) + ), + ); + } + + public function testUpdateTags() { + // this test will replace the existing tags "tagremove" with "tag1" and "tag2" + // and keep "tagkeep" + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Node') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $this->tagger->expects($this->at(0)) + ->method('getTagsForObjects') + ->with($this->equalTo(array(123))) + ->will($this->returnValue(array(123 => array('tagkeep', 'tagremove', self::TAG_FAVORITE)))); + + // then tag as tag1 and tag2 + $this->tagger->expects($this->at(1)) + ->method('tagAs') + ->with(123, 'tag1'); + $this->tagger->expects($this->at(2)) + ->method('tagAs') + ->with(123, 'tag2'); + + // it will untag tag3 + $this->tagger->expects($this->at(3)) + ->method('unTag') + ->with(123, 'tagremove'); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(array('tag1', 'tag2', 'tagkeep')) + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + // all requested properties removed, as they were processed already + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(200, $result[self::TAGS_PROPERTYNAME]); + $this->assertFalse(isset($result[self::FAVORITE_PROPERTYNAME])); + } + + public function testUpdateTagsFromScratch() { + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Node') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $this->tagger->expects($this->at(0)) + ->method('getTagsForObjects') + ->with($this->equalTo(array(123))) + ->will($this->returnValue(array())); + + // then tag as tag1 and tag2 + $this->tagger->expects($this->at(1)) + ->method('tagAs') + ->with(123, 'tag1'); + $this->tagger->expects($this->at(2)) + ->method('tagAs') + ->with(123, 'tag2'); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(array('tag1', 'tag2', 'tagkeep')) + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + // all requested properties removed, as they were processed already + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(200, $result[self::TAGS_PROPERTYNAME]); + $this->assertFalse(false, isset($result[self::FAVORITE_PROPERTYNAME])); + } + + public function testUpdateFav() { + // this test will replace the existing tags "tagremove" with "tag1" and "tag2" + // and keep "tagkeep" + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Node') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + // set favorite tag + $this->tagger->expects($this->once()) + ->method('tagAs') + ->with(123, self::TAG_FAVORITE); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::FAVORITE_PROPERTYNAME => true + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + // all requested properties removed, as they were processed already + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertFalse(false, isset($result[self::TAGS_PROPERTYNAME])); + $this->assertEquals(200, isset($result[self::FAVORITE_PROPERTYNAME])); + + // unfavorite now + // set favorite tag + $this->tagger->expects($this->once()) + ->method('unTag') + ->with(123, self::TAG_FAVORITE); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::FAVORITE_PROPERTYNAME => false + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + // all requested properties removed, as they were processed already + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertFalse(false, isset($result[self::TAGS_PROPERTYNAME])); + $this->assertEquals(200, isset($result[self::FAVORITE_PROPERTYNAME])); + } + +} diff --git a/apps/encryption/appinfo/application.php b/apps/encryption/appinfo/application.php index 812f1042a8f..6275047252e 100644 --- a/apps/encryption/appinfo/application.php +++ b/apps/encryption/appinfo/application.php @@ -201,7 +201,8 @@ class Application extends \OCP\AppFramework\App { $c->query('KeyManager'), $c->query('Crypt'), $c->query('Session'), - $server->getSession() + $server->getSession(), + $c->query('Util') ); }); diff --git a/apps/encryption/appinfo/info.xml b/apps/encryption/appinfo/info.xml index 536551e4841..2224f026e4d 100644 --- a/apps/encryption/appinfo/info.xml +++ b/apps/encryption/appinfo/info.xml @@ -14,18 +14,19 @@ <name>Default encryption module</name> <license>AGPL</license> <author>Bjoern Schiessle, Clark Tomlinson</author> - <requiremin>8</requiremin> <shipped>true</shipped> <documentation> <user>user-encryption</user> <admin>admin-encryption</admin> </documentation> <rememberlogin>false</rememberlogin> + <version>1.2.0</version> <types> <filesystem/> </types> <dependencies> <lib>openssl</lib> + <owncloud min-version="9.0" /> </dependencies> </info> diff --git a/apps/encryption/appinfo/routes.php b/apps/encryption/appinfo/routes.php index 8fa163d0751..260337361e8 100644 --- a/apps/encryption/appinfo/routes.php +++ b/apps/encryption/appinfo/routes.php @@ -36,6 +36,11 @@ namespace OCA\Encryption\AppInfo; 'verb' => 'POST' ], [ + 'name' => 'Settings#setEncryptHomeStorage', + 'url' => '/ajax/setEncryptHomeStorage', + 'verb' => 'POST' + ], + [ 'name' => 'Recovery#changeRecoveryPassword', 'url' => '/ajax/changeRecoveryPassword', 'verb' => 'POST' diff --git a/apps/encryption/appinfo/version b/apps/encryption/appinfo/version deleted file mode 100644 index 3eefcb9dd5b..00000000000 --- a/apps/encryption/appinfo/version +++ /dev/null @@ -1 +0,0 @@ -1.0.0 diff --git a/apps/encryption/controller/settingscontroller.php b/apps/encryption/controller/settingscontroller.php index e5bb79a1d40..59e23087b3a 100644 --- a/apps/encryption/controller/settingscontroller.php +++ b/apps/encryption/controller/settingscontroller.php @@ -25,6 +25,7 @@ namespace OCA\Encryption\Controller; use OCA\Encryption\Crypto\Crypt; use OCA\Encryption\KeyManager; use OCA\Encryption\Session; +use OCA\Encryption\Util; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; @@ -57,6 +58,9 @@ class SettingsController extends Controller { /** @var ISession */ private $ocSession; + /** @var Util */ + private $util; + /** * @param string $AppName * @param IRequest $request @@ -67,6 +71,7 @@ class SettingsController extends Controller { * @param Crypt $crypt * @param Session $session * @param ISession $ocSession + * @param Util $util */ public function __construct($AppName, IRequest $request, @@ -76,7 +81,9 @@ class SettingsController extends Controller { KeyManager $keyManager, Crypt $crypt, Session $session, - ISession $ocSession) { + ISession $ocSession, + Util $util +) { parent::__construct($AppName, $request); $this->l = $l10n; $this->userSession = $userSession; @@ -85,6 +92,7 @@ class SettingsController extends Controller { $this->crypt = $crypt; $this->session = $session; $this->ocSession = $ocSession; + $this->util = $util; } @@ -143,4 +151,15 @@ class SettingsController extends Controller { } } + + /** + * @UseSession + * + * @param bool $encryptHomeStorage + * @return DataResponse + */ + public function setEncryptHomeStorage($encryptHomeStorage) { + $this->util->setEncryptHomeStorage($encryptHomeStorage); + return new DataResponse(); + } } diff --git a/apps/encryption/js/settings-admin.js b/apps/encryption/js/settings-admin.js index 39923718c21..9b00a4ec627 100644 --- a/apps/encryption/js/settings-admin.js +++ b/apps/encryption/js/settings-admin.js @@ -76,4 +76,13 @@ $(document).ready(function () { }); }); + $('#encryptHomeStorage').change(function() { + $.post( + OC.generateUrl('/apps/encryption/ajax/setEncryptHomeStorage'), + { + encryptHomeStorage: this.checked + } + ); + }); + }); diff --git a/apps/encryption/l10n/fr.js b/apps/encryption/l10n/fr.js index 3ee1c05ffa7..f6016cc3ce6 100644 --- a/apps/encryption/l10n/fr.js +++ b/apps/encryption/l10n/fr.js @@ -28,10 +28,10 @@ OC.L10N.register( "one-time password for server-side-encryption" : "Mot de passe à usage unique pour le chiffrement côté serveur", "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Impossible de déchiffrer ce fichier : il s'agit probablement d'un fichier partagé. Veuillez demander au propriétaire du fichier de le partager à nouveau avec vous.", "Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Impossible de lire ce fichier, il s'agit probablement d'un fichier partagé. Veuillez demander au propriétaire du fichier de le repartager avec vous. ", - "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Bonjour,\n\nL'administrateur a activé le chiffrement sur le serveur. Vos fichiers ont été chiffrés avec le mot de passe '%s'.\n\nVeuillez vous connecter dans l'interface web et aller dans la section \"Module de chiffrement de base d'ownCloud\" de vos paramètres personnels. De là, mettez à jour votre mot de passe de chiffrement en entrant le mot de passe fourni dans ce message dans le champ \"Ancien mot de passe de connexion\", et votre mot de passe de connexion actuel.\n", + "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Bonjour,\n\nL'administrateur a activé le chiffrement sur le serveur. Vos fichiers ont été chiffrés avec le mot de passe suivant :\n\n%s\n\nVeuillez suivre ces instructions :\n\n1. Connectez-vous à l'interface web et trouvez la section \"Module de chiffrement de base d'ownCloud\" dans vos paramètres personnels;\n\n2. Entrez le mot de passe fourni ci-dessus dans le champ \"Ancien mot de passe de connexion\";\n\n3. Entrez le mot de passe que vous utilisez actuellement pour vous connecter dans le champ \"Actuel mot de passe de connexion\";\n\n4. Validez en cliquant sur le bouton \"Mettre à jour le mot de passe de votre clef privée\".\n", "The share will expire on %s." : "Le partage expirera le %s.", "Cheers!" : "À bientôt !", - "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Bonjour,<br><br>L'administrateur a activé le chiffrement sur le serveur. Vos fichiers ont été chiffrés avec le mot de passe <strong>%s</strong>.<br><br>\nVeuillez vous connecter dans l'interface web et aller dans la section \"Module de chiffrement de base d'ownCloud\" de vos paramètres personnels. De là, mettez à jour votre mot de passe de chiffrement en entrant le mot de passe fourni dans ce message dans le champ \"Ancien mot de passe de connexion\", et votre mot de passe de connexion actuel.<br><br>", + "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Bonjour,\n<br><br>\nL'administrateur a activé le chiffrement sur le serveur. Vos fichiers ont été chiffrés avec le mot de passe suivant :\n\n<p style=\"font-family: monospace;\"><b>%s</b></p>\n\n<p>\nVeuillez suivre ces instructions :\n<ol>\n<li>Connectez-vous à l'interface web et trouvez la section <em>\"Module de chiffrement de base d'ownCloud\"</em> dans vos paramètres personnels;</li>\n<li>Entrez le mot de passe fourni ci-dessus dans le champ <em>\"Ancien mot de passe de connexion\"</em>;</li>\n<li>Entrez le mot de passe que vous utilisez actuellement pour vous connecter dans le champ <em>\"Actuel mot de passe de connexion\"</em>;</li>\n<li>Validez en cliquant sur le bouton <em>\"Mettre à jour le mot de passe de votre clef privée\"</em>.</li>\n</ol>\n</p>", "Enable recovery key" : "Activer la clé de récupération", "Disable recovery key" : "Désactiver la clé de récupération", "The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user's files if the user forgets his or her password." : "La clé de récupération est une clé supplémentaire utilisée pour chiffrer les fichiers. Elle permet de récupérer les fichiers des utilisateurs s'ils oublient leur mot de passe.", diff --git a/apps/encryption/l10n/fr.json b/apps/encryption/l10n/fr.json index 3694682d8bc..54a7431db30 100644 --- a/apps/encryption/l10n/fr.json +++ b/apps/encryption/l10n/fr.json @@ -26,10 +26,10 @@ "one-time password for server-side-encryption" : "Mot de passe à usage unique pour le chiffrement côté serveur", "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Impossible de déchiffrer ce fichier : il s'agit probablement d'un fichier partagé. Veuillez demander au propriétaire du fichier de le partager à nouveau avec vous.", "Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Impossible de lire ce fichier, il s'agit probablement d'un fichier partagé. Veuillez demander au propriétaire du fichier de le repartager avec vous. ", - "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Bonjour,\n\nL'administrateur a activé le chiffrement sur le serveur. Vos fichiers ont été chiffrés avec le mot de passe '%s'.\n\nVeuillez vous connecter dans l'interface web et aller dans la section \"Module de chiffrement de base d'ownCloud\" de vos paramètres personnels. De là, mettez à jour votre mot de passe de chiffrement en entrant le mot de passe fourni dans ce message dans le champ \"Ancien mot de passe de connexion\", et votre mot de passe de connexion actuel.\n", + "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Bonjour,\n\nL'administrateur a activé le chiffrement sur le serveur. Vos fichiers ont été chiffrés avec le mot de passe suivant :\n\n%s\n\nVeuillez suivre ces instructions :\n\n1. Connectez-vous à l'interface web et trouvez la section \"Module de chiffrement de base d'ownCloud\" dans vos paramètres personnels;\n\n2. Entrez le mot de passe fourni ci-dessus dans le champ \"Ancien mot de passe de connexion\";\n\n3. Entrez le mot de passe que vous utilisez actuellement pour vous connecter dans le champ \"Actuel mot de passe de connexion\";\n\n4. Validez en cliquant sur le bouton \"Mettre à jour le mot de passe de votre clef privée\".\n", "The share will expire on %s." : "Le partage expirera le %s.", "Cheers!" : "À bientôt !", - "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Bonjour,<br><br>L'administrateur a activé le chiffrement sur le serveur. Vos fichiers ont été chiffrés avec le mot de passe <strong>%s</strong>.<br><br>\nVeuillez vous connecter dans l'interface web et aller dans la section \"Module de chiffrement de base d'ownCloud\" de vos paramètres personnels. De là, mettez à jour votre mot de passe de chiffrement en entrant le mot de passe fourni dans ce message dans le champ \"Ancien mot de passe de connexion\", et votre mot de passe de connexion actuel.<br><br>", + "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Bonjour,\n<br><br>\nL'administrateur a activé le chiffrement sur le serveur. Vos fichiers ont été chiffrés avec le mot de passe suivant :\n\n<p style=\"font-family: monospace;\"><b>%s</b></p>\n\n<p>\nVeuillez suivre ces instructions :\n<ol>\n<li>Connectez-vous à l'interface web et trouvez la section <em>\"Module de chiffrement de base d'ownCloud\"</em> dans vos paramètres personnels;</li>\n<li>Entrez le mot de passe fourni ci-dessus dans le champ <em>\"Ancien mot de passe de connexion\"</em>;</li>\n<li>Entrez le mot de passe que vous utilisez actuellement pour vous connecter dans le champ <em>\"Actuel mot de passe de connexion\"</em>;</li>\n<li>Validez en cliquant sur le bouton <em>\"Mettre à jour le mot de passe de votre clef privée\"</em>.</li>\n</ol>\n</p>", "Enable recovery key" : "Activer la clé de récupération", "Disable recovery key" : "Désactiver la clé de récupération", "The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user's files if the user forgets his or her password." : "La clé de récupération est une clé supplémentaire utilisée pour chiffrer les fichiers. Elle permet de récupérer les fichiers des utilisateurs s'ils oublient leur mot de passe.", diff --git a/apps/encryption/l10n/ja.js b/apps/encryption/l10n/ja.js index 5c4f470def8..6babac76b2f 100644 --- a/apps/encryption/l10n/ja.js +++ b/apps/encryption/l10n/ja.js @@ -25,10 +25,13 @@ OC.L10N.register( "Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." : "暗号化アプリの無効なプライベートキーです。あなたの暗号化されたファイルへアクセスするために、個人設定からプライベートキーのパスワードを更新してください。", "Encryption App is enabled but your keys are not initialized, please log-out and log-in again" : "暗号化アプリは有効ですが、あなたの暗号化キーは初期化されていません。ログアウトした後に、再度ログインしてください", "Encryption App is enabled and ready" : "暗号化アプリは有効になっており、準備が整いました", + "one-time password for server-side-encryption" : "サーバーサイド暗号化のワンタイムパスワード", "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "このファイルを復号化できません、共有ファイルの可能性があります。ファイルの所有者にお願いして、ファイルを共有しなおしてもらってください。", "Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "このファイルを読み取ることができません、共有ファイルの可能性があります。ファイルの所有者にお願いして、ファイルを共有しなおしてもらってください。", + "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "こんにちは、\n\n管理者がサーバーサイド暗号化を有効にしました。'%s'というパスワードであなたのファイルが暗号化されました。\n\nWeb画面からログインして、個人設定画面の'ownCloud 基本暗号化モジュール' セクションにいき、暗号化パスワードの更新をお願いします。 '旧ログインパスワード'部分に上記パスワードを入力し、現在のログインパスワードで更新します。\n", "The share will expire on %s." : "共有は %s で有効期限が切れます。", "Cheers!" : "それでは!", + "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "こんにちは、<br><br>管理者がサーバーサイド暗号化を有効にしました。<strong>%s</strong>というパスワードであなたのファイルが暗号化されました。<br><br>Web画面からログインして、個人設定画面の\"ownCloud 基本暗号化モジュール\"のセクションにいき、暗号化パスワードの更新をお願いします。 \"旧ログインパスワード”部分に上記パスワードを入力し、現在のログインパスワードで更新します。<br><br>", "Enable recovery key" : "復旧キーを有効にする", "Disable recovery key" : "復旧キーを無効にする", "The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user's files if the user forgets his or her password." : "復旧キーは、ファイルの暗号化に使う特別な暗号化キーです。ユーザがパスワードを忘れてしまった場合には、リカバリキーを使ってユーザのファイルを復元することができます。", diff --git a/apps/encryption/l10n/ja.json b/apps/encryption/l10n/ja.json index 9e04dd87f83..9ae46a8d9c2 100644 --- a/apps/encryption/l10n/ja.json +++ b/apps/encryption/l10n/ja.json @@ -23,10 +23,13 @@ "Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." : "暗号化アプリの無効なプライベートキーです。あなたの暗号化されたファイルへアクセスするために、個人設定からプライベートキーのパスワードを更新してください。", "Encryption App is enabled but your keys are not initialized, please log-out and log-in again" : "暗号化アプリは有効ですが、あなたの暗号化キーは初期化されていません。ログアウトした後に、再度ログインしてください", "Encryption App is enabled and ready" : "暗号化アプリは有効になっており、準備が整いました", + "one-time password for server-side-encryption" : "サーバーサイド暗号化のワンタイムパスワード", "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "このファイルを復号化できません、共有ファイルの可能性があります。ファイルの所有者にお願いして、ファイルを共有しなおしてもらってください。", "Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "このファイルを読み取ることができません、共有ファイルの可能性があります。ファイルの所有者にお願いして、ファイルを共有しなおしてもらってください。", + "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "こんにちは、\n\n管理者がサーバーサイド暗号化を有効にしました。'%s'というパスワードであなたのファイルが暗号化されました。\n\nWeb画面からログインして、個人設定画面の'ownCloud 基本暗号化モジュール' セクションにいき、暗号化パスワードの更新をお願いします。 '旧ログインパスワード'部分に上記パスワードを入力し、現在のログインパスワードで更新します。\n", "The share will expire on %s." : "共有は %s で有効期限が切れます。", "Cheers!" : "それでは!", + "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "こんにちは、<br><br>管理者がサーバーサイド暗号化を有効にしました。<strong>%s</strong>というパスワードであなたのファイルが暗号化されました。<br><br>Web画面からログインして、個人設定画面の\"ownCloud 基本暗号化モジュール\"のセクションにいき、暗号化パスワードの更新をお願いします。 \"旧ログインパスワード”部分に上記パスワードを入力し、現在のログインパスワードで更新します。<br><br>", "Enable recovery key" : "復旧キーを有効にする", "Disable recovery key" : "復旧キーを無効にする", "The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user's files if the user forgets his or her password." : "復旧キーは、ファイルの暗号化に使う特別な暗号化キーです。ユーザがパスワードを忘れてしまった場合には、リカバリキーを使ってユーザのファイルを復元することができます。", diff --git a/apps/encryption/l10n/ko.js b/apps/encryption/l10n/ko.js index 8bf099b6b36..50dd5857692 100644 --- a/apps/encryption/l10n/ko.js +++ b/apps/encryption/l10n/ko.js @@ -28,10 +28,10 @@ OC.L10N.register( "one-time password for server-side-encryption" : "서버 측 암호화용 일회용 암호", "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "이 파일을 복호화할 수 없습니다. 공유된 파일일 수도 있습니다. 파일 소유자에게 공유를 다시 요청하십시오.", "Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "이 파일을 읽을 수 없습니다. 공유된 파일이라면 파일 소유자에게 연락하여 다시 공유해 달라고 요청하십시오.", - "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "안녕하세요,\n\n시스템 관리자가 서버 측 암호화를 활성화하였습니다. 저장된 파일이 암호 '%s'으(로) 암호화되었습니다.\n\n웹 인터페이스에 로그인하여 개인 설정의 'ownCloud 기본 암호화 모듈'로 이동한 다음, '이전 로그인 암호' 필드에 위 암호를 입력하고 현재 로그인 암호로 변경하여 암호화 암호를 업데이트하십시오.\n\n", + "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "안녕하세요,\n\n시스템 관리자가 서버 측 암호화를 활성화했습니다. 저장된 파일이 암호 '%s'으(로) 암호화되었습니다.\n\n웹 인터페이스에 로그인하여 개인 설정의 'ownCloud 기본 암호화 모듈'로 이동한 다음, '이전 로그인 암호' 필드에 위 암호를 입력하고 현재 로그인 암호로 변경하여 암호화 암호를 업데이트하십시오.\n\n", "The share will expire on %s." : "이 공유는 %s 까지 유지됩니다.", "Cheers!" : "감사합니다!", - "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "안녕하세요,<br><br>시스템 관리자가 서버 측 암호화를 활성화하였습니다. 저장된 파일이 암호 <strong>%s</strong>으(로) 암호화되었습니다.<br><br>웹 인터페이스에 로그인하여 개인 설정의 'ownCloud 기본 암호화 모듈'로 이동한 다음, '이전 로그인 암호' 필드에 위 암호를 입력하고 현재 로그인 암호로 변경하여 암호화 암호를 업데이트하십시오.<br><br>", + "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "안녕하세요,<br><br>시스템 관리자가 서버 측 암호화를 활성화했습니다. 저장된 파일이 암호 <strong>%s</strong>으(로) 암호화되었습니다.<br><br>웹 인터페이스에 로그인하여 개인 설정의 'ownCloud 기본 암호화 모듈'로 이동한 다음, '이전 로그인 암호' 필드에 위 암호를 입력하고 현재 로그인 암호로 변경하여 암호화 암호를 업데이트하십시오.<br><br>", "Enable recovery key" : "복구 키 활성화", "Disable recovery key" : "복구 키 비활성화", "The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user's files if the user forgets his or her password." : "복구 키는 파일을 암호화하는 추가 키입니다. 사용자가 암호를 잊었을 때 복구할 수 있도록 해 줍니다.", diff --git a/apps/encryption/l10n/ko.json b/apps/encryption/l10n/ko.json index a6843bdda45..88eab7837b8 100644 --- a/apps/encryption/l10n/ko.json +++ b/apps/encryption/l10n/ko.json @@ -26,10 +26,10 @@ "one-time password for server-side-encryption" : "서버 측 암호화용 일회용 암호", "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "이 파일을 복호화할 수 없습니다. 공유된 파일일 수도 있습니다. 파일 소유자에게 공유를 다시 요청하십시오.", "Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "이 파일을 읽을 수 없습니다. 공유된 파일이라면 파일 소유자에게 연락하여 다시 공유해 달라고 요청하십시오.", - "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "안녕하세요,\n\n시스템 관리자가 서버 측 암호화를 활성화하였습니다. 저장된 파일이 암호 '%s'으(로) 암호화되었습니다.\n\n웹 인터페이스에 로그인하여 개인 설정의 'ownCloud 기본 암호화 모듈'로 이동한 다음, '이전 로그인 암호' 필드에 위 암호를 입력하고 현재 로그인 암호로 변경하여 암호화 암호를 업데이트하십시오.\n\n", + "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "안녕하세요,\n\n시스템 관리자가 서버 측 암호화를 활성화했습니다. 저장된 파일이 암호 '%s'으(로) 암호화되었습니다.\n\n웹 인터페이스에 로그인하여 개인 설정의 'ownCloud 기본 암호화 모듈'로 이동한 다음, '이전 로그인 암호' 필드에 위 암호를 입력하고 현재 로그인 암호로 변경하여 암호화 암호를 업데이트하십시오.\n\n", "The share will expire on %s." : "이 공유는 %s 까지 유지됩니다.", "Cheers!" : "감사합니다!", - "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "안녕하세요,<br><br>시스템 관리자가 서버 측 암호화를 활성화하였습니다. 저장된 파일이 암호 <strong>%s</strong>으(로) 암호화되었습니다.<br><br>웹 인터페이스에 로그인하여 개인 설정의 'ownCloud 기본 암호화 모듈'로 이동한 다음, '이전 로그인 암호' 필드에 위 암호를 입력하고 현재 로그인 암호로 변경하여 암호화 암호를 업데이트하십시오.<br><br>", + "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "안녕하세요,<br><br>시스템 관리자가 서버 측 암호화를 활성화했습니다. 저장된 파일이 암호 <strong>%s</strong>으(로) 암호화되었습니다.<br><br>웹 인터페이스에 로그인하여 개인 설정의 'ownCloud 기본 암호화 모듈'로 이동한 다음, '이전 로그인 암호' 필드에 위 암호를 입력하고 현재 로그인 암호로 변경하여 암호화 암호를 업데이트하십시오.<br><br>", "Enable recovery key" : "복구 키 활성화", "Disable recovery key" : "복구 키 비활성화", "The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user's files if the user forgets his or her password." : "복구 키는 파일을 암호화하는 추가 키입니다. 사용자가 암호를 잊었을 때 복구할 수 있도록 해 줍니다.", diff --git a/apps/encryption/l10n/lt_LT.js b/apps/encryption/l10n/lt_LT.js index 627be83860b..a27747055dd 100644 --- a/apps/encryption/l10n/lt_LT.js +++ b/apps/encryption/l10n/lt_LT.js @@ -1,21 +1,46 @@ OC.L10N.register( "encryption", { + "Missing recovery key password" : "Nėra atstatymo rakto slaptažodžio", + "Please repeat the recovery key password" : "Pakartokite atstatymo rakto slaptažodį", + "Repeated recovery key password does not match the provided recovery key password" : "Pakartotas atstatymo rakto slaptažodis nesutampa su atstatymo rakto slaptažodžiu", "Recovery key successfully enabled" : "Atkūrimo raktas sėkmingai įjungtas", "Could not enable recovery key. Please check your recovery key password!" : "Neišėjo įjungti jūsų atkūrimo rakto. Prašome jį patikrinti!", "Recovery key successfully disabled" : "Atkūrimo raktas sėkmingai išjungtas", "Could not disable recovery key. Please check your recovery key password!" : "Neišėjo išjungti jūsų atkūrimo rakto. Prašome jį patikrinti!", + "Missing parameters" : "Trūksta parametrų", + "Please provide the old recovery password" : "Įveskite seną atstatymo slaptažodį", + "Please provide a new recovery password" : "Įveskite naują atstatymo slaptažodį", + "Please repeat the new recovery password" : "Pakartokite naują atstatymo slaptažodį", "Password successfully changed." : "Slaptažodis sėkmingai pakeistas", "Could not change the password. Maybe the old password was not correct." : "Slaptažodis nebuvo pakeistas. Gali būti, kad buvo neteisingai suvestas senasis.", + "Recovery Key disabled" : "Atstatymo raktas išjungtas", + "Recovery Key enabled" : "Atstatymo raktas įjungtas", + "Could not enable the recovery key, please try again or contact your administrator" : "Nepavyko įjungti atstatymo rakto, bandykite dar kartą arba susisiekite su administratoriumi", + "Could not update the private key password." : "Nepavyko atnaujinti privataus rakto slaptažodžio.", + "The old password was not correct, please try again." : "Neteisingas senas slaptažodis, pakartokite.", + "The current log-in password was not correct, please try again." : "Esamas prisijungimo slaptažodis neteisingas, bandykite dar kartą.", "Private key password successfully updated." : "Privataus rakto slaptažodis buvo sėkmingai atnaujintas.", + "You need to migrate your encryption keys from the old encryption (ownCloud <= 8.0) to the new one. Please run 'occ encryption:migrate' or contact your administrator" : "Reikalinga šifravimo raktų migracija iš senos versijos ( ownCloud <= 8.0) į naują. Įvykdykite komanda 'occ encryption:migrate' arba susisiekite su adminstratoriumi", "Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." : "Netinkamas privatus raktas Šifravimo programai. Prašome atnaujinti savo privataus rakto slaptažodį asmeniniuose nustatymuose, kad atkurti prieigą prie šifruotų failų.", "Encryption App is enabled but your keys are not initialized, please log-out and log-in again" : "Šifravimo programa įjungta, bet Jūsų raktai nėra pritaikyti. Prašome atsijungti ir vėl prisijungti", + "Encryption App is enabled and ready" : "Šifravimo programėlė įjungta ir veikia", + "one-time password for server-side-encryption" : "Vienkartinis slaptažodis šifravimui serverio pusėje", "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Failo iššifruoti nepavyko, gali būti jog jis yra pasidalintas su jumis. Paprašykite failo savininko, kad jums iš naujo pateiktų šį failą.", "The share will expire on %s." : "Bendrinimo laikas baigsis %s.", "Cheers!" : "Sveikinimai!", + "Enable recovery key" : "Įjungti atstatymo raktą", + "Disable recovery key" : "Išjungti atstatymo raktą", "Recovery key password" : "Atkūrimo rakto slaptažodis", + "Repeat recovery key password" : "Pakartokite atstatymo rakto slaptažodį", "Change recovery key password:" : "Pakeisti atkūrimo rakto slaptažodį:", + "Old recovery key password" : "Senas atstatymo rakto slaptažodis", + "New recovery key password" : "Naujas atstatymo rakto slaptažodis", + "Repeat new recovery key password" : "Pakartokite naują atstatymo rakto slaptažodį", "Change Password" : "Pakeisti slaptažodį", + "ownCloud basic encryption module" : "ownCloud bazinis šifravimo modulis", + "Your private key password no longer matches your log-in password." : "Privataus rakto slaptažodis nebe sutampa su prisijungimo slaptažodžiu.", + "Set your old private key password to your current log-in password:" : "Nustatyti Jūsų privataus rakto slaptažodį į Jūsų dabartinį slaptažodį.", " If you don't remember your old password you can ask your administrator to recover your files." : "Jei nepamenate savo seno slaptažodžio, galite paprašyti administratoriaus atkurti Jūsų failus.", "Old log-in password" : "Senas prisijungimo slaptažodis", "Current log-in password" : "Dabartinis prisijungimo slaptažodis", diff --git a/apps/encryption/l10n/lt_LT.json b/apps/encryption/l10n/lt_LT.json index 7a38dca5a97..b063eba7539 100644 --- a/apps/encryption/l10n/lt_LT.json +++ b/apps/encryption/l10n/lt_LT.json @@ -1,19 +1,44 @@ { "translations": { + "Missing recovery key password" : "Nėra atstatymo rakto slaptažodžio", + "Please repeat the recovery key password" : "Pakartokite atstatymo rakto slaptažodį", + "Repeated recovery key password does not match the provided recovery key password" : "Pakartotas atstatymo rakto slaptažodis nesutampa su atstatymo rakto slaptažodžiu", "Recovery key successfully enabled" : "Atkūrimo raktas sėkmingai įjungtas", "Could not enable recovery key. Please check your recovery key password!" : "Neišėjo įjungti jūsų atkūrimo rakto. Prašome jį patikrinti!", "Recovery key successfully disabled" : "Atkūrimo raktas sėkmingai išjungtas", "Could not disable recovery key. Please check your recovery key password!" : "Neišėjo išjungti jūsų atkūrimo rakto. Prašome jį patikrinti!", + "Missing parameters" : "Trūksta parametrų", + "Please provide the old recovery password" : "Įveskite seną atstatymo slaptažodį", + "Please provide a new recovery password" : "Įveskite naują atstatymo slaptažodį", + "Please repeat the new recovery password" : "Pakartokite naują atstatymo slaptažodį", "Password successfully changed." : "Slaptažodis sėkmingai pakeistas", "Could not change the password. Maybe the old password was not correct." : "Slaptažodis nebuvo pakeistas. Gali būti, kad buvo neteisingai suvestas senasis.", + "Recovery Key disabled" : "Atstatymo raktas išjungtas", + "Recovery Key enabled" : "Atstatymo raktas įjungtas", + "Could not enable the recovery key, please try again or contact your administrator" : "Nepavyko įjungti atstatymo rakto, bandykite dar kartą arba susisiekite su administratoriumi", + "Could not update the private key password." : "Nepavyko atnaujinti privataus rakto slaptažodžio.", + "The old password was not correct, please try again." : "Neteisingas senas slaptažodis, pakartokite.", + "The current log-in password was not correct, please try again." : "Esamas prisijungimo slaptažodis neteisingas, bandykite dar kartą.", "Private key password successfully updated." : "Privataus rakto slaptažodis buvo sėkmingai atnaujintas.", + "You need to migrate your encryption keys from the old encryption (ownCloud <= 8.0) to the new one. Please run 'occ encryption:migrate' or contact your administrator" : "Reikalinga šifravimo raktų migracija iš senos versijos ( ownCloud <= 8.0) į naują. Įvykdykite komanda 'occ encryption:migrate' arba susisiekite su adminstratoriumi", "Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." : "Netinkamas privatus raktas Šifravimo programai. Prašome atnaujinti savo privataus rakto slaptažodį asmeniniuose nustatymuose, kad atkurti prieigą prie šifruotų failų.", "Encryption App is enabled but your keys are not initialized, please log-out and log-in again" : "Šifravimo programa įjungta, bet Jūsų raktai nėra pritaikyti. Prašome atsijungti ir vėl prisijungti", + "Encryption App is enabled and ready" : "Šifravimo programėlė įjungta ir veikia", + "one-time password for server-side-encryption" : "Vienkartinis slaptažodis šifravimui serverio pusėje", "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Failo iššifruoti nepavyko, gali būti jog jis yra pasidalintas su jumis. Paprašykite failo savininko, kad jums iš naujo pateiktų šį failą.", "The share will expire on %s." : "Bendrinimo laikas baigsis %s.", "Cheers!" : "Sveikinimai!", + "Enable recovery key" : "Įjungti atstatymo raktą", + "Disable recovery key" : "Išjungti atstatymo raktą", "Recovery key password" : "Atkūrimo rakto slaptažodis", + "Repeat recovery key password" : "Pakartokite atstatymo rakto slaptažodį", "Change recovery key password:" : "Pakeisti atkūrimo rakto slaptažodį:", + "Old recovery key password" : "Senas atstatymo rakto slaptažodis", + "New recovery key password" : "Naujas atstatymo rakto slaptažodis", + "Repeat new recovery key password" : "Pakartokite naują atstatymo rakto slaptažodį", "Change Password" : "Pakeisti slaptažodį", + "ownCloud basic encryption module" : "ownCloud bazinis šifravimo modulis", + "Your private key password no longer matches your log-in password." : "Privataus rakto slaptažodis nebe sutampa su prisijungimo slaptažodžiu.", + "Set your old private key password to your current log-in password:" : "Nustatyti Jūsų privataus rakto slaptažodį į Jūsų dabartinį slaptažodį.", " If you don't remember your old password you can ask your administrator to recover your files." : "Jei nepamenate savo seno slaptažodžio, galite paprašyti administratoriaus atkurti Jūsų failus.", "Old log-in password" : "Senas prisijungimo slaptažodis", "Current log-in password" : "Dabartinis prisijungimo slaptažodis", diff --git a/apps/encryption/l10n/sq.js b/apps/encryption/l10n/sq.js index 9a9230e86fb..d18e8bcbbfd 100644 --- a/apps/encryption/l10n/sq.js +++ b/apps/encryption/l10n/sq.js @@ -1,9 +1,57 @@ OC.L10N.register( "encryption", { - "Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." : "Çelësi privat për Aplikacionin e Shifrimit është i pavlefshëm. Ju lutem përditësoni fjalëkalimin e çelësit tuaj privat në parametrat tuaj për të rimarrë qasje në skedarët tuaj të shifruar.", - "The share will expire on %s." : "Ndarja do të skadojë në %s.", - "Cheers!" : "Gjithë të mirat", - "Enabled" : "Aktivizuar" + "Missing recovery key password" : "Mungon fjalëkalim kyçi rimarrjesh", + "Please repeat the recovery key password" : "Ju lutemi, rijepni fjalëkalimin për kyç rimarrjesh", + "Repeated recovery key password does not match the provided recovery key password" : "Fjalëkalimi i ridhënë për kyç rimarrjesh s’përputhet me fjalëkalimin e dhënë për kyç rimarrjesh", + "Recovery key successfully enabled" : "Kyçi i rimarrjeve u aktivizua me sukses", + "Could not enable recovery key. Please check your recovery key password!" : "S’u aktivizua dot kyçi i rimarrjeve. Ju lutemi, kontrolloni fjalëkalimin për kyç rimarrjesh!", + "Recovery key successfully disabled" : "Kyçi i rimarrjeve u çaktivizua me sukses", + "Could not disable recovery key. Please check your recovery key password!" : "S’u çaktivizua dot kyçi i rimarrjeve. Ju lutemi, kontrolloni fjalëkalimin për kyç rimarrjesh!", + "Missing parameters" : "Mungojnë parametra", + "Please provide the old recovery password" : "Ju lutemi, jepni fjalëkalimin e vjetër të rimarrjes", + "Please provide a new recovery password" : "Ju lutemi, jepni fjalëkalimin e ri të rimarrjes", + "Please repeat the new recovery password" : "Ju lutemi, rijepni fjalëkalimin e ri të rimarrjes", + "Password successfully changed." : "Fjalëkalimi u ndryshua me sukses.", + "Could not change the password. Maybe the old password was not correct." : "Fjalëkalimi s’u ndryshua dot. Ndoshta fjalëkalimi i vjetër s’qe i saktë.", + "Recovery Key disabled" : "Kyçi i Rimarrjeve u çaktivizua", + "Recovery Key enabled" : "Kyçi i Rimarrjeve u aktivizua", + "Could not enable the recovery key, please try again or contact your administrator" : "S’u aktivizua dot kyçi i rimarrjeve. ju lutemi, riprovoni ose lidhuni me përgjegjësin tuaj", + "Could not update the private key password." : "Fjalëkalimi për kyçin privat s’u përditësua dot.", + "The old password was not correct, please try again." : "Fjalëkalimi i vjetër s’qe i saktë, ju lutemi, riprovoni.", + "The current log-in password was not correct, please try again." : "Fjalëkalimi i tanishëm i hyrjeve s’qe i saktë, ju lutemi, riprovoni.", + "Private key password successfully updated." : "Fjalëkalimi për kyçin privat u përditësua me sukses.", + "You need to migrate your encryption keys from the old encryption (ownCloud <= 8.0) to the new one. Please run 'occ encryption:migrate' or contact your administrator" : "Lypset të kaloni kyçet tuaj të fshehtëzimeve nga versioni i vjetër i fshehtëzimeve (ownCloud <= 8.0) te i riu. Ju lutemi, ekzekutoni run 'occ encryption:migrate' ose lidhuni me përgjegjësin tuaj", + "Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." : "Kyç privat i pavlefshëm për aplikacionin e fshehtëzimeve. Ju lutemi, përditësoni fjalëkalimin tuaj të kyçit privat te rregullimet tuaja personale që të rimerrni hyrje te kartelat tuaja të fshehtëzuara.", + "Encryption App is enabled but your keys are not initialized, please log-out and log-in again" : "Aplikacioni i fshehtëzimeve është i aktivizuar, por kyçet tuaj s’janë vënë në punë, ju lutemi, dilni dhe ribëni hyrjen", + "Encryption App is enabled and ready" : "Aplikacioni i Fshehtëzimeve u aktivizua dhe është gati", + "one-time password for server-side-encryption" : "fjalëkalim vetëm për një herë, për fshehtëzim-më-anë-shërbyesi", + "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Nuk shfshehtëzohet dot kjo kartelë, ndoshta është kartelë e ndarë me të tjerët. Ju lutemi, kërkojini të zotit të kartelës ta rindajë kartelën me ju.", + "Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "S’lexohet dot kjo kartelë, ndoshta është kartelë e ndarë me të tjerët. Ju lutemi, kërkojini të zotit të kartelës ta rindajë kartelën me ju.", + "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Njatjeta,\n\npërgjegjësi aktivizoi fshehtëzim më anë shërbyesi. Kartelat tuaja qenë fshehtëzuar duke përdorur fjalëkalimin '%s'.\n\nJu lutemi, bëni hyrjen te ndërfaqja web, kaloni te ndarja 'modul i thjeshtë ownCloud për fshehtëzime' e rregullimeve tuaja personale dhe përditësoni fjalëkalimin tuaj për fshehtëzime duke dhënë këtë fjalëkalim te fusha 'old log-in password' dhe fjalëkalimin tuaj të tanishëm për hyrjet.\n\n", + "The share will expire on %s." : "Ndarja do të skadojë më %s.", + "Cheers!" : "Gëzuar!", + "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Njatjeta,<br><br>përgjegjësi aktivizoi fshehtëzim më anë shërbyesi. Kartelat tuaja qenë fshehtëzuar duke përdorur fjalëkalimin <strong>%s</strong>.<br><br>Ju lutemi, bëni hyrjen te ndërfaqja web, kaloni te ndarja \"modul i thjeshtë ownCloud për fshehtëzime\" e rregullimeve tuaja personale dhe përditësoni fjalëkalimin tuaj për fshehtëzime duke dhënë këtë fjalëkalim te fusha \"old log-in password\" dhe fjalëkalimin tuaj të tanishëm për hyrjet.<br><br>", + "Enable recovery key" : "Aktivizo kyç rimarrjesh", + "Disable recovery key" : "Çaktivizo kyç rimarrjesh", + "The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user's files if the user forgets his or her password." : "Kyçi i rimarrjeve është një kyç ekstra fshehtëzimesh që përdoret për të fshehtëzuar kartela. Ai lejon rimarrjen e një kartele të përdoruesit, nëse përdoruesi harron fjalëkalimin e vet.", + "Recovery key password" : "Fjalëkalim kyçi rimarrjesh", + "Repeat recovery key password" : "Rijepni fjalëkalim kyçi rimarrjesh", + "Change recovery key password:" : "Ndryshoni fjalëkalim kyçi rimarrjesh:", + "Old recovery key password" : "Fjalëkalimi i vjetër kyçi rimarrjesh", + "New recovery key password" : "Fjalëkalimi i ri kyçi rimarrjesh", + "Repeat new recovery key password" : "Rijepni fjalëkalimin e ri kyçi rimarrjesh", + "Change Password" : "Ndryshoni Fjalëkalimin", + "ownCloud basic encryption module" : "modul i thjeshtë ownCloud fshehtëzimesh", + "Your private key password no longer matches your log-in password." : "Fjalëkalimi juaj për kyçe privatë s’përputhet më me fjalëkalimin për hyrjet.", + "Set your old private key password to your current log-in password:" : "Fjalëkalimit të vjetër të kyçit privat jepini vlerën e fjalëkalimit tuaj të tanishëm për hyrjet:", + " If you don't remember your old password you can ask your administrator to recover your files." : " Nëse s’e mbani mend fjalëkalimin tuaj të vjetër, mund t’i kërkoni përgjegjësit tuaj të rimarrë kartelat tuaja.", + "Old log-in password" : "Fjalëkalimi i vjetër për hyrjet", + "Current log-in password" : "Fjalëkalimi i tanishëm për hyrjet", + "Update Private Key Password" : "Përditësoni Fjalëkalim Kyçi Privat", + "Enable password recovery:" : "Aktivizo rimarrje fjalëkalimesh:", + "Enabling this option will allow you to reobtain access to your encrypted files in case of password loss" : "Aktivizimi i kësaj mundësie do t’ju lejojë të rifitoni hyrje te kartelat tuaja të fshehtëzuara në rast humbjeje fjalëkalimi", + "Enabled" : "E aktivizuar", + "Disabled" : "E çaktivizuar" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/encryption/l10n/sq.json b/apps/encryption/l10n/sq.json index 87481aa3349..7415195f955 100644 --- a/apps/encryption/l10n/sq.json +++ b/apps/encryption/l10n/sq.json @@ -1,7 +1,55 @@ { "translations": { - "Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." : "Çelësi privat për Aplikacionin e Shifrimit është i pavlefshëm. Ju lutem përditësoni fjalëkalimin e çelësit tuaj privat në parametrat tuaj për të rimarrë qasje në skedarët tuaj të shifruar.", - "The share will expire on %s." : "Ndarja do të skadojë në %s.", - "Cheers!" : "Gjithë të mirat", - "Enabled" : "Aktivizuar" + "Missing recovery key password" : "Mungon fjalëkalim kyçi rimarrjesh", + "Please repeat the recovery key password" : "Ju lutemi, rijepni fjalëkalimin për kyç rimarrjesh", + "Repeated recovery key password does not match the provided recovery key password" : "Fjalëkalimi i ridhënë për kyç rimarrjesh s’përputhet me fjalëkalimin e dhënë për kyç rimarrjesh", + "Recovery key successfully enabled" : "Kyçi i rimarrjeve u aktivizua me sukses", + "Could not enable recovery key. Please check your recovery key password!" : "S’u aktivizua dot kyçi i rimarrjeve. Ju lutemi, kontrolloni fjalëkalimin për kyç rimarrjesh!", + "Recovery key successfully disabled" : "Kyçi i rimarrjeve u çaktivizua me sukses", + "Could not disable recovery key. Please check your recovery key password!" : "S’u çaktivizua dot kyçi i rimarrjeve. Ju lutemi, kontrolloni fjalëkalimin për kyç rimarrjesh!", + "Missing parameters" : "Mungojnë parametra", + "Please provide the old recovery password" : "Ju lutemi, jepni fjalëkalimin e vjetër të rimarrjes", + "Please provide a new recovery password" : "Ju lutemi, jepni fjalëkalimin e ri të rimarrjes", + "Please repeat the new recovery password" : "Ju lutemi, rijepni fjalëkalimin e ri të rimarrjes", + "Password successfully changed." : "Fjalëkalimi u ndryshua me sukses.", + "Could not change the password. Maybe the old password was not correct." : "Fjalëkalimi s’u ndryshua dot. Ndoshta fjalëkalimi i vjetër s’qe i saktë.", + "Recovery Key disabled" : "Kyçi i Rimarrjeve u çaktivizua", + "Recovery Key enabled" : "Kyçi i Rimarrjeve u aktivizua", + "Could not enable the recovery key, please try again or contact your administrator" : "S’u aktivizua dot kyçi i rimarrjeve. ju lutemi, riprovoni ose lidhuni me përgjegjësin tuaj", + "Could not update the private key password." : "Fjalëkalimi për kyçin privat s’u përditësua dot.", + "The old password was not correct, please try again." : "Fjalëkalimi i vjetër s’qe i saktë, ju lutemi, riprovoni.", + "The current log-in password was not correct, please try again." : "Fjalëkalimi i tanishëm i hyrjeve s’qe i saktë, ju lutemi, riprovoni.", + "Private key password successfully updated." : "Fjalëkalimi për kyçin privat u përditësua me sukses.", + "You need to migrate your encryption keys from the old encryption (ownCloud <= 8.0) to the new one. Please run 'occ encryption:migrate' or contact your administrator" : "Lypset të kaloni kyçet tuaj të fshehtëzimeve nga versioni i vjetër i fshehtëzimeve (ownCloud <= 8.0) te i riu. Ju lutemi, ekzekutoni run 'occ encryption:migrate' ose lidhuni me përgjegjësin tuaj", + "Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." : "Kyç privat i pavlefshëm për aplikacionin e fshehtëzimeve. Ju lutemi, përditësoni fjalëkalimin tuaj të kyçit privat te rregullimet tuaja personale që të rimerrni hyrje te kartelat tuaja të fshehtëzuara.", + "Encryption App is enabled but your keys are not initialized, please log-out and log-in again" : "Aplikacioni i fshehtëzimeve është i aktivizuar, por kyçet tuaj s’janë vënë në punë, ju lutemi, dilni dhe ribëni hyrjen", + "Encryption App is enabled and ready" : "Aplikacioni i Fshehtëzimeve u aktivizua dhe është gati", + "one-time password for server-side-encryption" : "fjalëkalim vetëm për një herë, për fshehtëzim-më-anë-shërbyesi", + "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Nuk shfshehtëzohet dot kjo kartelë, ndoshta është kartelë e ndarë me të tjerët. Ju lutemi, kërkojini të zotit të kartelës ta rindajë kartelën me ju.", + "Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "S’lexohet dot kjo kartelë, ndoshta është kartelë e ndarë me të tjerët. Ju lutemi, kërkojini të zotit të kartelës ta rindajë kartelën me ju.", + "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Njatjeta,\n\npërgjegjësi aktivizoi fshehtëzim më anë shërbyesi. Kartelat tuaja qenë fshehtëzuar duke përdorur fjalëkalimin '%s'.\n\nJu lutemi, bëni hyrjen te ndërfaqja web, kaloni te ndarja 'modul i thjeshtë ownCloud për fshehtëzime' e rregullimeve tuaja personale dhe përditësoni fjalëkalimin tuaj për fshehtëzime duke dhënë këtë fjalëkalim te fusha 'old log-in password' dhe fjalëkalimin tuaj të tanishëm për hyrjet.\n\n", + "The share will expire on %s." : "Ndarja do të skadojë më %s.", + "Cheers!" : "Gëzuar!", + "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Njatjeta,<br><br>përgjegjësi aktivizoi fshehtëzim më anë shërbyesi. Kartelat tuaja qenë fshehtëzuar duke përdorur fjalëkalimin <strong>%s</strong>.<br><br>Ju lutemi, bëni hyrjen te ndërfaqja web, kaloni te ndarja \"modul i thjeshtë ownCloud për fshehtëzime\" e rregullimeve tuaja personale dhe përditësoni fjalëkalimin tuaj për fshehtëzime duke dhënë këtë fjalëkalim te fusha \"old log-in password\" dhe fjalëkalimin tuaj të tanishëm për hyrjet.<br><br>", + "Enable recovery key" : "Aktivizo kyç rimarrjesh", + "Disable recovery key" : "Çaktivizo kyç rimarrjesh", + "The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user's files if the user forgets his or her password." : "Kyçi i rimarrjeve është një kyç ekstra fshehtëzimesh që përdoret për të fshehtëzuar kartela. Ai lejon rimarrjen e një kartele të përdoruesit, nëse përdoruesi harron fjalëkalimin e vet.", + "Recovery key password" : "Fjalëkalim kyçi rimarrjesh", + "Repeat recovery key password" : "Rijepni fjalëkalim kyçi rimarrjesh", + "Change recovery key password:" : "Ndryshoni fjalëkalim kyçi rimarrjesh:", + "Old recovery key password" : "Fjalëkalimi i vjetër kyçi rimarrjesh", + "New recovery key password" : "Fjalëkalimi i ri kyçi rimarrjesh", + "Repeat new recovery key password" : "Rijepni fjalëkalimin e ri kyçi rimarrjesh", + "Change Password" : "Ndryshoni Fjalëkalimin", + "ownCloud basic encryption module" : "modul i thjeshtë ownCloud fshehtëzimesh", + "Your private key password no longer matches your log-in password." : "Fjalëkalimi juaj për kyçe privatë s’përputhet më me fjalëkalimin për hyrjet.", + "Set your old private key password to your current log-in password:" : "Fjalëkalimit të vjetër të kyçit privat jepini vlerën e fjalëkalimit tuaj të tanishëm për hyrjet:", + " If you don't remember your old password you can ask your administrator to recover your files." : " Nëse s’e mbani mend fjalëkalimin tuaj të vjetër, mund t’i kërkoni përgjegjësit tuaj të rimarrë kartelat tuaja.", + "Old log-in password" : "Fjalëkalimi i vjetër për hyrjet", + "Current log-in password" : "Fjalëkalimi i tanishëm për hyrjet", + "Update Private Key Password" : "Përditësoni Fjalëkalim Kyçi Privat", + "Enable password recovery:" : "Aktivizo rimarrje fjalëkalimesh:", + "Enabling this option will allow you to reobtain access to your encrypted files in case of password loss" : "Aktivizimi i kësaj mundësie do t’ju lejojë të rifitoni hyrje te kartelat tuaja të fshehtëzuara në rast humbjeje fjalëkalimi", + "Enabled" : "E aktivizuar", + "Disabled" : "E çaktivizuar" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/encryption/lib/crypto/crypt.php b/apps/encryption/lib/crypto/crypt.php index c55fb00f838..c0dcc936bdf 100644 --- a/apps/encryption/lib/crypto/crypt.php +++ b/apps/encryption/lib/crypto/crypt.php @@ -2,6 +2,7 @@ /** * @author Björn Schießle <schiessle@owncloud.com> * @author Clark Tomlinson <fallen013@gmail.com> + * @author Joas Schilling <nickvergessen@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @@ -53,7 +54,7 @@ class Crypt { */ private $logger; /** - * @var IUser + * @var string */ private $user; /** @@ -73,7 +74,7 @@ class Crypt { */ public function __construct(ILogger $logger, IUserSession $userSession, IConfig $config) { $this->logger = $logger; - $this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser() : false; + $this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : '"no user given"'; $this->config = $config; $this->supportedKeyFormats = ['hash', 'password']; } @@ -89,7 +90,7 @@ class Crypt { $res = $this->getOpenSSLPKey(); if (!$res) { - $log->error("Encryption Library couldn't generate users key-pair for {$this->user->getUID()}", + $log->error("Encryption Library couldn't generate users key-pair for {$this->user}", ['app' => 'encryption']); if (openssl_error_string()) { @@ -108,7 +109,7 @@ class Crypt { 'privateKey' => $privateKey ]; } - $log->error('Encryption library couldn\'t export users private key, please check your servers OpenSSL configuration.' . $this->user->getUID(), + $log->error('Encryption library couldn\'t export users private key, please check your servers OpenSSL configuration.' . $this->user, ['app' => 'encryption']); if (openssl_error_string()) { $log->error('Encryption Library:' . openssl_error_string(), diff --git a/apps/encryption/lib/crypto/encryption.php b/apps/encryption/lib/crypto/encryption.php index 1a05277e20d..d1140ce7cde 100644 --- a/apps/encryption/lib/crypto/encryption.php +++ b/apps/encryption/lib/crypto/encryption.php @@ -378,6 +378,12 @@ class Encryption implements IEncryptionModule { * @return boolean */ public function shouldEncrypt($path) { + if ($this->util->shouldEncryptHomeStorage() === false) { + $storage = $this->util->getStorage($path); + if ($storage->instanceOfStorage('\OCP\Files\IHomeStorage')) { + return false; + } + } $parts = explode('/', $path); if (count($parts) < 4) { return false; diff --git a/apps/encryption/lib/util.php b/apps/encryption/lib/util.php index a162dcde305..62c9dc6dc5f 100644 --- a/apps/encryption/lib/util.php +++ b/apps/encryption/lib/util.php @@ -94,12 +94,41 @@ class Util { $recoveryMode = $this->config->getUserValue($uid, 'encryption', 'recoveryEnabled', - 0); + '0'); return ($recoveryMode === '1'); } /** + * check if the home storage should be encrypted + * + * @return bool + */ + public function shouldEncryptHomeStorage() { + $encryptHomeStorage = $this->config->getAppValue( + 'encryption', + 'encryptHomeStorage', + '1' + ); + + return ($encryptHomeStorage === '1'); + } + + /** + * check if the home storage should be encrypted + * + * @param bool $encryptHomeStorage + */ + public function setEncryptHomeStorage($encryptHomeStorage) { + $value = $encryptHomeStorage ? '1' : '0'; + $this->config->setAppValue( + 'encryption', + 'encryptHomeStorage', + $value + ); + } + + /** * check if master key is enabled * * @return bool @@ -157,4 +186,15 @@ class Util { return $owner; } + /** + * get storage of path + * + * @param string $path + * @return \OC\Files\Storage\Storage + */ + public function getStorage($path) { + $storage = $this->files->getMount($path)->getStorage(); + return $storage; + } + } diff --git a/apps/encryption/settings/settings-admin.php b/apps/encryption/settings/settings-admin.php index c7ac8c09c6b..8d55d587fed 100644 --- a/apps/encryption/settings/settings-admin.php +++ b/apps/encryption/settings/settings-admin.php @@ -25,12 +25,27 @@ $tmpl = new OCP\Template('encryption', 'settings-admin'); +$crypt = new \OCA\Encryption\Crypto\Crypt( + \OC::$server->getLogger(), + \OC::$server->getUserSession(), + \OC::$server->getConfig()); + +$util = new \OCA\Encryption\Util( + new \OC\Files\View(), + $crypt, + \OC::$server->getLogger(), + \OC::$server->getUserSession(), + \OC::$server->getConfig(), + \OC::$server->getUserManager()); + // Check if an adminRecovery account is enabled for recovering files after lost pwd $recoveryAdminEnabled = \OC::$server->getConfig()->getAppValue('encryption', 'recoveryAdminEnabled', '0'); $session = new \OCA\Encryption\Session(\OC::$server->getSession()); +$encryptHomeStorage = $util->shouldEncryptHomeStorage($user); $tmpl->assign('recoveryEnabled', $recoveryAdminEnabled); $tmpl->assign('initStatus', $session->getStatus()); +$tmpl->assign('encryptHomeStorage', $encryptHomeStorage); return $tmpl->fetchPage(); diff --git a/apps/encryption/templates/settings-admin.php b/apps/encryption/templates/settings-admin.php index 81c7f0607d8..e55aba6757c 100644 --- a/apps/encryption/templates/settings-admin.php +++ b/apps/encryption/templates/settings-admin.php @@ -9,56 +9,63 @@ style('encryption', 'settings-admin'); <?php if(!$_["initStatus"]): ?> <?php p($l->t("Encryption App is enabled but your keys are not initialized, please log-out and log-in again")); ?> <?php else: ?> - <p id="encryptionSetRecoveryKey"> - <?php $_["recoveryEnabled"] === '0' ? p($l->t("Enable recovery key")) : p($l->t("Disable recovery key")); ?> - <span class="msg"></span> - <br/> - <em> - <?php p($l->t("The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user's files if the user forgets his or her password.")) ?> - </em> - <br/> - <input type="password" - name="encryptionRecoveryPassword" - id="encryptionRecoveryPassword" - placeholder="<?php p($l->t("Recovery key password")); ?>"/> - <input type="password" - name="encryptionRecoveryPassword" - id="repeatEncryptionRecoveryPassword" - placeholder="<?php p($l->t("Repeat recovery key password")); ?>"/> - <input type="button" - name="enableRecoveryKey" - id="enableRecoveryKey" - status="<?php p($_["recoveryEnabled"]) ?>" - value="<?php $_["recoveryEnabled"] === '0' ? p($l->t("Enable recovery key")) : p($l->t("Disable recovery key")); ?>"/> - </p> - <br/><br/> - - <p name="changeRecoveryPasswordBlock" id="encryptionChangeRecoveryKey" <?php if($_['recoveryEnabled'] === '0') print_unescaped('class="hidden"');?>> - <?php p($l->t("Change recovery key password:")); ?> - <span class="msg"></span> - <br/> - <input - type="password" - name="changeRecoveryPassword" - id="oldEncryptionRecoveryPassword" - placeholder="<?php p($l->t("Old recovery key password")); ?>"/> + <p id="encryptHomeStorageSetting"> + <input type="checkbox" class="checkbox" name="encrypt_home_storage" id="encryptHomeStorage" + value="1" <?php if ($_['encryptHomeStorage']) print_unescaped('checked="checked"'); ?> /> + <label for="encryptHomeStorage"><?php p($l->t('Encrypt the home storage'));?></label></br> + <em><?php p( $l->t( "Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" ) ); ?></em> + </p> <br /> - <input - type="password" - name="changeRecoveryPassword" - id="newEncryptionRecoveryPassword" - placeholder="<?php p($l->t("New recovery key password")); ?>"/> - <input - type="password" - name="changeRecoveryPassword" - id="repeatedNewEncryptionRecoveryPassword" - placeholder="<?php p($l->t("Repeat new recovery key password")); ?>"/> + <p id="encryptionSetRecoveryKey"> + <?php $_["recoveryEnabled"] === '0' ? p($l->t("Enable recovery key")) : p($l->t("Disable recovery key")); ?> + <span class="msg"></span> + <br/> + <em> + <?php p($l->t("The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user's files if the user forgets his or her password.")) ?> + </em> + <br/> + <input type="password" + name="encryptionRecoveryPassword" + id="encryptionRecoveryPassword" + placeholder="<?php p($l->t("Recovery key password")); ?>"/> + <input type="password" + name="encryptionRecoveryPassword" + id="repeatEncryptionRecoveryPassword" + placeholder="<?php p($l->t("Repeat recovery key password")); ?>"/> + <input type="button" + name="enableRecoveryKey" + id="enableRecoveryKey" + status="<?php p($_["recoveryEnabled"]) ?>" + value="<?php $_["recoveryEnabled"] === '0' ? p($l->t("Enable recovery key")) : p($l->t("Disable recovery key")); ?>"/> + </p> + <br/><br/> + + <p name="changeRecoveryPasswordBlock" id="encryptionChangeRecoveryKey" <?php if($_['recoveryEnabled'] === '0') print_unescaped('class="hidden"');?>> + <?php p($l->t("Change recovery key password:")); ?> + <span class="msg"></span> + <br/> + <input + type="password" + name="changeRecoveryPassword" + id="oldEncryptionRecoveryPassword" + placeholder="<?php p($l->t("Old recovery key password")); ?>"/> + <br /> + <input + type="password" + name="changeRecoveryPassword" + id="newEncryptionRecoveryPassword" + placeholder="<?php p($l->t("New recovery key password")); ?>"/> + <input + type="password" + name="changeRecoveryPassword" + id="repeatedNewEncryptionRecoveryPassword" + placeholder="<?php p($l->t("Repeat new recovery key password")); ?>"/> - <button - type="button" - name="submitChangeRecoveryKey"> + <button + type="button" + name="submitChangeRecoveryKey"> <?php p($l->t("Change Password")); ?> - </button> - </p> + </button> + </p> <?php endif; ?> </form> diff --git a/apps/encryption/tests/controller/SettingsControllerTest.php b/apps/encryption/tests/controller/SettingsControllerTest.php index 724a01522af..3b30e61a45b 100644 --- a/apps/encryption/tests/controller/SettingsControllerTest.php +++ b/apps/encryption/tests/controller/SettingsControllerTest.php @@ -56,6 +56,9 @@ class SettingsControllerTest extends TestCase { /** @var \PHPUnit_Framework_MockObject_MockObject */ private $ocSessionMock; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $utilMock; + protected function setUp() { parent::setUp(); @@ -106,6 +109,10 @@ class SettingsControllerTest extends TestCase { $this->sessionMock = $this->getMockBuilder('OCA\Encryption\Session') ->disableOriginalConstructor()->getMock(); + $this->utilMock = $this->getMockBuilder('OCA\Encryption\Util') + ->disableOriginalConstructor() + ->getMock(); + $this->controller = new SettingsController( 'encryption', $this->requestMock, @@ -115,7 +122,8 @@ class SettingsControllerTest extends TestCase { $this->keyManagerMock, $this->cryptMock, $this->sessionMock, - $this->ocSessionMock + $this->ocSessionMock, + $this->utilMock ); } @@ -234,4 +242,10 @@ class SettingsControllerTest extends TestCase { $data['message']); } + function testSetEncryptHomeStorage() { + $value = true; + $this->utilMock->expects($this->once())->method('setEncryptHomeStorage')->with($value); + $this->controller->setEncryptHomeStorage($value); + } + } diff --git a/apps/encryption/tests/lib/MigrationTest.php b/apps/encryption/tests/lib/MigrationTest.php index 6146e4e7cfb..65fefa262a7 100644 --- a/apps/encryption/tests/lib/MigrationTest.php +++ b/apps/encryption/tests/lib/MigrationTest.php @@ -2,6 +2,7 @@ /** * @author Björn Schießle <schiessle@owncloud.com> * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Robin Appelman <icewind@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 @@ -62,6 +63,8 @@ class MigrationTest extends \Test\TestCase { } protected function createDummyShareKeys($uid) { + $this->loginAsUser($uid); + $this->view->mkdir($uid . '/files_encryption/keys/folder1/folder2/folder3/file3'); $this->view->mkdir($uid . '/files_encryption/keys/folder1/folder2/file2'); $this->view->mkdir($uid . '/files_encryption/keys/folder1/file.1'); @@ -87,6 +90,8 @@ class MigrationTest extends \Test\TestCase { } protected function createDummyUserKeys($uid) { + $this->loginAsUser($uid); + $this->view->mkdir($uid . '/files_encryption/'); $this->view->mkdir('/files_encryption/public_keys'); $this->view->file_put_contents($uid . '/files_encryption/' . $uid . '.privateKey', 'privateKey'); @@ -94,6 +99,8 @@ class MigrationTest extends \Test\TestCase { } protected function createDummyFileKeys($uid) { + $this->loginAsUser($uid); + $this->view->mkdir($uid . '/files_encryption/keys/folder1/folder2/folder3/file3'); $this->view->mkdir($uid . '/files_encryption/keys/folder1/folder2/file2'); $this->view->mkdir($uid . '/files_encryption/keys/folder1/file.1'); @@ -105,6 +112,8 @@ class MigrationTest extends \Test\TestCase { } protected function createDummyFiles($uid) { + $this->loginAsUser($uid); + $this->view->mkdir($uid . '/files/folder1/folder2/folder3/file3'); $this->view->mkdir($uid . '/files/folder1/folder2/file2'); $this->view->mkdir($uid . '/files/folder1/file.1'); @@ -116,6 +125,8 @@ class MigrationTest extends \Test\TestCase { } protected function createDummyFilesInTrash($uid) { + $this->loginAsUser($uid); + $this->view->mkdir($uid . '/files_trashbin/keys/file1.d5457864'); $this->view->mkdir($uid . '/files_trashbin/keys/folder1.d7437648723/file2'); $this->view->file_put_contents($uid . '/files_trashbin/keys/file1.d5457864/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey' , 'data'); @@ -165,6 +176,7 @@ class MigrationTest extends \Test\TestCase { $this->createDummySystemWideKeys(); + /** @var \PHPUnit_Framework_MockObject_MockObject|\OCA\Encryption\Migration $m */ $m = $this->getMockBuilder('OCA\Encryption\Migration') ->setConstructorArgs( [ @@ -176,27 +188,38 @@ class MigrationTest extends \Test\TestCase { )->setMethods(['getSystemMountPoints'])->getMock(); $m->expects($this->any())->method('getSystemMountPoints') - ->willReturn([['mountpoint' => 'folder1'], ['mountpoint' => 'folder2']]); + ->will($this->returnValue([['mountpoint' => 'folder1'], ['mountpoint' => 'folder2']])); $m->reorganizeFolderStructure(); // even if it runs twice folder should always move only once $m->reorganizeFolderStructure(); + $this->loginAsUser(self::TEST_ENCRYPTION_MIGRATION_USER1); + $this->assertTrue( $this->view->file_exists( self::TEST_ENCRYPTION_MIGRATION_USER1 . '/files_encryption/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.publicKey') ); + + $this->loginAsUser(self::TEST_ENCRYPTION_MIGRATION_USER2); + $this->assertTrue( $this->view->file_exists( self::TEST_ENCRYPTION_MIGRATION_USER2 . '/files_encryption/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER2 . '.publicKey') ); + + $this->loginAsUser(self::TEST_ENCRYPTION_MIGRATION_USER3); + $this->assertTrue( $this->view->file_exists( self::TEST_ENCRYPTION_MIGRATION_USER3 . '/files_encryption/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER3 . '.publicKey') ); + + $this->loginAsUser(self::TEST_ENCRYPTION_MIGRATION_USER1); + $this->assertTrue( $this->view->file_exists( '/files_encryption/' . $this->moduleId . '/systemwide_1.publicKey') @@ -217,6 +240,8 @@ class MigrationTest extends \Test\TestCase { } protected function verifyFilesInTrash($uid) { + $this->loginAsUser($uid); + // share keys $this->assertTrue( $this->view->file_exists($uid . '/files_encryption/keys/files_trashbin/file1.d5457864/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey') @@ -244,6 +269,7 @@ class MigrationTest extends \Test\TestCase { protected function verifyNewKeyPath($uid) { // private key if ($uid !== '') { + $this->loginAsUser($uid); $this->assertTrue($this->view->file_exists($uid . '/files_encryption/' . $this->moduleId . '/'. $uid . '.privateKey')); } // file keys diff --git a/apps/encryption/tests/lib/UtilTest.php b/apps/encryption/tests/lib/UtilTest.php index 723cc9fb910..d55b6b50b3e 100644 --- a/apps/encryption/tests/lib/UtilTest.php +++ b/apps/encryption/tests/lib/UtilTest.php @@ -39,6 +39,9 @@ class UtilTest extends TestCase { /** @var \PHPUnit_Framework_MockObject_MockObject */ private $userManagerMock; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $mountMock; + /** @var Util */ private $instance; @@ -65,6 +68,7 @@ class UtilTest extends TestCase { protected function setUp() { parent::setUp(); + $this->mountMock = $this->getMock('\OCP\Files\Mount\IMountPoint'); $this->filesMock = $this->getMock('OC\Files\View'); $this->userManagerMock = $this->getMock('\OCP\IUserManager'); @@ -151,4 +155,52 @@ class UtilTest extends TestCase { ]; } + /** + * @dataProvider dataTestShouldEncryptHomeStorage + * @param $returnValue return value from getAppValue() + * @param $expected + */ + public function testShouldEncryptHomeStorage($returnValue, $expected) { + $this->configMock->expects($this->once())->method('getAppValue') + ->with('encryption', 'encryptHomeStorage', '1') + ->willReturn($returnValue); + + $this->assertSame($expected, + $this->instance->shouldEncryptHomeStorage()); + } + + public function dataTestShouldEncryptHomeStorage() { + return [ + ['1', true], + ['0', false] + ]; + } + + /** + * @dataProvider dataTestSetEncryptHomeStorage + * @param $value + * @param $expected + */ + public function testSetEncryptHomeStorage($value, $expected) { + $this->configMock->expects($this->once())->method('setAppValue') + ->with('encryption', 'encryptHomeStorage', $expected); + $this->instance->setEncryptHomeStorage($value); + } + + public function dataTestSetEncryptHomeStorage() { + return [ + [true, '1'], + [false, '0'] + ]; + } + + public function testGetStorage() { + $path = '/foo/bar.txt'; + $this->filesMock->expects($this->once())->method('getMount')->with($path) + ->willReturn($this->mountMock); + $this->mountMock->expects($this->once())->method('getStorage')->willReturn(true); + + $this->assertTrue($this->instance->getStorage($path)); + } + } diff --git a/apps/encryption/tests/lib/crypto/encryptionTest.php b/apps/encryption/tests/lib/crypto/encryptionTest.php index f76bdfb6d61..138c1bc9446 100644 --- a/apps/encryption/tests/lib/crypto/encryptionTest.php +++ b/apps/encryption/tests/lib/crypto/encryptionTest.php @@ -55,9 +55,14 @@ class EncryptionTest extends TestCase { /** @var \PHPUnit_Framework_MockObject_MockObject */ private $l10nMock; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $storageMock; + public function setUp() { parent::setUp(); + $this->storageMock = $this->getMockBuilder('OCP\Files\Storage') + ->disableOriginalConstructor()->getMock(); $this->cryptMock = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt') ->disableOriginalConstructor() ->getMock(); @@ -312,7 +317,17 @@ class EncryptionTest extends TestCase { * * @dataProvider dataTestShouldEncrypt */ - public function testShouldEncrypt($path, $expected) { + public function testShouldEncrypt($path, $shouldEncryptHomeStorage, $isHomeStorage, $expected) { + $this->utilMock->expects($this->once())->method('shouldEncryptHomeStorage') + ->willReturn($shouldEncryptHomeStorage); + + if ($shouldEncryptHomeStorage === false) { + $this->storageMock->expects($this->once())->method('instanceOfStorage') + ->with('\OCP\Files\IHomeStorage')->willReturn($isHomeStorage); + $this->utilMock->expects($this->once())->method('getStorage')->with($path) + ->willReturn($this->storageMock); + } + $this->assertSame($expected, $this->instance->shouldEncrypt($path) ); @@ -320,14 +335,17 @@ class EncryptionTest extends TestCase { public function dataTestShouldEncrypt() { return array( - array('/user1/files/foo.txt', true), - array('/user1/files_versions/foo.txt', true), - array('/user1/files_trashbin/foo.txt', true), - array('/user1/some_folder/foo.txt', false), - array('/user1/foo.txt', false), - array('/user1/files', false), - array('/user1/files_trashbin', false), - array('/user1/files_versions', false), + array('/user1/files/foo.txt', true, true, true), + array('/user1/files_versions/foo.txt', true, true, true), + array('/user1/files_trashbin/foo.txt', true, true, true), + array('/user1/some_folder/foo.txt', true, true, false), + array('/user1/foo.txt', true, true, false), + array('/user1/files', true, true, false), + array('/user1/files_trashbin', true, true, false), + array('/user1/files_versions', true, true, false), + // test if shouldEncryptHomeStorage is set to false + array('/user1/files/foo.txt', false, true, false), + array('/user1/files_versions/foo.txt', false, false, true), ); } diff --git a/apps/files/appinfo/application.php b/apps/files/appinfo/application.php index 6ba77e09556..6aff517e17f 100644 --- a/apps/files/appinfo/application.php +++ b/apps/files/appinfo/application.php @@ -1,6 +1,6 @@ <?php /** - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Tobias Kaminsky <tobias@kaminsky.me> * @author Vincent Petry <pvince81@owncloud.com> * diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml index 8586c6794f2..ba8bb62494e 100644 --- a/apps/files/appinfo/info.xml +++ b/apps/files/appinfo/info.xml @@ -5,17 +5,16 @@ <description>File Management</description> <licence>AGPL</licence> <author>Robin Appelman, Vincent Petry</author> - <requiremin>4.93</requiremin> <shipped>true</shipped> <standalone/> <default_enable/> + <version>1.3.0</version> <types> <filesystem/> </types> - <remote> - <files>appinfo/remote.php</files> - <webdav>appinfo/remote.php</webdav> - </remote> + <dependencies> + <owncloud min-version="9.0" /> + </dependencies> <documentation> <user>user-files</user> </documentation> diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php index 41aeec6a152..d52dfaab21c 100644 --- a/apps/files/appinfo/routes.php +++ b/apps/files/appinfo/routes.php @@ -2,7 +2,7 @@ /** * @author Bart Visscher <bartv@thisnet.nl> * @author Lukas Reschke <lukas@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Tobias Kaminsky <tobias@kaminsky.me> * @author Tom Needham <tom@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> diff --git a/apps/files/appinfo/version b/apps/files/appinfo/version deleted file mode 100644 index 9ee1f786d50..00000000000 --- a/apps/files/appinfo/version +++ /dev/null @@ -1 +0,0 @@ -1.1.11 diff --git a/apps/files/controller/apicontroller.php b/apps/files/controller/apicontroller.php index ea5ee81a9f6..1ecd5294c66 100644 --- a/apps/files/controller/apicontroller.php +++ b/apps/files/controller/apicontroller.php @@ -3,7 +3,7 @@ * @author Joas Schilling <nickvergessen@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Tobias Kaminsky <tobias@kaminsky.me> * @author Vincent Petry <pvince81@owncloud.com> diff --git a/apps/files/css/files.css b/apps/files/css/files.css index d0a2cce1a86..9588faebc3b 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -35,13 +35,15 @@ } /* FILE TABLE */ - #filestable { position: relative; - top: 44px; width: 100%; } +#filestable.has-controls { + top: 44px; +} + /* make sure there's enough room for the file actions */ #body-user #filestable { min-width: 688px; /* 768 (mobile break) - 80 (nav width) */ @@ -85,19 +87,6 @@ background-image: url('../img/delete.svg'); } -/* move Deleted Files to bottom of sidebar */ -.nav-trashbin { - position: fixed !important; - bottom: 44px; - width: inherit !important; - background-color: #fff; - border-right: 1px solid #eee; -} -/* double padding to account for Deleted files entry, issue with Firefox */ -.app-files #app-navigation > ul li:nth-last-child(2) { - margin-bottom: 44px; -} - #app-navigation .nav-files a.nav-icon-files { width: auto; } @@ -745,6 +734,10 @@ table.dragshadow td.size { margin: -10px; } +html.ie8 #controls .button.new { + padding-right: 0; +} + .newFileMenu { width: 140px; margin-left: -56px; @@ -796,3 +789,4 @@ table.dragshadow td.size { color: #000; padding: 0; } + diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 8bae8567a05..d419cb3a2c0 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -18,7 +18,7 @@ * - TODO music upload button */ -/* global jQuery, oc_requesttoken, humanFileSize */ +/* global jQuery, oc_requesttoken, humanFileSize, FileList */ /** * Function that will allow us to know if Ajax uploads are supported @@ -48,6 +48,26 @@ function supportAjaxUploadWithProgress() { } /** + * Add form data into the given form data + * + * @param {Array|Object} formData form data which can either be an array or an object + * @param {Object} newData key-values to add to the form data + * + * @return updated form data + */ +function addFormData(formData, newData) { + // in IE8, formData is an array instead of object + if (_.isArray(formData)) { + _.each(newData, function(value, key) { + formData.push({name: key, value: value}); + }); + } else { + formData = _.extend(formData, newData); + } + return formData; +} + +/** * keeps track of uploads in progress and implements callbacks for the conflicts dialog * @namespace */ @@ -75,6 +95,9 @@ OC.Upload = { this._uploads.push(jqXHR); } }, + showUploadCancelMessage: _.debounce(function() { + OC.Notification.showTemporary(t('files', 'Upload cancelled.'), {timeout: 10}); + }, 500), /** * Checks the currently known uploads. * returns true if any hxr has the state 'pending' @@ -140,9 +163,9 @@ OC.Upload = { data.data.append('resolution', 'replace'); } else { if (!data.formData) { - data.formData = []; + data.formData = {}; } - data.formData.push({name:'resolution', value:'replace'}); //hack for ie8 + addFormData(data.formData, {resolution: 'replace'}); } data.submit(); }, @@ -156,9 +179,9 @@ OC.Upload = { data.data.append('resolution', 'autorename'); } else { if (!data.formData) { - data.formData = []; + data.formData = {}; } - data.formData.push({name:'resolution', value:'autorename'}); //hack for ie8 + addFormData(data.formData, {resolution: 'autorename'}); } data.submit(); }, @@ -182,7 +205,7 @@ OC.Upload = { * @param {function} callbacks.onCancel */ checkExistingFiles: function (selection, callbacks) { - var fileList = OCA.Files.App.fileList; + var fileList = FileList; var conflicts = []; // only keep non-conflicting uploads selection.uploads = _.filter(selection.uploads, function(upload) { @@ -399,26 +422,28 @@ OC.Upload = { submit: function(e, data) { OC.Upload.rememberUpload(data); if (!data.formData) { - data.formData = []; + data.formData = {}; } var fileDirectory = ''; if(typeof data.files[0].relativePath !== 'undefined') { fileDirectory = data.files[0].relativePath; } - // FIXME: prevent re-adding the same - data.formData.push({name: 'requesttoken', value: oc_requesttoken}); - data.formData.push({name: 'dir', value: data.targetDir || FileList.getCurrentDirectory()}); - data.formData.push({name: 'file_directory', value: fileDirectory}); + + addFormData(data.formData, { + requesttoken: oc_requesttoken, + dir: data.targetDir || FileList.getCurrentDirectory(), + file_directory: fileDirectory + }); }, fail: function(e, data) { OC.Upload.log('fail', e, data); if (typeof data.textStatus !== 'undefined' && data.textStatus !== 'success' ) { if (data.textStatus === 'abort') { - OC.Notification.show(t('files', 'Upload cancelled.')); + OC.Upload.showUploadCancelMessage(); } else { // HTTP connection problem - OC.Notification.show(data.errorThrown); + OC.Notification.showTemporary(data.errorThrown, {timeout: 10}); if (data.result) { var result = JSON.parse(data.result); if (result && result[0] && result[0].data && result[0].data.code === 'targetnotfound') { @@ -427,10 +452,6 @@ OC.Upload = { } } } - //hide notification after 10 sec - setTimeout(function() { - OC.Notification.hide(); - }, 10000); } OC.Upload.deleteUpload(data); }, diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 6cf82391baf..c84d6c3c47d 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -226,7 +226,11 @@ } this.breadcrumb = new OCA.Files.BreadCrumb(breadcrumbOptions); - this.$el.find('#controls').prepend(this.breadcrumb.$el); + var $controls = this.$el.find('#controls'); + if ($controls.length > 0) { + $controls.prepend(this.breadcrumb.$el); + this.$table.addClass('has-controls'); + } this._renderNewButton(); @@ -386,12 +390,15 @@ * Update the details view to display the given file * * @param {string} fileName file name from the current list + * @param {boolean} [show=true] whether to open the sidebar if it was closed */ - _updateDetailsView: function(fileName) { + _updateDetailsView: function(fileName, show) { if (!this._detailsView) { return; } + // show defaults to true + show = _.isUndefined(show) || !!show; var oldFileInfo = this._detailsView.getFileInfo(); if (oldFileInfo) { // TODO: use more efficient way, maybe track the highlight @@ -409,7 +416,7 @@ return; } - if (this._detailsView.$el.hasClass('disappear')) { + if (show && this._detailsView.$el.hasClass('disappear')) { OC.Apps.showAppSidebar(this._detailsView.$el); } @@ -1350,7 +1357,7 @@ ) { OC.redirect(OC.generateUrl('apps/files')); } - OC.Notification.show(result.data.message); + OC.Notification.showTemporary(result.data.message); return false; } @@ -1358,7 +1365,7 @@ if (result.status === 403) { // Go home this.changeDirectory('/'); - OC.Notification.show(t('files', 'This operation is forbidden')); + OC.Notification.showTemporary(t('files', 'This operation is forbidden')); return false; } @@ -1366,7 +1373,7 @@ if (result.status === 500) { // Go home this.changeDirectory('/'); - OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator')); + OC.Notification.showTemporary(t('files', 'This directory is unavailable, please check the logs or contact the administrator')); return false; } @@ -1640,15 +1647,11 @@ } else { OC.Notification.hide(); if (result.status === 'error' && result.data.message) { - OC.Notification.show(result.data.message); + OC.Notification.showTemporary(result.data.message); } else { - OC.Notification.show(t('files', 'Error moving file.')); + OC.Notification.showTemporary(t('files', 'Error moving file.')); } - // hide notification after 10 sec - setTimeout(function() { - OC.Notification.hide(); - }, 10000); } } else { OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); @@ -1771,7 +1774,7 @@ tr.remove(); tr = self.add(fileInfo, {updateSummary: false, silent: true}); self.$fileList.trigger($.Event('fileActionsReady', {fileList: self, $files: $(tr)})); - self._updateDetailsView(fileInfo.name); + self._updateDetailsView(fileInfo.name, false); } }); } else { @@ -2011,17 +2014,15 @@ self.fileSummary.update(); self.updateSelectionSummary(); self.updateStorageStatistics(); + // in case there was a "storage full" permanent notification + OC.Notification.hide(); } else { if (result.status === 'error' && result.data.message) { - OC.Notification.show(result.data.message); + OC.Notification.showTemporary(result.data.message); } else { - OC.Notification.show(t('files', 'Error deleting file.')); + OC.Notification.showTemporary(t('files', 'Error deleting file.')); } - // hide notification after 10 sec - setTimeout(function() { - OC.Notification.hide(); - }, 10000); if (params.allfiles) { // reload the page as we don't know what files were deleted // and which ones remain @@ -2262,11 +2263,7 @@ */ _showPermissionDeniedNotification: function() { var message = t('core', 'You don’t have permission to upload or create files here'); - OC.Notification.show(message); - //hide notification after 10 sec - setTimeout(function() { - OC.Notification.hide(); - }, 5000); + OC.Notification.showTemporary(message); }, /** @@ -2620,14 +2617,18 @@ * Register a tab view to be added to all views */ registerTabView: function(tabView) { - this._detailsView.addTabView(tabView); + if (this._detailsView) { + this._detailsView.addTabView(tabView); + } }, /** * Register a detail view to be added to all views */ registerDetailView: function(detailView) { - this._detailsView.addDetailView(detailView); + if (this._detailsView) { + this._detailsView.addDetailView(detailView); + } } }; diff --git a/apps/files/js/mainfileinfodetailview.js b/apps/files/js/mainfileinfodetailview.js index b50e92dea8c..abf7da52ff4 100644 --- a/apps/files/js/mainfileinfodetailview.js +++ b/apps/files/js/mainfileinfodetailview.js @@ -15,9 +15,8 @@ '<div class="fileName"><h3 title="{{name}}" class="ellipsis">{{name}}</h3></div>' + ' <div class="file-details ellipsis">' + ' <a href="#" ' + - ' alt="{{starAltText}}"' + ' class="action action-favorite favorite">' + - ' <img class="svg" src="{{starIcon}}" />' + + ' <img class="svg" alt="{{starAltText}}" src="{{starIcon}}" />' + ' </a>' + ' {{#if hasSize}}<span class="size" title="{{altSize}}">{{size}}</span>, {{/if}}<span class="date" title="{{altDate}}">{{date}}</span>' + ' </div>' + @@ -66,10 +65,10 @@ this._fileList = options.fileList; this._fileActions = options.fileActions; if (!this._fileList) { - throw 'Missing requird parameter "fileList"'; + throw 'Missing required parameter "fileList"'; } if (!this._fileActions) { - throw 'Missing requird parameter "fileActions"'; + throw 'Missing required parameter "fileActions"'; } }, diff --git a/apps/files/l10n/ja.js b/apps/files/l10n/ja.js index dbf66176c06..052562d13ee 100644 --- a/apps/files/l10n/ja.js +++ b/apps/files/l10n/ja.js @@ -30,10 +30,10 @@ OC.L10N.register( "Favorites" : "お気に入り", "Home" : "ホーム", "Close" : "閉じる", + "Upload cancelled." : "アップロードはキャンセルされました。", "Unable to upload {filename} as it is a directory or has 0 bytes" : "ディレクトリもしくは0バイトのため {filename} をアップロードできません", "Total file size {size1} exceeds upload limit {size2}" : "合計ファイルサイズ {size1} はアップロード制限 {size2} を超過しています。", "Not enough free space, you are uploading {size1} but only {size2} is left" : "空き容量が十分でなく、 {size1} をアップロードしていますが、 {size2} しか残っていません。", - "Upload cancelled." : "アップロードはキャンセルされました。", "Could not get result from server." : "サーバーから結果を取得できませんでした。", "File upload is in progress. Leaving the page now will cancel the upload." : "ファイル転送を実行中です。今このページから移動するとアップロードが中止されます。", "Actions" : "アクション", @@ -75,8 +75,10 @@ OC.L10N.register( "_%n byte_::_%n bytes_" : ["%n バイト"], "Favorited" : "お気に入り済", "Favorite" : "お気に入り", + "{newname} already exists" : "{newname} はすでに存在します", "Upload" : "アップロード", "Text file" : "テキストファイル", + "New text file.txt" : "新規のテキストファイル作成", "Folder" : "フォルダー", "New folder" : "新しいフォルダー", "An error occurred while trying to update the tags" : "タグを更新する際にエラーが発生しました", @@ -94,6 +96,9 @@ OC.L10N.register( "%2$s deleted %1$s" : "%2$s は %1$s を削除しました", "You restored %1$s" : "%1$s を復元しました", "%2$s restored %1$s" : "%2$s は、 %1$s を復元しました", + "Changed by %2$s" : "%2$s により更新", + "Deleted by %2$s" : "%2$s により削除", + "Restored by %2$s" : "%2$s により復元", "%s could not be renamed as it has been deleted" : "%s は削除されたため、ファイル名を変更できません", "%s could not be renamed" : "%sの名前を変更できませんでした", "Upload (max. %s)" : "アップロード ( 最大 %s )", diff --git a/apps/files/l10n/ja.json b/apps/files/l10n/ja.json index 5a562f400c0..4534e787e0f 100644 --- a/apps/files/l10n/ja.json +++ b/apps/files/l10n/ja.json @@ -28,10 +28,10 @@ "Favorites" : "お気に入り", "Home" : "ホーム", "Close" : "閉じる", + "Upload cancelled." : "アップロードはキャンセルされました。", "Unable to upload {filename} as it is a directory or has 0 bytes" : "ディレクトリもしくは0バイトのため {filename} をアップロードできません", "Total file size {size1} exceeds upload limit {size2}" : "合計ファイルサイズ {size1} はアップロード制限 {size2} を超過しています。", "Not enough free space, you are uploading {size1} but only {size2} is left" : "空き容量が十分でなく、 {size1} をアップロードしていますが、 {size2} しか残っていません。", - "Upload cancelled." : "アップロードはキャンセルされました。", "Could not get result from server." : "サーバーから結果を取得できませんでした。", "File upload is in progress. Leaving the page now will cancel the upload." : "ファイル転送を実行中です。今このページから移動するとアップロードが中止されます。", "Actions" : "アクション", @@ -73,8 +73,10 @@ "_%n byte_::_%n bytes_" : ["%n バイト"], "Favorited" : "お気に入り済", "Favorite" : "お気に入り", + "{newname} already exists" : "{newname} はすでに存在します", "Upload" : "アップロード", "Text file" : "テキストファイル", + "New text file.txt" : "新規のテキストファイル作成", "Folder" : "フォルダー", "New folder" : "新しいフォルダー", "An error occurred while trying to update the tags" : "タグを更新する際にエラーが発生しました", @@ -92,6 +94,9 @@ "%2$s deleted %1$s" : "%2$s は %1$s を削除しました", "You restored %1$s" : "%1$s を復元しました", "%2$s restored %1$s" : "%2$s は、 %1$s を復元しました", + "Changed by %2$s" : "%2$s により更新", + "Deleted by %2$s" : "%2$s により削除", + "Restored by %2$s" : "%2$s により復元", "%s could not be renamed as it has been deleted" : "%s は削除されたため、ファイル名を変更できません", "%s could not be renamed" : "%sの名前を変更できませんでした", "Upload (max. %s)" : "アップロード ( 最大 %s )", diff --git a/apps/files/l10n/ko.js b/apps/files/l10n/ko.js index d5090d57bde..67b5e869e5e 100644 --- a/apps/files/l10n/ko.js +++ b/apps/files/l10n/ko.js @@ -14,7 +14,7 @@ OC.L10N.register( "Unable to set upload directory." : "업로드 디렉터리를 설정할 수 없습니다.", "Invalid Token" : "잘못된 토큰", "No file was uploaded. Unknown error" : "파일이 업로드 되지 않았습니다. 알 수 없는 오류입니다", - "There is no error, the file uploaded with success" : "파일 업로드에 성공하였습니다.", + "There is no error, the file uploaded with success" : "파일 업로드에 성공했습니다.", "The uploaded file exceeds the upload_max_filesize directive in php.ini: " : "업로드한 파일이 php.ini의 upload_max_filesize보다 큽니다:", "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "업로드한 파일 크기가 HTML 폼의 MAX_FILE_SIZE보다 큼", "The uploaded file was only partially uploaded" : "파일의 일부분만 업로드됨", @@ -30,10 +30,10 @@ OC.L10N.register( "Favorites" : "즐겨찾기", "Home" : "가정", "Close" : "닫기", + "Upload cancelled." : "업로드가 취소되었습니다.", "Unable to upload {filename} as it is a directory or has 0 bytes" : "{filename}을(를) 업로드할 수 없습니다. 폴더이거나 0 바이트 파일입니다.", "Total file size {size1} exceeds upload limit {size2}" : "총 파일 크기 {size1}이(가) 업로드 제한 {size2}을(를) 초과함", "Not enough free space, you are uploading {size1} but only {size2} is left" : "빈 공간이 부족합니다. 업로드할 파일 크기는 {size1}이지만 현재 {size2}만큼 비었습니다", - "Upload cancelled." : "업로드가 취소되었습니다.", "Could not get result from server." : "서버에서 결과를 가져올 수 없습니다.", "File upload is in progress. Leaving the page now will cancel the upload." : "파일 업로드가 진행 중입니다. 이 페이지를 벗어나면 업로드가 취소됩니다.", "Actions" : "작업", diff --git a/apps/files/l10n/ko.json b/apps/files/l10n/ko.json index 92ea053f64f..3c88e502b1f 100644 --- a/apps/files/l10n/ko.json +++ b/apps/files/l10n/ko.json @@ -12,7 +12,7 @@ "Unable to set upload directory." : "업로드 디렉터리를 설정할 수 없습니다.", "Invalid Token" : "잘못된 토큰", "No file was uploaded. Unknown error" : "파일이 업로드 되지 않았습니다. 알 수 없는 오류입니다", - "There is no error, the file uploaded with success" : "파일 업로드에 성공하였습니다.", + "There is no error, the file uploaded with success" : "파일 업로드에 성공했습니다.", "The uploaded file exceeds the upload_max_filesize directive in php.ini: " : "업로드한 파일이 php.ini의 upload_max_filesize보다 큽니다:", "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "업로드한 파일 크기가 HTML 폼의 MAX_FILE_SIZE보다 큼", "The uploaded file was only partially uploaded" : "파일의 일부분만 업로드됨", @@ -28,10 +28,10 @@ "Favorites" : "즐겨찾기", "Home" : "가정", "Close" : "닫기", + "Upload cancelled." : "업로드가 취소되었습니다.", "Unable to upload {filename} as it is a directory or has 0 bytes" : "{filename}을(를) 업로드할 수 없습니다. 폴더이거나 0 바이트 파일입니다.", "Total file size {size1} exceeds upload limit {size2}" : "총 파일 크기 {size1}이(가) 업로드 제한 {size2}을(를) 초과함", "Not enough free space, you are uploading {size1} but only {size2} is left" : "빈 공간이 부족합니다. 업로드할 파일 크기는 {size1}이지만 현재 {size2}만큼 비었습니다", - "Upload cancelled." : "업로드가 취소되었습니다.", "Could not get result from server." : "서버에서 결과를 가져올 수 없습니다.", "File upload is in progress. Leaving the page now will cancel the upload." : "파일 업로드가 진행 중입니다. 이 페이지를 벗어나면 업로드가 취소됩니다.", "Actions" : "작업", diff --git a/apps/files/l10n/nb_NO.js b/apps/files/l10n/nb_NO.js index 3d27173708d..7fe475a6a22 100644 --- a/apps/files/l10n/nb_NO.js +++ b/apps/files/l10n/nb_NO.js @@ -30,10 +30,10 @@ OC.L10N.register( "Favorites" : "Favoritter", "Home" : "Hjem", "Close" : "Lukk", + "Upload cancelled." : "Opplasting avbrutt.", "Unable to upload {filename} as it is a directory or has 0 bytes" : "Kan ikke laste opp {filename} fordi det er en mappe eller har 0 bytes", "Total file size {size1} exceeds upload limit {size2}" : "Total filstørrelse {size1} overstiger grense for opplasting {size2}", "Not enough free space, you are uploading {size1} but only {size2} is left" : "Ikke nok ledig plass. Du laster opp size1} men bare {size2} er ledig", - "Upload cancelled." : "Opplasting avbrutt.", "Could not get result from server." : "Fikk ikke resultat fra serveren.", "File upload is in progress. Leaving the page now will cancel the upload." : "Filopplasting pågår. Forlater du siden nå avbrytes opplastingen.", "Actions" : "Handlinger", @@ -96,6 +96,9 @@ OC.L10N.register( "%2$s deleted %1$s" : "%2$s slettet %1$s", "You restored %1$s" : "Du gjenopprettet %1$s", "%2$s restored %1$s" : "%2$s gjenopprettet %1$s", + "Changed by %2$s" : "Endret av %2$s", + "Deleted by %2$s" : "Slettet av %2$s", + "Restored by %2$s" : "Gjenopprettet av %2$s", "%s could not be renamed as it has been deleted" : "%s kunne ikke gis nytt navn da den er blitt slettet", "%s could not be renamed" : "Kunne ikke gi nytt navn til %s", "Upload (max. %s)" : "Opplasting (maks. %s)", diff --git a/apps/files/l10n/nb_NO.json b/apps/files/l10n/nb_NO.json index a7e0cf33b83..69e8ca742aa 100644 --- a/apps/files/l10n/nb_NO.json +++ b/apps/files/l10n/nb_NO.json @@ -28,10 +28,10 @@ "Favorites" : "Favoritter", "Home" : "Hjem", "Close" : "Lukk", + "Upload cancelled." : "Opplasting avbrutt.", "Unable to upload {filename} as it is a directory or has 0 bytes" : "Kan ikke laste opp {filename} fordi det er en mappe eller har 0 bytes", "Total file size {size1} exceeds upload limit {size2}" : "Total filstørrelse {size1} overstiger grense for opplasting {size2}", "Not enough free space, you are uploading {size1} but only {size2} is left" : "Ikke nok ledig plass. Du laster opp size1} men bare {size2} er ledig", - "Upload cancelled." : "Opplasting avbrutt.", "Could not get result from server." : "Fikk ikke resultat fra serveren.", "File upload is in progress. Leaving the page now will cancel the upload." : "Filopplasting pågår. Forlater du siden nå avbrytes opplastingen.", "Actions" : "Handlinger", @@ -94,6 +94,9 @@ "%2$s deleted %1$s" : "%2$s slettet %1$s", "You restored %1$s" : "Du gjenopprettet %1$s", "%2$s restored %1$s" : "%2$s gjenopprettet %1$s", + "Changed by %2$s" : "Endret av %2$s", + "Deleted by %2$s" : "Slettet av %2$s", + "Restored by %2$s" : "Gjenopprettet av %2$s", "%s could not be renamed as it has been deleted" : "%s kunne ikke gis nytt navn da den er blitt slettet", "%s could not be renamed" : "Kunne ikke gi nytt navn til %s", "Upload (max. %s)" : "Opplasting (maks. %s)", diff --git a/apps/files/l10n/oc.js b/apps/files/l10n/oc.js index 6e82e5c5dff..e5dfd46a405 100644 --- a/apps/files/l10n/oc.js +++ b/apps/files/l10n/oc.js @@ -30,10 +30,10 @@ OC.L10N.register( "Favorites" : "Favorits", "Home" : "Mos fichièrs", "Close" : "Tampar", + "Upload cancelled." : "Mandadís anullat.", "Unable to upload {filename} as it is a directory or has 0 bytes" : "Impossible de mandar {filename} perque s'agís d'un repertòri o d'un fichièr de talha nulla", "Total file size {size1} exceeds upload limit {size2}" : "La talha totala del fichièr {size1} excedís la talha maximala de mandadís {size2}", "Not enough free space, you are uploading {size1} but only {size2} is left" : "Espaci liure insufisent : ensajatz de mandar {size1} mas solament {size2} son disponibles", - "Upload cancelled." : "Mandadís anullat.", "Could not get result from server." : "Pòt pas recebre los resultats del servidor.", "File upload is in progress. Leaving the page now will cancel the upload." : "Lo mandadís del fichièr es en cors. Quitar aquesta pagina ara anullarà lo mandadís del fichièr.", "Actions" : "Accions", @@ -44,6 +44,7 @@ OC.L10N.register( "Select" : "Seleccionar", "Pending" : "En espèra", "Unable to determine date" : "Impossible de determinar la data", + "This operation is forbidden" : "L'operacion es interdicha", "Error moving file." : "Error al moment del desplaçament del fichièr.", "Error moving file" : "Error al moment del desplaçament del fichièr", "Error" : "Error", @@ -67,8 +68,10 @@ OC.L10N.register( "Your storage is full, files can not be updated or synced anymore!" : "Vòstre espaci d'emmagazinatge es plen, los fichièrs pòdon pas mai èsser aponduts o sincronizats !", "Your storage is almost full ({usedSpacePercent}%)" : "Vòstre espace d'emmagazinatge es gaireben plen ({usedSpacePercent}%)", "_matches '{filter}'_::_match '{filter}'_" : ["correspond a '{filter}'","correspondon a '{filter}'"], + "_%n byte_::_%n bytes_" : ["%n octet","%n octets"], "Favorited" : "Marcat coma favorit", "Favorite" : "Favorit", + "{newname} already exists" : "{new_name} existís ja", "Upload" : "Cargament", "Text file" : "Fichièr tèxte", "New text file.txt" : "Novèl fichièr tèxte .txt", @@ -89,6 +92,9 @@ OC.L10N.register( "%2$s deleted %1$s" : "%2$s a suprimit %1$s", "You restored %1$s" : "Avètz restablit %1$s", "%2$s restored %1$s" : "%2$s a restablit %1$s", + "Changed by %2$s" : "Modificat per %2$s", + "Deleted by %2$s" : "Suprimit per %2$s", + "Restored by %2$s" : "Restablit per %2$s", "%s could not be renamed as it has been deleted" : "%s pòt pas èsser renomenat perque es estat suprimit ", "%s could not be renamed" : "%s pòt pas èsser renomenat", "Upload (max. %s)" : "Mandadís (max. %s)", diff --git a/apps/files/l10n/oc.json b/apps/files/l10n/oc.json index b75d26a5ad3..c35ca8011dd 100644 --- a/apps/files/l10n/oc.json +++ b/apps/files/l10n/oc.json @@ -28,10 +28,10 @@ "Favorites" : "Favorits", "Home" : "Mos fichièrs", "Close" : "Tampar", + "Upload cancelled." : "Mandadís anullat.", "Unable to upload {filename} as it is a directory or has 0 bytes" : "Impossible de mandar {filename} perque s'agís d'un repertòri o d'un fichièr de talha nulla", "Total file size {size1} exceeds upload limit {size2}" : "La talha totala del fichièr {size1} excedís la talha maximala de mandadís {size2}", "Not enough free space, you are uploading {size1} but only {size2} is left" : "Espaci liure insufisent : ensajatz de mandar {size1} mas solament {size2} son disponibles", - "Upload cancelled." : "Mandadís anullat.", "Could not get result from server." : "Pòt pas recebre los resultats del servidor.", "File upload is in progress. Leaving the page now will cancel the upload." : "Lo mandadís del fichièr es en cors. Quitar aquesta pagina ara anullarà lo mandadís del fichièr.", "Actions" : "Accions", @@ -42,6 +42,7 @@ "Select" : "Seleccionar", "Pending" : "En espèra", "Unable to determine date" : "Impossible de determinar la data", + "This operation is forbidden" : "L'operacion es interdicha", "Error moving file." : "Error al moment del desplaçament del fichièr.", "Error moving file" : "Error al moment del desplaçament del fichièr", "Error" : "Error", @@ -65,8 +66,10 @@ "Your storage is full, files can not be updated or synced anymore!" : "Vòstre espaci d'emmagazinatge es plen, los fichièrs pòdon pas mai èsser aponduts o sincronizats !", "Your storage is almost full ({usedSpacePercent}%)" : "Vòstre espace d'emmagazinatge es gaireben plen ({usedSpacePercent}%)", "_matches '{filter}'_::_match '{filter}'_" : ["correspond a '{filter}'","correspondon a '{filter}'"], + "_%n byte_::_%n bytes_" : ["%n octet","%n octets"], "Favorited" : "Marcat coma favorit", "Favorite" : "Favorit", + "{newname} already exists" : "{new_name} existís ja", "Upload" : "Cargament", "Text file" : "Fichièr tèxte", "New text file.txt" : "Novèl fichièr tèxte .txt", @@ -87,6 +90,9 @@ "%2$s deleted %1$s" : "%2$s a suprimit %1$s", "You restored %1$s" : "Avètz restablit %1$s", "%2$s restored %1$s" : "%2$s a restablit %1$s", + "Changed by %2$s" : "Modificat per %2$s", + "Deleted by %2$s" : "Suprimit per %2$s", + "Restored by %2$s" : "Restablit per %2$s", "%s could not be renamed as it has been deleted" : "%s pòt pas èsser renomenat perque es estat suprimit ", "%s could not be renamed" : "%s pòt pas èsser renomenat", "Upload (max. %s)" : "Mandadís (max. %s)", diff --git a/apps/files/l10n/sq.js b/apps/files/l10n/sq.js index 296efe6381d..8f19800cd2a 100644 --- a/apps/files/l10n/sq.js +++ b/apps/files/l10n/sq.js @@ -1,96 +1,125 @@ OC.L10N.register( "files", { - "Storage not available" : "Hapësira e memorizimit nuk është e disponueshme", - "Storage invalid" : "Hapësirë memorizimi e pavlefshme", - "Unknown error" : "Gabim panjohur", - "Could not move %s - File with this name already exists" : "E pa mundur zhvendosja e %s - ekziston nje skedar me te njetin emer", - "Could not move %s" : "Nuk mund të zhvendoset %s", - "Permission denied" : "Nuk ka të drejtë", - "The target folder has been moved or deleted." : "Dosja e destinacionit është zhvendosur ose fshirë.", - "The name %s is already used in the folder %s. Please choose a different name." : "Emri %s është i përdorur në dosjen %s. Ju lutem zgjidhni një emër tjetër.", - "Error when creating the file" : "Gabim gjatë krijimit të skedarit", + "Storage not available" : "Pa depozitë gati", + "Storage invalid" : "Depozitë e pavlefshme", + "Unknown error" : "Gabim i panjohur", + "Could not move %s - File with this name already exists" : "S’u zhvendos dot %s - Ka tashmë kartelë me këtë", + "Could not move %s" : "S’u zhvendos dot %s", + "Permission denied" : "Leje e mohuar", + "The target folder has been moved or deleted." : "Dosja vendmbërritje është zhvendosur ose fshirë.", + "The name %s is already used in the folder %s. Please choose a different name." : "Emri %s tashmë është i përdorur në dosjen %s. Ju lutemi, zgjidhni një emër tjetër.", + "Error when creating the file" : "Gabim gjatë krijimit të kartelës", "Error when creating the folder" : "Gabim gjatë krijimit të dosjes", - "Unable to set upload directory." : "E pa mundur të vendoset dosja e ngarkimit", - "Invalid Token" : "Shenjë e gabuar", - "No file was uploaded. Unknown error" : "Asnjë skedar nuk u dërgua. Gabim i pa njohur", - "There is no error, the file uploaded with success" : "Skedari u ngarkua me sukses", - "The uploaded file exceeds the upload_max_filesize directive in php.ini: " : "Skedari i ngarkuar tejkalon limitin hapsirës së lejuar në php.ini", - "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Skedari i ngarkuar tejlakon vlerën MAX_FILE_SIZE të përcaktuar në formën HTML", - "The uploaded file was only partially uploaded" : "Skedari është ngakruar vetëm pjesërisht", - "No file was uploaded" : "Asnjë skedar nuk është ngarkuar", - "Missing a temporary folder" : "Mungon dosja e përkohshme", + "Unable to set upload directory." : "S’arrihet të caktohet drejtori ngarkimesh", + "Invalid Token" : "Token i pavlefshëm", + "No file was uploaded. Unknown error" : "S’u ngarkua ndonjë kartelë. Gabim i panjohur", + "There is no error, the file uploaded with success" : "S’pati gabim, kartela u ngarkua me sukses", + "The uploaded file exceeds the upload_max_filesize directive in php.ini: " : "Kartela e ngarkuar tejkalon udhëzimin upload_max_filesize directive te php.ini: ", + "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Kartela e ngarkuar tejkalon udhëzimin MAX_FILE_SIZE që qe përcaktuar te formulari HTML", + "The uploaded file was only partially uploaded" : "Kartela në fjalë u ngarkua vetëm pjesërisht", + "No file was uploaded" : "S’u ngarkua kartelë", + "Missing a temporary folder" : "Mungon një dosje e përkohshme", "Failed to write to disk" : "Dështoi shkrimi në disk", - "Not enough storage available" : "Hapsira e arkivimit e pamjaftueshme", - "Upload failed. Could not find uploaded file" : "Ngarkimi dështoi. Nuk mund të gjendet skedari i ngarkuar", - "Upload failed. Could not get file info." : "Ngarkimi dështoi. Nuk mund të gjej informacion mbi skedarin.", - "Invalid directory." : "Dosje e pavlefshme", - "Files" : "Skedarë", - "All files" : "Të gjithë", - "Home" : "Shtëpi", - "Close" : "Mbyll", - "Unable to upload {filename} as it is a directory or has 0 bytes" : "Nuk mund të ngarkohet {filename} sepse është dosje ose ka 0 byte", - "Total file size {size1} exceeds upload limit {size2}" : "Përmasa totale {size1} e skedarit tejkalon limitin e ngarkimit {size2}", - "Not enough free space, you are uploading {size1} but only {size2} is left" : "Nuk ka hapësirë të mjaftueshme, ju po ngarkoni {size1} por vetëm {size2} është e lirë", - "Upload cancelled." : "Ngarkimi u anullua", - "Could not get result from server." : "Nuk mund të merret ndonjë rezultat nga serveri.", - "File upload is in progress. Leaving the page now will cancel the upload." : "Skedari duke u ngarkuar. Largimi nga faqja do të anullojë ngarkimin", - "Download" : "Shkarko", - "Rename" : "Riemëro", - "Delete" : "Fshi", - "Pending" : "Në vijim", - "Error moving file." : "Gabim në lëvizjen e skedarëve.", - "Error moving file" : "Gabim lëvizjen dokumentave", + "Not enough storage available" : "S’ka depozitë të mjaftueshme", + "Upload failed. Could not find uploaded file" : "Ngarkimi dështoi. S’u gjet dot kartela e ngarkuar", + "Upload failed. Could not get file info." : "Ngarkoi dështoi. S’u morën dot të dhëna kartele.", + "Invalid directory." : "Drejtori e pavlefshme.", + "Files" : "Kartela", + "All files" : "Krejt kartelat", + "Favorites" : "Të parapëlqyera", + "Home" : "Kreu", + "Close" : "Mbylle", + "Upload cancelled." : "Ngarkimi u anulua.", + "Unable to upload {filename} as it is a directory or has 0 bytes" : "S’arrihet të ngarkohet {filename}, ngaqë është drejtori ose ka 0 bajte", + "Total file size {size1} exceeds upload limit {size2}" : "Madhësia gjithsej e kartelës {size1} e tejkalon kufirin {size2} për ngarkimet", + "Not enough free space, you are uploading {size1} but only {size2} is left" : "Nuk ka hapësirë të mjaftueshme, po ngarkoni {size1}, por ka vetëm {size2} të lira", + "Could not get result from server." : "S’mori dot përfundime nga shërbyesi.", + "File upload is in progress. Leaving the page now will cancel the upload." : "Ngarkimi i kartelës është në punë e sipër. Largimi nga faqja do të anulojë ngarkimin.", + "Actions" : "Veprime", + "Download" : "Shkarkoje", + "Rename" : "Riemërtojeni", + "Delete" : "Fshije", + "Details" : "Hollësi", + "Select" : "Përzgjidhe", + "Pending" : "Në pritje", + "Unable to determine date" : "S’arrihet të përcaktohet data", + "This operation is forbidden" : "Ky veprim është i ndaluar", + "This directory is unavailable, please check the logs or contact the administrator" : "Kjo drejtori nuk kapet, ju lutemi, kontrolloni regjistrat ose lidhuni me përgjegjësin", + "Error moving file." : "Gabim në lëvizjen e kartelës.", + "Error moving file" : "Gabim në lëvizjen e kartelës", "Error" : "Gabim", - "{new_name} already exists" : "{new_name} është ekzistues ", - "Could not rename file" : "Riemërtimi i skedarit nuk është i mundur", - "Could not create file" : "Skedari nuk mund të krijohet", - "Could not create folder" : "I pamundur krijimi i kartelës", - "Error deleting file." : "Gabim gjatë fshirjes së skedarit.", - "Name" : "Emri", - "Size" : "Madhësia", - "Modified" : "Ndryshuar", + "{new_name} already exists" : "{new_name} ekziston tashmtë", + "Could not rename file" : "Kartela s’u riemërtua dot", + "Could not create file" : "Kartela s’u krijua dot", + "Could not create folder" : "Dosja s’u krijua dot", + "Error deleting file." : "Gabim gjatë fshirjes së kartelës.", + "No entries in this folder match '{filter}'" : "Në këtë dosje s’ka zëra me përputhje me '{filter}'", + "Name" : "Emër", + "Size" : "Madhësi", + "Modified" : "Ndryshuar më", "_%n folder_::_%n folders_" : ["%n dosje","%n dosje"], - "_%n file_::_%n files_" : ["%n skedar","%n skedarë"], + "_%n file_::_%n files_" : ["%n kartelë","%n kartela"], "{dirs} and {files}" : "{dirs} dhe {files}", - "You don’t have permission to upload or create files here" : "Ju nuk keni të drejta për të ngarkuar apo krijuar skedarë këtu", - "_Uploading %n file_::_Uploading %n files_" : ["Po ngarkoj %n skedar","Po ngarkoj %n skedarë"], + "You don’t have permission to upload or create files here" : "S’keni leje për të ngarkuar apo krijuar kartela këtu", + "_Uploading %n file_::_Uploading %n files_" : ["Po ngarkohet %n kartelë","Po ngarkohen %n kartela"], "New" : "E re", - "\"{name}\" is an invalid file name." : "\"{name}\" është emër i pavlefshëm.", - "File name cannot be empty." : "Emri i skedarit nuk mund të jetë bosh.", - "Your storage is full, files can not be updated or synced anymore!" : "Hapsira juaj e arkivimit është plot, skedarët nuk mund të përditësohen ose sinkronizohen!", - "Your storage is almost full ({usedSpacePercent}%)" : "Hapsira juaj e arkivimit është pothuajse në fund ({usedSpacePercent}%)", - "Upload" : "Ngarko", - "Text file" : "Skedar tekst", + "\"{name}\" is an invalid file name." : "\"{name}\" është emër i pavlefshëm kartele.", + "File name cannot be empty." : "Emri i kartelës s’mund të jetë i zbrazët.", + "Storage of {owner} is full, files can not be updated or synced anymore!" : "Depozita e {owner} është plot, kartelat s’mund të përditësohen ose njëkohësohen më!", + "Your storage is full, files can not be updated or synced anymore!" : "Depozita juaj është plot, kartelat s’mund të përditësohen ose njëkohësohen më!", + "Storage of {owner} is almost full ({usedSpacePercent}%)" : "Depozita e {owner} është thuasje plot ({usedSpacePercent}%)", + "Your storage is almost full ({usedSpacePercent}%)" : "Depozita juaj është thuajse plot ({usedSpacePercent}%)", + "Path" : "Shteg", + "_%n byte_::_%n bytes_" : ["%n bajt","%n bajte"], + "Favorited" : "U kalua e parapëlqyer", + "Favorite" : "E parapëlqyer", + "{newname} already exists" : "Ka tashmë një {newname}", + "Upload" : "Ngarkoje", + "Text file" : "Kartelë tekst", + "New text file.txt" : "Kartelë e re file.txt", "Folder" : "Dosje", - "New folder" : "Dosje e're", - "A new file or folder has been <strong>created</strong>" : "Një skedar ose një dosje e re është <strong>krijuar</strong>", - "A file or folder has been <strong>changed</strong>" : "Një skedar ose një dosje ka <strong>ndryshuar</strong>", - "A file or folder has been <strong>deleted</strong>" : "Një skedar ose një dosje është <strong>fshirë</strong>", - "A file or folder has been <strong>restored</strong>" : "Një skedar ose dosje është <strong>rikthyer</strong>", - "You created %1$s" : "Ju krijuat %1$s", - "%2$s created %1$s" : "%2$s krijuar %1$s", - "%1$s was created in a public folder" : "%1$s është krijuar në një dosje publike", - "You changed %1$s" : "Ju ndryshuat %1$s", - "%2$s changed %1$s" : "%2$s ndryshuar %1$s", - "You deleted %1$s" : "Ju fshitë %1$s", - "%2$s deleted %1$s" : "%2$s fshirë %1$s", - "You restored %1$s" : "Ju rikthyet %1$s", + "New folder" : "Dosje e re", + "An error occurred while trying to update the tags" : "Ndodhi një gabim teksa provohej të përditësoheshin etiketat", + "A new file or folder has been <strong>created</strong>" : "<strong>U krijua</strong> një kartelë ose dosje e re", + "A file or folder has been <strong>changed</strong>" : "<strong>U ndryshua</strong> një kartelë ose dosje", + "Limit notifications about creation and changes to your <strong>favorite files</strong> <em>(Stream only)</em>" : "Kufizojini njoftimet mbi krijim dhe ndryshim kartelash vetëm për <strong>kartelat tuaja të parapëlqyera</strong> <em>(Vetëm te rrjedha)</em>", + "A file or folder has been <strong>deleted</strong>" : "<strong>U fshi</strong> një kartelë ose dosje", + "A file or folder has been <strong>restored</strong>" : "<strong>U rikthye</strong> një kartelë ose dosje", + "You created %1$s" : "Krijuat %1$s", + "%2$s created %1$s" : "%2$s krijoi %1$s", + "%1$s was created in a public folder" : "%1$s u krijua në një dosje publike", + "You changed %1$s" : "Ndryshuat %1$s", + "%2$s changed %1$s" : "%2$s ndryshoi %1$s", + "You deleted %1$s" : "Fshitë %1$s", + "%2$s deleted %1$s" : "%2$s fshiu %1$s", + "You restored %1$s" : "Rikthyet %1$s", "%2$s restored %1$s" : "%2$s riktheu %1$s", - "%s could not be renamed as it has been deleted" : "%s nuk mund të riemërtohet sepse është fshirë", - "%s could not be renamed" : "Nuk është i mundur riemërtimi i %s", - "Upload (max. %s)" : "Ngarko (maks. %s)", - "File handling" : "Trajtimi i Skedarëve", - "Maximum upload size" : "Madhësia maksimale e nagarkimit", - "max. possible: " : "maks i mundshëm", - "Save" : "Ruaj", - "Settings" : "Konfigurime", + "Changed by %2$s" : "Ndryshuar nga %2$s", + "Deleted by %2$s" : "Fshirë nga %2$s", + "Restored by %2$s" : "Rikthyer nga %2$s", + "%s could not be renamed as it has been deleted" : "%s s’riemërtohet dot, sepse është fshirë", + "%s could not be renamed" : "%s s’riemërtohet dot", + "Upload (max. %s)" : "Ngarkim (max. %s)", + "File handling" : "Trajtim kartele", + "Maximum upload size" : "Madhësi maksimale ngarkimi", + "max. possible: " : "maks. i mundshëm: ", + "With PHP-FPM this value may take up to 5 minutes to take effect after saving." : "Me PHP-FPM kjo vlerë mund të dojë deri në 5 minuta të hyjë në fuqi, pasi të ruhet.", + "Save" : "Ruaje", + "Can not be edited from here due to insufficient permissions." : "S’mund të përpunohet që këtu, për shkak lejesh të pamjaftueshme.", + "Settings" : "Rregullime", "WebDAV" : "WebDAV", - "Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "Përdorni këtë adresë për <a href=\"%s\" target=\"_blank\">qasje në skedarët tuaj me anë të WebDAV</a>", - "Cancel upload" : "Anullo ngarkimin", - "Upload too large" : "Ngarkimi shumë i madh", - "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Skedarët që po mundoheni të ngarkoni e tejkalojnë madhësinë maksimale të lejuar nga serveri.", - "Files are being scanned, please wait." : "Skanerizimi i skedarit në proces. Ju lutem prisni.", - "Currently scanning" : "Duke skanuar" + "Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "Përdorni këtë adresë për <a href=\"%s\" target=\"_blank\">të hyrë te Kartelat tuaja përmes WebDAV-it</a>", + "Cancel upload" : "Anuloje ngarkimin", + "No files in here" : "S’ka kartela këtu", + "Upload some content or sync with your devices!" : "Ngarkoni ca lëndë ose bëni njëkohësim me pajisjet tuaja!", + "No entries found in this folder" : "Në këtë dosje s’u gjetën zëra", + "Select all" : "Përzgjidhe krejt", + "Upload too large" : "Ngarkim shumë i madh", + "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Kartelat që po rrekeni të ngarkoni e tejkalojnë madhësinë maksimale për ngarkime kartelash në këtë shërbyes.", + "Files are being scanned, please wait." : "Kartelat po kontrollohen, ju lutemi, pritni.", + "Currently scanning" : "Po kontrollohet", + "No favorites" : "Pa të parapëlqyera", + "Files and folders you mark as favorite will show up here" : "Këtu do të duken kartelat dhe dosjet që i shënoni si të parapëlqyera" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files/l10n/sq.json b/apps/files/l10n/sq.json index 993e38f4644..462f4d06392 100644 --- a/apps/files/l10n/sq.json +++ b/apps/files/l10n/sq.json @@ -1,94 +1,123 @@ { "translations": { - "Storage not available" : "Hapësira e memorizimit nuk është e disponueshme", - "Storage invalid" : "Hapësirë memorizimi e pavlefshme", - "Unknown error" : "Gabim panjohur", - "Could not move %s - File with this name already exists" : "E pa mundur zhvendosja e %s - ekziston nje skedar me te njetin emer", - "Could not move %s" : "Nuk mund të zhvendoset %s", - "Permission denied" : "Nuk ka të drejtë", - "The target folder has been moved or deleted." : "Dosja e destinacionit është zhvendosur ose fshirë.", - "The name %s is already used in the folder %s. Please choose a different name." : "Emri %s është i përdorur në dosjen %s. Ju lutem zgjidhni një emër tjetër.", - "Error when creating the file" : "Gabim gjatë krijimit të skedarit", + "Storage not available" : "Pa depozitë gati", + "Storage invalid" : "Depozitë e pavlefshme", + "Unknown error" : "Gabim i panjohur", + "Could not move %s - File with this name already exists" : "S’u zhvendos dot %s - Ka tashmë kartelë me këtë", + "Could not move %s" : "S’u zhvendos dot %s", + "Permission denied" : "Leje e mohuar", + "The target folder has been moved or deleted." : "Dosja vendmbërritje është zhvendosur ose fshirë.", + "The name %s is already used in the folder %s. Please choose a different name." : "Emri %s tashmë është i përdorur në dosjen %s. Ju lutemi, zgjidhni një emër tjetër.", + "Error when creating the file" : "Gabim gjatë krijimit të kartelës", "Error when creating the folder" : "Gabim gjatë krijimit të dosjes", - "Unable to set upload directory." : "E pa mundur të vendoset dosja e ngarkimit", - "Invalid Token" : "Shenjë e gabuar", - "No file was uploaded. Unknown error" : "Asnjë skedar nuk u dërgua. Gabim i pa njohur", - "There is no error, the file uploaded with success" : "Skedari u ngarkua me sukses", - "The uploaded file exceeds the upload_max_filesize directive in php.ini: " : "Skedari i ngarkuar tejkalon limitin hapsirës së lejuar në php.ini", - "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Skedari i ngarkuar tejlakon vlerën MAX_FILE_SIZE të përcaktuar në formën HTML", - "The uploaded file was only partially uploaded" : "Skedari është ngakruar vetëm pjesërisht", - "No file was uploaded" : "Asnjë skedar nuk është ngarkuar", - "Missing a temporary folder" : "Mungon dosja e përkohshme", + "Unable to set upload directory." : "S’arrihet të caktohet drejtori ngarkimesh", + "Invalid Token" : "Token i pavlefshëm", + "No file was uploaded. Unknown error" : "S’u ngarkua ndonjë kartelë. Gabim i panjohur", + "There is no error, the file uploaded with success" : "S’pati gabim, kartela u ngarkua me sukses", + "The uploaded file exceeds the upload_max_filesize directive in php.ini: " : "Kartela e ngarkuar tejkalon udhëzimin upload_max_filesize directive te php.ini: ", + "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Kartela e ngarkuar tejkalon udhëzimin MAX_FILE_SIZE që qe përcaktuar te formulari HTML", + "The uploaded file was only partially uploaded" : "Kartela në fjalë u ngarkua vetëm pjesërisht", + "No file was uploaded" : "S’u ngarkua kartelë", + "Missing a temporary folder" : "Mungon një dosje e përkohshme", "Failed to write to disk" : "Dështoi shkrimi në disk", - "Not enough storage available" : "Hapsira e arkivimit e pamjaftueshme", - "Upload failed. Could not find uploaded file" : "Ngarkimi dështoi. Nuk mund të gjendet skedari i ngarkuar", - "Upload failed. Could not get file info." : "Ngarkimi dështoi. Nuk mund të gjej informacion mbi skedarin.", - "Invalid directory." : "Dosje e pavlefshme", - "Files" : "Skedarë", - "All files" : "Të gjithë", - "Home" : "Shtëpi", - "Close" : "Mbyll", - "Unable to upload {filename} as it is a directory or has 0 bytes" : "Nuk mund të ngarkohet {filename} sepse është dosje ose ka 0 byte", - "Total file size {size1} exceeds upload limit {size2}" : "Përmasa totale {size1} e skedarit tejkalon limitin e ngarkimit {size2}", - "Not enough free space, you are uploading {size1} but only {size2} is left" : "Nuk ka hapësirë të mjaftueshme, ju po ngarkoni {size1} por vetëm {size2} është e lirë", - "Upload cancelled." : "Ngarkimi u anullua", - "Could not get result from server." : "Nuk mund të merret ndonjë rezultat nga serveri.", - "File upload is in progress. Leaving the page now will cancel the upload." : "Skedari duke u ngarkuar. Largimi nga faqja do të anullojë ngarkimin", - "Download" : "Shkarko", - "Rename" : "Riemëro", - "Delete" : "Fshi", - "Pending" : "Në vijim", - "Error moving file." : "Gabim në lëvizjen e skedarëve.", - "Error moving file" : "Gabim lëvizjen dokumentave", + "Not enough storage available" : "S’ka depozitë të mjaftueshme", + "Upload failed. Could not find uploaded file" : "Ngarkimi dështoi. S’u gjet dot kartela e ngarkuar", + "Upload failed. Could not get file info." : "Ngarkoi dështoi. S’u morën dot të dhëna kartele.", + "Invalid directory." : "Drejtori e pavlefshme.", + "Files" : "Kartela", + "All files" : "Krejt kartelat", + "Favorites" : "Të parapëlqyera", + "Home" : "Kreu", + "Close" : "Mbylle", + "Upload cancelled." : "Ngarkimi u anulua.", + "Unable to upload {filename} as it is a directory or has 0 bytes" : "S’arrihet të ngarkohet {filename}, ngaqë është drejtori ose ka 0 bajte", + "Total file size {size1} exceeds upload limit {size2}" : "Madhësia gjithsej e kartelës {size1} e tejkalon kufirin {size2} për ngarkimet", + "Not enough free space, you are uploading {size1} but only {size2} is left" : "Nuk ka hapësirë të mjaftueshme, po ngarkoni {size1}, por ka vetëm {size2} të lira", + "Could not get result from server." : "S’mori dot përfundime nga shërbyesi.", + "File upload is in progress. Leaving the page now will cancel the upload." : "Ngarkimi i kartelës është në punë e sipër. Largimi nga faqja do të anulojë ngarkimin.", + "Actions" : "Veprime", + "Download" : "Shkarkoje", + "Rename" : "Riemërtojeni", + "Delete" : "Fshije", + "Details" : "Hollësi", + "Select" : "Përzgjidhe", + "Pending" : "Në pritje", + "Unable to determine date" : "S’arrihet të përcaktohet data", + "This operation is forbidden" : "Ky veprim është i ndaluar", + "This directory is unavailable, please check the logs or contact the administrator" : "Kjo drejtori nuk kapet, ju lutemi, kontrolloni regjistrat ose lidhuni me përgjegjësin", + "Error moving file." : "Gabim në lëvizjen e kartelës.", + "Error moving file" : "Gabim në lëvizjen e kartelës", "Error" : "Gabim", - "{new_name} already exists" : "{new_name} është ekzistues ", - "Could not rename file" : "Riemërtimi i skedarit nuk është i mundur", - "Could not create file" : "Skedari nuk mund të krijohet", - "Could not create folder" : "I pamundur krijimi i kartelës", - "Error deleting file." : "Gabim gjatë fshirjes së skedarit.", - "Name" : "Emri", - "Size" : "Madhësia", - "Modified" : "Ndryshuar", + "{new_name} already exists" : "{new_name} ekziston tashmtë", + "Could not rename file" : "Kartela s’u riemërtua dot", + "Could not create file" : "Kartela s’u krijua dot", + "Could not create folder" : "Dosja s’u krijua dot", + "Error deleting file." : "Gabim gjatë fshirjes së kartelës.", + "No entries in this folder match '{filter}'" : "Në këtë dosje s’ka zëra me përputhje me '{filter}'", + "Name" : "Emër", + "Size" : "Madhësi", + "Modified" : "Ndryshuar më", "_%n folder_::_%n folders_" : ["%n dosje","%n dosje"], - "_%n file_::_%n files_" : ["%n skedar","%n skedarë"], + "_%n file_::_%n files_" : ["%n kartelë","%n kartela"], "{dirs} and {files}" : "{dirs} dhe {files}", - "You don’t have permission to upload or create files here" : "Ju nuk keni të drejta për të ngarkuar apo krijuar skedarë këtu", - "_Uploading %n file_::_Uploading %n files_" : ["Po ngarkoj %n skedar","Po ngarkoj %n skedarë"], + "You don’t have permission to upload or create files here" : "S’keni leje për të ngarkuar apo krijuar kartela këtu", + "_Uploading %n file_::_Uploading %n files_" : ["Po ngarkohet %n kartelë","Po ngarkohen %n kartela"], "New" : "E re", - "\"{name}\" is an invalid file name." : "\"{name}\" është emër i pavlefshëm.", - "File name cannot be empty." : "Emri i skedarit nuk mund të jetë bosh.", - "Your storage is full, files can not be updated or synced anymore!" : "Hapsira juaj e arkivimit është plot, skedarët nuk mund të përditësohen ose sinkronizohen!", - "Your storage is almost full ({usedSpacePercent}%)" : "Hapsira juaj e arkivimit është pothuajse në fund ({usedSpacePercent}%)", - "Upload" : "Ngarko", - "Text file" : "Skedar tekst", + "\"{name}\" is an invalid file name." : "\"{name}\" është emër i pavlefshëm kartele.", + "File name cannot be empty." : "Emri i kartelës s’mund të jetë i zbrazët.", + "Storage of {owner} is full, files can not be updated or synced anymore!" : "Depozita e {owner} është plot, kartelat s’mund të përditësohen ose njëkohësohen më!", + "Your storage is full, files can not be updated or synced anymore!" : "Depozita juaj është plot, kartelat s’mund të përditësohen ose njëkohësohen më!", + "Storage of {owner} is almost full ({usedSpacePercent}%)" : "Depozita e {owner} është thuasje plot ({usedSpacePercent}%)", + "Your storage is almost full ({usedSpacePercent}%)" : "Depozita juaj është thuajse plot ({usedSpacePercent}%)", + "Path" : "Shteg", + "_%n byte_::_%n bytes_" : ["%n bajt","%n bajte"], + "Favorited" : "U kalua e parapëlqyer", + "Favorite" : "E parapëlqyer", + "{newname} already exists" : "Ka tashmë një {newname}", + "Upload" : "Ngarkoje", + "Text file" : "Kartelë tekst", + "New text file.txt" : "Kartelë e re file.txt", "Folder" : "Dosje", - "New folder" : "Dosje e're", - "A new file or folder has been <strong>created</strong>" : "Një skedar ose një dosje e re është <strong>krijuar</strong>", - "A file or folder has been <strong>changed</strong>" : "Një skedar ose një dosje ka <strong>ndryshuar</strong>", - "A file or folder has been <strong>deleted</strong>" : "Një skedar ose një dosje është <strong>fshirë</strong>", - "A file or folder has been <strong>restored</strong>" : "Një skedar ose dosje është <strong>rikthyer</strong>", - "You created %1$s" : "Ju krijuat %1$s", - "%2$s created %1$s" : "%2$s krijuar %1$s", - "%1$s was created in a public folder" : "%1$s është krijuar në një dosje publike", - "You changed %1$s" : "Ju ndryshuat %1$s", - "%2$s changed %1$s" : "%2$s ndryshuar %1$s", - "You deleted %1$s" : "Ju fshitë %1$s", - "%2$s deleted %1$s" : "%2$s fshirë %1$s", - "You restored %1$s" : "Ju rikthyet %1$s", + "New folder" : "Dosje e re", + "An error occurred while trying to update the tags" : "Ndodhi një gabim teksa provohej të përditësoheshin etiketat", + "A new file or folder has been <strong>created</strong>" : "<strong>U krijua</strong> një kartelë ose dosje e re", + "A file or folder has been <strong>changed</strong>" : "<strong>U ndryshua</strong> një kartelë ose dosje", + "Limit notifications about creation and changes to your <strong>favorite files</strong> <em>(Stream only)</em>" : "Kufizojini njoftimet mbi krijim dhe ndryshim kartelash vetëm për <strong>kartelat tuaja të parapëlqyera</strong> <em>(Vetëm te rrjedha)</em>", + "A file or folder has been <strong>deleted</strong>" : "<strong>U fshi</strong> një kartelë ose dosje", + "A file or folder has been <strong>restored</strong>" : "<strong>U rikthye</strong> një kartelë ose dosje", + "You created %1$s" : "Krijuat %1$s", + "%2$s created %1$s" : "%2$s krijoi %1$s", + "%1$s was created in a public folder" : "%1$s u krijua në një dosje publike", + "You changed %1$s" : "Ndryshuat %1$s", + "%2$s changed %1$s" : "%2$s ndryshoi %1$s", + "You deleted %1$s" : "Fshitë %1$s", + "%2$s deleted %1$s" : "%2$s fshiu %1$s", + "You restored %1$s" : "Rikthyet %1$s", "%2$s restored %1$s" : "%2$s riktheu %1$s", - "%s could not be renamed as it has been deleted" : "%s nuk mund të riemërtohet sepse është fshirë", - "%s could not be renamed" : "Nuk është i mundur riemërtimi i %s", - "Upload (max. %s)" : "Ngarko (maks. %s)", - "File handling" : "Trajtimi i Skedarëve", - "Maximum upload size" : "Madhësia maksimale e nagarkimit", - "max. possible: " : "maks i mundshëm", - "Save" : "Ruaj", - "Settings" : "Konfigurime", + "Changed by %2$s" : "Ndryshuar nga %2$s", + "Deleted by %2$s" : "Fshirë nga %2$s", + "Restored by %2$s" : "Rikthyer nga %2$s", + "%s could not be renamed as it has been deleted" : "%s s’riemërtohet dot, sepse është fshirë", + "%s could not be renamed" : "%s s’riemërtohet dot", + "Upload (max. %s)" : "Ngarkim (max. %s)", + "File handling" : "Trajtim kartele", + "Maximum upload size" : "Madhësi maksimale ngarkimi", + "max. possible: " : "maks. i mundshëm: ", + "With PHP-FPM this value may take up to 5 minutes to take effect after saving." : "Me PHP-FPM kjo vlerë mund të dojë deri në 5 minuta të hyjë në fuqi, pasi të ruhet.", + "Save" : "Ruaje", + "Can not be edited from here due to insufficient permissions." : "S’mund të përpunohet që këtu, për shkak lejesh të pamjaftueshme.", + "Settings" : "Rregullime", "WebDAV" : "WebDAV", - "Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "Përdorni këtë adresë për <a href=\"%s\" target=\"_blank\">qasje në skedarët tuaj me anë të WebDAV</a>", - "Cancel upload" : "Anullo ngarkimin", - "Upload too large" : "Ngarkimi shumë i madh", - "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Skedarët që po mundoheni të ngarkoni e tejkalojnë madhësinë maksimale të lejuar nga serveri.", - "Files are being scanned, please wait." : "Skanerizimi i skedarit në proces. Ju lutem prisni.", - "Currently scanning" : "Duke skanuar" + "Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "Përdorni këtë adresë për <a href=\"%s\" target=\"_blank\">të hyrë te Kartelat tuaja përmes WebDAV-it</a>", + "Cancel upload" : "Anuloje ngarkimin", + "No files in here" : "S’ka kartela këtu", + "Upload some content or sync with your devices!" : "Ngarkoni ca lëndë ose bëni njëkohësim me pajisjet tuaja!", + "No entries found in this folder" : "Në këtë dosje s’u gjetën zëra", + "Select all" : "Përzgjidhe krejt", + "Upload too large" : "Ngarkim shumë i madh", + "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Kartelat që po rrekeni të ngarkoni e tejkalojnë madhësinë maksimale për ngarkime kartelash në këtë shërbyes.", + "Files are being scanned, please wait." : "Kartelat po kontrollohen, ju lutemi, pritni.", + "Currently scanning" : "Po kontrollohet", + "No favorites" : "Pa të parapëlqyera", + "Files and folders you mark as favorite will show up here" : "Këtu do të duken kartelat dhe dosjet që i shënoni si të parapëlqyera" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files/l10n/sv.js b/apps/files/l10n/sv.js index a5dc7478f80..19f42fb144b 100644 --- a/apps/files/l10n/sv.js +++ b/apps/files/l10n/sv.js @@ -30,10 +30,10 @@ OC.L10N.register( "Favorites" : "Favoriter", "Home" : "Hem", "Close" : "Stäng", + "Upload cancelled." : "Uppladdning avbruten.", "Unable to upload {filename} as it is a directory or has 0 bytes" : "Kan inte ladda upp {filename} eftersom den antingen är en mapp eller har 0 bytes.", "Total file size {size1} exceeds upload limit {size2}" : "Totala filstorleken {size1} överskrider uppladdningsgränsen {size2}", "Not enough free space, you are uploading {size1} but only {size2} is left" : "Inte tillräckligt med ledigt utrymme, du laddar upp {size1} men endast {size2} finns kvar.", - "Upload cancelled." : "Uppladdning avbruten.", "Could not get result from server." : "Gick inte att hämta resultat från server.", "File upload is in progress. Leaving the page now will cancel the upload." : "Filuppladdning pågår. Lämnar du sidan så avbryts uppladdningen.", "Actions" : "Åtgärder", @@ -44,6 +44,8 @@ OC.L10N.register( "Select" : "Välj", "Pending" : "Väntar", "Unable to determine date" : "Misslyckades avgöra datum", + "This operation is forbidden" : "Denna operation är förbjuden", + "This directory is unavailable, please check the logs or contact the administrator" : "Denna katalog är inte tillgänglig, kontrollera loggarna eller kontakta administratören", "Error moving file." : "Fel vid flytt av fil.", "Error moving file" : "Fel uppstod vid flyttning av fil", "Error" : "Fel", @@ -52,6 +54,7 @@ OC.L10N.register( "Could not create file" : "Kunde ej skapa fil", "Could not create folder" : "Kunde ej skapa katalog", "Error deleting file." : "Kunde inte ta bort filen.", + "No entries in this folder match '{filter}'" : "Inga poster i denna mapp match \"{filter}\"", "Name" : "Namn", "Size" : "Storlek", "Modified" : "Ändrad", @@ -63,14 +66,20 @@ OC.L10N.register( "New" : "Ny", "\"{name}\" is an invalid file name." : "\"{name}\" är ett ogiltligt filnamn.", "File name cannot be empty." : "Filnamn kan inte vara tomt.", + "Storage of {owner} is full, files can not be updated or synced anymore!" : "Lagring av {owner} är full, filer kan inte uppdateras eller synkroniseras längre!", "Your storage is full, files can not be updated or synced anymore!" : "Ditt lagringsutrymme är fullt, filer kan inte längre uppdateras eller synkroniseras!", + "Storage of {owner} is almost full ({usedSpacePercent}%)" : "Lagring av {owner} är nästan full ({usedSpacePercent}%)", "Your storage is almost full ({usedSpacePercent}%)" : "Ditt lagringsutrymme är nästan fullt ({usedSpacePercent}%)", + "Path" : "sökväg", "Favorited" : "Favoritiserad", "Favorite" : "Favorit", + "{newname} already exists" : "{newname} existerar redan", "Upload" : "Ladda upp", "Text file" : "Textfil", + "New text file.txt" : "nytextfil.txt", "Folder" : "Mapp", "New folder" : "Ny mapp", + "An error occurred while trying to update the tags" : "Ett fel uppstod när uppdatera taggarna", "A new file or folder has been <strong>created</strong>" : "En ny fil eller mapp har blivit <strong>skapad</strong>", "A file or folder has been <strong>changed</strong>" : "En ny fil eller mapp har blivit <strong>ändrad</strong>", "A file or folder has been <strong>deleted</strong>" : "En ny fil eller mapp har blivit <strong>raderad</strong>", @@ -84,18 +93,25 @@ OC.L10N.register( "%2$s deleted %1$s" : "%2$s raderade %1$s", "You restored %1$s" : "Du återkapade %1$s", "%2$s restored %1$s" : "%2$s återskapade %1$s", + "Changed by %2$s" : "Ändrad av %2$s", + "Deleted by %2$s" : "Bortagen av %2$s", + "Restored by %2$s" : "Återställd av %2$s", "%s could not be renamed as it has been deleted" : "%s kan inte döpas om eftersom den har raderats", "%s could not be renamed" : "%s kunde inte namnändras", "Upload (max. %s)" : "Ladda upp (max. %s)", "File handling" : "Filhantering", "Maximum upload size" : "Maximal storlek att ladda upp", "max. possible: " : "max. möjligt:", + "With PHP-FPM this value may take up to 5 minutes to take effect after saving." : "Med PHP-FPM detta värde kan ta upp till 5 minuter för att träda i kraft efter att ha sparat.", "Save" : "Spara", + "Can not be edited from here due to insufficient permissions." : "Kan inte redigeras härifrån på grund av otillräcklig behörighet.", "Settings" : "Inställningar", "WebDAV" : "WebDAV", "Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "Använd denna adress till <a href=\"%s\" target=\"_blank\">nå dina Filer via WebDAV</a>", "Cancel upload" : "Avbryt uppladdning", + "No files in here" : "Inga filer kunde hittas", "Upload some content or sync with your devices!" : "Ladda upp innehåll eller synkronisera med dina enheter!", + "No entries found in this folder" : "nga Filer hittades i denna mapp", "Select all" : "Välj allt", "Upload too large" : "För stor uppladdning", "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Filerna du försöker ladda upp överstiger den maximala storleken för filöverföringar på servern.", diff --git a/apps/files/l10n/sv.json b/apps/files/l10n/sv.json index afcbb6100db..ea512d4f54b 100644 --- a/apps/files/l10n/sv.json +++ b/apps/files/l10n/sv.json @@ -28,10 +28,10 @@ "Favorites" : "Favoriter", "Home" : "Hem", "Close" : "Stäng", + "Upload cancelled." : "Uppladdning avbruten.", "Unable to upload {filename} as it is a directory or has 0 bytes" : "Kan inte ladda upp {filename} eftersom den antingen är en mapp eller har 0 bytes.", "Total file size {size1} exceeds upload limit {size2}" : "Totala filstorleken {size1} överskrider uppladdningsgränsen {size2}", "Not enough free space, you are uploading {size1} but only {size2} is left" : "Inte tillräckligt med ledigt utrymme, du laddar upp {size1} men endast {size2} finns kvar.", - "Upload cancelled." : "Uppladdning avbruten.", "Could not get result from server." : "Gick inte att hämta resultat från server.", "File upload is in progress. Leaving the page now will cancel the upload." : "Filuppladdning pågår. Lämnar du sidan så avbryts uppladdningen.", "Actions" : "Åtgärder", @@ -42,6 +42,8 @@ "Select" : "Välj", "Pending" : "Väntar", "Unable to determine date" : "Misslyckades avgöra datum", + "This operation is forbidden" : "Denna operation är förbjuden", + "This directory is unavailable, please check the logs or contact the administrator" : "Denna katalog är inte tillgänglig, kontrollera loggarna eller kontakta administratören", "Error moving file." : "Fel vid flytt av fil.", "Error moving file" : "Fel uppstod vid flyttning av fil", "Error" : "Fel", @@ -50,6 +52,7 @@ "Could not create file" : "Kunde ej skapa fil", "Could not create folder" : "Kunde ej skapa katalog", "Error deleting file." : "Kunde inte ta bort filen.", + "No entries in this folder match '{filter}'" : "Inga poster i denna mapp match \"{filter}\"", "Name" : "Namn", "Size" : "Storlek", "Modified" : "Ändrad", @@ -61,14 +64,20 @@ "New" : "Ny", "\"{name}\" is an invalid file name." : "\"{name}\" är ett ogiltligt filnamn.", "File name cannot be empty." : "Filnamn kan inte vara tomt.", + "Storage of {owner} is full, files can not be updated or synced anymore!" : "Lagring av {owner} är full, filer kan inte uppdateras eller synkroniseras längre!", "Your storage is full, files can not be updated or synced anymore!" : "Ditt lagringsutrymme är fullt, filer kan inte längre uppdateras eller synkroniseras!", + "Storage of {owner} is almost full ({usedSpacePercent}%)" : "Lagring av {owner} är nästan full ({usedSpacePercent}%)", "Your storage is almost full ({usedSpacePercent}%)" : "Ditt lagringsutrymme är nästan fullt ({usedSpacePercent}%)", + "Path" : "sökväg", "Favorited" : "Favoritiserad", "Favorite" : "Favorit", + "{newname} already exists" : "{newname} existerar redan", "Upload" : "Ladda upp", "Text file" : "Textfil", + "New text file.txt" : "nytextfil.txt", "Folder" : "Mapp", "New folder" : "Ny mapp", + "An error occurred while trying to update the tags" : "Ett fel uppstod när uppdatera taggarna", "A new file or folder has been <strong>created</strong>" : "En ny fil eller mapp har blivit <strong>skapad</strong>", "A file or folder has been <strong>changed</strong>" : "En ny fil eller mapp har blivit <strong>ändrad</strong>", "A file or folder has been <strong>deleted</strong>" : "En ny fil eller mapp har blivit <strong>raderad</strong>", @@ -82,18 +91,25 @@ "%2$s deleted %1$s" : "%2$s raderade %1$s", "You restored %1$s" : "Du återkapade %1$s", "%2$s restored %1$s" : "%2$s återskapade %1$s", + "Changed by %2$s" : "Ändrad av %2$s", + "Deleted by %2$s" : "Bortagen av %2$s", + "Restored by %2$s" : "Återställd av %2$s", "%s could not be renamed as it has been deleted" : "%s kan inte döpas om eftersom den har raderats", "%s could not be renamed" : "%s kunde inte namnändras", "Upload (max. %s)" : "Ladda upp (max. %s)", "File handling" : "Filhantering", "Maximum upload size" : "Maximal storlek att ladda upp", "max. possible: " : "max. möjligt:", + "With PHP-FPM this value may take up to 5 minutes to take effect after saving." : "Med PHP-FPM detta värde kan ta upp till 5 minuter för att träda i kraft efter att ha sparat.", "Save" : "Spara", + "Can not be edited from here due to insufficient permissions." : "Kan inte redigeras härifrån på grund av otillräcklig behörighet.", "Settings" : "Inställningar", "WebDAV" : "WebDAV", "Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "Använd denna adress till <a href=\"%s\" target=\"_blank\">nå dina Filer via WebDAV</a>", "Cancel upload" : "Avbryt uppladdning", + "No files in here" : "Inga filer kunde hittas", "Upload some content or sync with your devices!" : "Ladda upp innehåll eller synkronisera med dina enheter!", + "No entries found in this folder" : "nga Filer hittades i denna mapp", "Select all" : "Välj allt", "Upload too large" : "För stor uppladdning", "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Filerna du försöker ladda upp överstiger den maximala storleken för filöverföringar på servern.", diff --git a/apps/files/l10n/zh_TW.js b/apps/files/l10n/zh_TW.js index 772cf083307..ad232d3ff64 100644 --- a/apps/files/l10n/zh_TW.js +++ b/apps/files/l10n/zh_TW.js @@ -30,10 +30,10 @@ OC.L10N.register( "Favorites" : "最愛", "Home" : "住宅", "Close" : " 關閉", + "Upload cancelled." : "上傳已取消", "Unable to upload {filename} as it is a directory or has 0 bytes" : "因為 {filename} 是個目錄或是大小為零,所以無法上傳", "Total file size {size1} exceeds upload limit {size2}" : "檔案大小總和 {size1} 超過上傳限制 {size2}", "Not enough free space, you are uploading {size1} but only {size2} is left" : "可用空間不足,你正要上傳 {size1} 可是只剩下 {size2}", - "Upload cancelled." : "上傳已取消", "Could not get result from server." : "無法從伺服器取回結果", "File upload is in progress. Leaving the page now will cancel the upload." : "檔案上傳中,離開此頁面將會取消上傳。", "Actions" : "動作", @@ -45,6 +45,7 @@ OC.L10N.register( "Pending" : "等候中", "Unable to determine date" : "無法確定日期", "This operation is forbidden" : "此動作被禁止", + "This directory is unavailable, please check the logs or contact the administrator" : "這個目錄無法存取,請檢查伺服器記錄檔或聯絡管理員", "Error moving file." : "移動檔案發生錯誤", "Error moving file" : "移動檔案失敗", "Error" : "錯誤", @@ -69,17 +70,21 @@ OC.L10N.register( "Your storage is full, files can not be updated or synced anymore!" : "您的儲存空間已滿,沒有辦法再更新或是同步檔案!", "Storage of {owner} is almost full ({usedSpacePercent}%)" : "{owner} 的儲存空間快要滿了 ({usedSpacePercent}%)", "Your storage is almost full ({usedSpacePercent}%)" : "您的儲存空間快要滿了 ({usedSpacePercent}%)", + "_matches '{filter}'_::_match '{filter}'_" : ["符合「{filter}」"], "Path" : "路徑", "_%n byte_::_%n bytes_" : ["%n 位元組"], "Favorited" : "已加入最愛", "Favorite" : "我的最愛", + "{newname} already exists" : "{newname} 已經存在", "Upload" : "上傳", "Text file" : "文字檔", + "New text file.txt" : "新文字檔.txt", "Folder" : "資料夾", "New folder" : "新資料夾", "An error occurred while trying to update the tags" : "更新標籤時發生錯誤", "A new file or folder has been <strong>created</strong>" : "新的檔案或目錄已被 <strong>建立</strong>", "A file or folder has been <strong>changed</strong>" : "檔案或目錄已被 <strong>變更</strong>", + "Limit notifications about creation and changes to your <strong>favorite files</strong> <em>(Stream only)</em>" : "僅顯示<strong>已加星號的檔案</strong>的動態通知<em>(僅限訊息串)</em>", "A file or folder has been <strong>deleted</strong>" : "檔案或目錄已被 <strong>刪除</strong>", "A file or folder has been <strong>restored</strong>" : "檔案或目錄已被 <strong>恢復</strong>", "You created %1$s" : "您建立了 %1$s", @@ -91,13 +96,18 @@ OC.L10N.register( "%2$s deleted %1$s" : "%2$s 已刪除 %1$s", "You restored %1$s" : "您恢復了 %1$s", "%2$s restored %1$s" : "%2$s 已恢復了 %1$s", + "Changed by %2$s" : "由 %2$s 改動", + "Deleted by %2$s" : "由 %2$s 刪除", + "Restored by %2$s" : "由 %2$s 復原", "%s could not be renamed as it has been deleted" : "%s 已經被刪除了所以無法重新命名", "%s could not be renamed" : "無法重新命名 %s", "Upload (max. %s)" : "上傳(至多 %s)", "File handling" : "檔案處理", "Maximum upload size" : "上傳限制", "max. possible: " : "最大允許:", + "With PHP-FPM this value may take up to 5 minutes to take effect after saving." : "如果使用 PHP-FPM ,此設定值需要5分鐘左右才會生效", "Save" : "儲存", + "Can not be edited from here due to insufficient permissions." : "無法在這邊編輯,因為權限不足", "Settings" : "設定", "WebDAV" : "WebDAV", "Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "使用這個地址<a href=\"%s\" target=\"_blank\">來透過 WebDAV 存取檔案</a>", diff --git a/apps/files/l10n/zh_TW.json b/apps/files/l10n/zh_TW.json index 4e1a38eccbd..85ca6b2af4d 100644 --- a/apps/files/l10n/zh_TW.json +++ b/apps/files/l10n/zh_TW.json @@ -28,10 +28,10 @@ "Favorites" : "最愛", "Home" : "住宅", "Close" : " 關閉", + "Upload cancelled." : "上傳已取消", "Unable to upload {filename} as it is a directory or has 0 bytes" : "因為 {filename} 是個目錄或是大小為零,所以無法上傳", "Total file size {size1} exceeds upload limit {size2}" : "檔案大小總和 {size1} 超過上傳限制 {size2}", "Not enough free space, you are uploading {size1} but only {size2} is left" : "可用空間不足,你正要上傳 {size1} 可是只剩下 {size2}", - "Upload cancelled." : "上傳已取消", "Could not get result from server." : "無法從伺服器取回結果", "File upload is in progress. Leaving the page now will cancel the upload." : "檔案上傳中,離開此頁面將會取消上傳。", "Actions" : "動作", @@ -43,6 +43,7 @@ "Pending" : "等候中", "Unable to determine date" : "無法確定日期", "This operation is forbidden" : "此動作被禁止", + "This directory is unavailable, please check the logs or contact the administrator" : "這個目錄無法存取,請檢查伺服器記錄檔或聯絡管理員", "Error moving file." : "移動檔案發生錯誤", "Error moving file" : "移動檔案失敗", "Error" : "錯誤", @@ -67,17 +68,21 @@ "Your storage is full, files can not be updated or synced anymore!" : "您的儲存空間已滿,沒有辦法再更新或是同步檔案!", "Storage of {owner} is almost full ({usedSpacePercent}%)" : "{owner} 的儲存空間快要滿了 ({usedSpacePercent}%)", "Your storage is almost full ({usedSpacePercent}%)" : "您的儲存空間快要滿了 ({usedSpacePercent}%)", + "_matches '{filter}'_::_match '{filter}'_" : ["符合「{filter}」"], "Path" : "路徑", "_%n byte_::_%n bytes_" : ["%n 位元組"], "Favorited" : "已加入最愛", "Favorite" : "我的最愛", + "{newname} already exists" : "{newname} 已經存在", "Upload" : "上傳", "Text file" : "文字檔", + "New text file.txt" : "新文字檔.txt", "Folder" : "資料夾", "New folder" : "新資料夾", "An error occurred while trying to update the tags" : "更新標籤時發生錯誤", "A new file or folder has been <strong>created</strong>" : "新的檔案或目錄已被 <strong>建立</strong>", "A file or folder has been <strong>changed</strong>" : "檔案或目錄已被 <strong>變更</strong>", + "Limit notifications about creation and changes to your <strong>favorite files</strong> <em>(Stream only)</em>" : "僅顯示<strong>已加星號的檔案</strong>的動態通知<em>(僅限訊息串)</em>", "A file or folder has been <strong>deleted</strong>" : "檔案或目錄已被 <strong>刪除</strong>", "A file or folder has been <strong>restored</strong>" : "檔案或目錄已被 <strong>恢復</strong>", "You created %1$s" : "您建立了 %1$s", @@ -89,13 +94,18 @@ "%2$s deleted %1$s" : "%2$s 已刪除 %1$s", "You restored %1$s" : "您恢復了 %1$s", "%2$s restored %1$s" : "%2$s 已恢復了 %1$s", + "Changed by %2$s" : "由 %2$s 改動", + "Deleted by %2$s" : "由 %2$s 刪除", + "Restored by %2$s" : "由 %2$s 復原", "%s could not be renamed as it has been deleted" : "%s 已經被刪除了所以無法重新命名", "%s could not be renamed" : "無法重新命名 %s", "Upload (max. %s)" : "上傳(至多 %s)", "File handling" : "檔案處理", "Maximum upload size" : "上傳限制", "max. possible: " : "最大允許:", + "With PHP-FPM this value may take up to 5 minutes to take effect after saving." : "如果使用 PHP-FPM ,此設定值需要5分鐘左右才會生效", "Save" : "儲存", + "Can not be edited from here due to insufficient permissions." : "無法在這邊編輯,因為權限不足", "Settings" : "設定", "WebDAV" : "WebDAV", "Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "使用這個地址<a href=\"%s\" target=\"_blank\">來透過 WebDAV 存取檔案</a>", diff --git a/apps/files/lib/capabilities.php b/apps/files/lib/capabilities.php index 2e19283e4d6..14fb07a9d86 100644 --- a/apps/files/lib/capabilities.php +++ b/apps/files/lib/capabilities.php @@ -1,7 +1,7 @@ <?php /** * @author Christopher Schäpers <kondou@ts.unde.re> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Tom Needham <tom@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files/lib/helper.php b/apps/files/lib/helper.php index 6bfdc0a095c..fb14cea731f 100644 --- a/apps/files/lib/helper.php +++ b/apps/files/lib/helper.php @@ -137,10 +137,8 @@ class Helper { $entry['id'] = $i['fileid']; $entry['parentId'] = $i['parent']; - $entry['date'] = \OCP\Util::formatDate($i['mtime']); $entry['mtime'] = $i['mtime'] * 1000; // only pick out the needed attributes - $entry['icon'] = \OCA\Files\Helper::determineIcon($i); if (\OC::$server->getPreviewManager()->isAvailable($i)) { $entry['isPreviewAvailable'] = true; } diff --git a/apps/files/templates/simplelist.php b/apps/files/templates/simplelist.php index 6b6c018024f..ca26d9b5d5b 100644 --- a/apps/files/templates/simplelist.php +++ b/apps/files/templates/simplelist.php @@ -1,6 +1,3 @@ -<div id="controls"> - <div id="file_action_panel"></div> -</div> <div id='notification'></div> <div id="emptycontent" class="hidden"> diff --git a/apps/files/tests/ajax_rename.php b/apps/files/tests/ajax_rename.php index 45d49c9549d..859c7042b89 100644 --- a/apps/files/tests/ajax_rename.php +++ b/apps/files/tests/ajax_rename.php @@ -5,7 +5,6 @@ * @author Joas Schilling <nickvergessen@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> - * @author tbartenstein <tbartenstein@users.noreply.github.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * @@ -117,9 +116,6 @@ class Test_OC_Files_App_Rename extends \Test\TestCase { $this->assertEquals('abcdef', $result['data']['etag']); $this->assertFalse(isset($result['data']['tags'])); $this->assertEquals('/', $result['data']['path']); - $icon = \OC_Helper::mimetypeIcon('dir-external'); - $icon = substr($icon, 0, -3) . 'svg'; - $this->assertEquals($icon, $result['data']['icon']); } /** @@ -182,9 +178,6 @@ class Test_OC_Files_App_Rename extends \Test\TestCase { $this->assertEquals('abcdef', $result['data']['etag']); $this->assertEquals(array('tag1', 'tag2'), $result['data']['tags']); $this->assertEquals('/', $result['data']['path']); - $icon = \OC_Helper::mimetypeIcon('text'); - $icon = substr($icon, 0, -3) . 'svg'; - $this->assertEquals($icon, $result['data']['icon']); \OC::$server->registerService('TagManager', function ($c) use ($oldTagManager) { return $oldTagManager; diff --git a/apps/files/tests/command/deleteorphanedfilestest.php b/apps/files/tests/command/deleteorphanedfilestest.php index 76fe9dbdfa0..a667dba99fc 100644 --- a/apps/files/tests/command/deleteorphanedfilestest.php +++ b/apps/files/tests/command/deleteorphanedfilestest.php @@ -1,6 +1,7 @@ <?php /** * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 @@ -22,6 +23,7 @@ namespace OCA\Files\Tests\Command; use OCA\Files\Command\DeleteOrphanedFiles; +use OCP\Files\StorageNotAvailableException; class DeleteOrphanedFilesTest extends \Test\TestCase { @@ -110,7 +112,11 @@ class DeleteOrphanedFilesTest extends \Test\TestCase { $this->assertCount(0, $this->getFile($fileInfo->getId()), 'Asserts that file gets cleaned up'); - $view->unlink('files/test'); + // since we deleted the storage it might throw a (valid) StorageNotAvailableException + try { + $view->unlink('files/test'); + } catch (StorageNotAvailableException $e) { + } } } diff --git a/apps/files/tests/controller/apicontrollertest.php b/apps/files/tests/controller/apicontrollertest.php index 35d00af75ba..fb728d5eff0 100644 --- a/apps/files/tests/controller/apicontrollertest.php +++ b/apps/files/tests/controller/apicontrollertest.php @@ -1,9 +1,8 @@ <?php /** - * @author Joas Schilling <nickvergessen@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 @@ -110,9 +109,7 @@ class ApiControllerTest extends TestCase { [ 'id' => null, 'parentId' => null, - 'date' => \OCP\Util::formatDate(55), 'mtime' => 55000, - 'icon' => \OCA\Files\Helper::determineIcon($fileInfo), 'name' => 'root.txt', 'permissions' => null, 'mimetype' => 'application/pdf', @@ -175,9 +172,7 @@ class ApiControllerTest extends TestCase { [ 'id' => null, 'parentId' => null, - 'date' => \OCP\Util::formatDate(55), 'mtime' => 55000, - 'icon' => \OCA\Files\Helper::determineIcon($fileInfo1), 'name' => 'root.txt', 'permissions' => null, 'mimetype' => 'application/pdf', @@ -194,9 +189,7 @@ class ApiControllerTest extends TestCase { [ 'id' => null, 'parentId' => null, - 'date' => \OCP\Util::formatDate(999), 'mtime' => 999000, - 'icon' => \OCA\Files\Helper::determineIcon($fileInfo2), 'name' => 'root.txt', 'permissions' => null, 'mimetype' => 'application/binary', diff --git a/apps/files/tests/js/fileUploadSpec.js b/apps/files/tests/js/fileUploadSpec.js index cad8468d1c8..a49a5d4e2e0 100644 --- a/apps/files/tests/js/fileUploadSpec.js +++ b/apps/files/tests/js/fileUploadSpec.js @@ -117,4 +117,100 @@ describe('OC.Upload tests', function() { ); }); }); + describe('Upload conflicts', function() { + var oldFileList; + var conflictDialogStub; + var callbacks; + + beforeEach(function() { + oldFileList = FileList; + $('#testArea').append( + '<div id="tableContainer">' + + '<table id="filestable">' + + '<thead><tr>' + + '<th id="headerName" class="hidden column-name">' + + '<input type="checkbox" id="select_all_files" class="select-all">' + + '<a class="name columntitle" data-sort="name"><span>Name</span><span class="sort-indicator"></span></a>' + + '<span id="selectedActionsList" class="selectedActions hidden">' + + '<a href class="download"><img src="actions/download.svg">Download</a>' + + '<a href class="delete-selected">Delete</a></span>' + + '</th>' + + '<th class="hidden column-size"><a class="columntitle" data-sort="size"><span class="sort-indicator"></span></a></th>' + + '<th class="hidden column-mtime"><a class="columntitle" data-sort="mtime"><span class="sort-indicator"></span></a></th>' + + '</tr></thead>' + + '<tbody id="fileList"></tbody>' + + '<tfoot></tfoot>' + + '</table>' + + '</div>' + ); + FileList = new OCA.Files.FileList($('#tableContainer')); + + FileList.add({name: 'conflict.txt', mimetype: 'text/plain'}); + FileList.add({name: 'conflict2.txt', mimetype: 'text/plain'}); + + conflictDialogStub = sinon.stub(OC.dialogs, 'fileexists'); + callbacks = { + onNoConflicts: sinon.stub() + }; + }); + afterEach(function() { + conflictDialogStub.restore(); + + FileList.destroy(); + FileList = oldFileList; + }); + it('does not show conflict dialog when no client side conflict', function() { + var selection = { + // yes, the format of uploads is weird... + uploads: [ + {files: [{name: 'noconflict.txt'}]}, + {files: [{name: 'noconflict2.txt'}]} + ] + }; + + OC.Upload.checkExistingFiles(selection, callbacks); + + expect(conflictDialogStub.notCalled).toEqual(true); + expect(callbacks.onNoConflicts.calledOnce).toEqual(true); + expect(callbacks.onNoConflicts.calledWith(selection)).toEqual(true); + }); + it('shows conflict dialog when no client side conflict', function() { + var selection = { + // yes, the format of uploads is weird... + uploads: [ + {files: [{name: 'conflict.txt'}]}, + {files: [{name: 'conflict2.txt'}]}, + {files: [{name: 'noconflict.txt'}]} + ] + }; + + var deferred = $.Deferred(); + conflictDialogStub.returns(deferred.promise()); + deferred.resolve(); + + OC.Upload.checkExistingFiles(selection, callbacks); + + expect(conflictDialogStub.callCount).toEqual(3); + expect(conflictDialogStub.getCall(1).args[0]) + .toEqual({files: [ { name: 'conflict.txt' } ]}); + expect(conflictDialogStub.getCall(1).args[1]) + .toEqual({ name: 'conflict.txt', mimetype: 'text/plain', directory: '/' }); + expect(conflictDialogStub.getCall(1).args[2]).toEqual({ name: 'conflict.txt' }); + + // yes, the dialog must be called several times... + expect(conflictDialogStub.getCall(2).args[0]).toEqual({ + files: [ { name: 'conflict2.txt' } ] + }); + expect(conflictDialogStub.getCall(2).args[1]) + .toEqual({ name: 'conflict2.txt', mimetype: 'text/plain', directory: '/' }); + expect(conflictDialogStub.getCall(2).args[2]).toEqual({ name: 'conflict2.txt' }); + + expect(callbacks.onNoConflicts.calledOnce).toEqual(true); + expect(callbacks.onNoConflicts.calledWith({ + uploads: [ + {files: [{name: 'noconflict.txt'}]} + ] + })).toEqual(true); + }); + }); }); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 96018917c85..994e1d32844 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -1879,15 +1879,54 @@ describe('OCA.Files.FileList tests', function() { $tr2.find('td.filename .name').trigger(e); expect(fileList.getSelectedFiles().length).toEqual(0); }); - }) + }); }); describe('Details sidebar', function() { beforeEach(function() { fileList.setFiles(testFiles); fileList.showDetailsView('Two.jpg'); }); + describe('registering', function() { + var addTabStub; + var addDetailStub; + + beforeEach(function() { + addTabStub = sinon.stub(OCA.Files.DetailsView.prototype, 'addTabView'); + addDetailStub = sinon.stub(OCA.Files.DetailsView.prototype, 'addDetailView'); + }); + afterEach(function() { + addTabStub.restore(); + addDetailStub.restore(); + }); + it('forward the registered views to the underlying DetailsView', function() { + fileList.destroy(); + fileList = new OCA.Files.FileList($('#app-content-files'), { + detailsViewEnabled: true + }); + fileList.registerTabView(new OCA.Files.DetailTabView()); + fileList.registerDetailView(new OCA.Files.DetailFileInfoView()); + + expect(addTabStub.calledOnce).toEqual(true); + // twice because the filelist already registers one by default + expect(addDetailStub.calledTwice).toEqual(true); + }); + it('does not error when registering panels when not details view configured', function() { + fileList.destroy(); + fileList = new OCA.Files.FileList($('#app-content-files'), { + detailsViewEnabled: false + }); + fileList.registerTabView(new OCA.Files.DetailTabView()); + fileList.registerDetailView(new OCA.Files.DetailFileInfoView()); + + expect(addTabStub.notCalled).toEqual(true); + expect(addDetailStub.notCalled).toEqual(true); + }); + }); it('triggers file action when clicking on row if no details view configured', function() { - fileList._detailsView = null; + fileList.destroy(); + fileList = new OCA.Files.FileList($('#app-content-files'), { + detailsViewEnabled: false + }); var updateDetailsViewStub = sinon.stub(fileList, '_updateDetailsView'); var actionStub = sinon.stub(); fileList.setFiles(testFiles); diff --git a/apps/files_external/3rdparty/composer.json b/apps/files_external/3rdparty/composer.json index ad007df23de..4ae2b7d0113 100644 --- a/apps/files_external/3rdparty/composer.json +++ b/apps/files_external/3rdparty/composer.json @@ -3,7 +3,9 @@ "description": "3rdparty components for files_external", "license": "MIT", "config": { - "vendor-dir": "." + "vendor-dir": ".", + "optimize-autoloader": true, + "classmap-authoritative": true }, "require": { "icewind/smb": "1.0.4", diff --git a/apps/files_external/3rdparty/composer/autoload_classmap.php b/apps/files_external/3rdparty/composer/autoload_classmap.php index 1bd6482f9ea..d5b0d51e82d 100644 --- a/apps/files_external/3rdparty/composer/autoload_classmap.php +++ b/apps/files_external/3rdparty/composer/autoload_classmap.php @@ -6,4 +6,49 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = $vendorDir; return array( + 'Icewind\\SMB\\AbstractShare' => $vendorDir . '/icewind/smb/src/AbstractShare.php', + 'Icewind\\SMB\\Connection' => $vendorDir . '/icewind/smb/src/Connection.php', + 'Icewind\\SMB\\ErrorCodes' => $vendorDir . '/icewind/smb/src/ErrorCodes.php', + 'Icewind\\SMB\\Exception\\AccessDeniedException' => $vendorDir . '/icewind/smb/src/Exception/AccessDeniedException.php', + 'Icewind\\SMB\\Exception\\AlreadyExistsException' => $vendorDir . '/icewind/smb/src/Exception/AlreadyExistsException.php', + 'Icewind\\SMB\\Exception\\AuthenticationException' => $vendorDir . '/icewind/smb/src/Exception/AuthenticationException.php', + 'Icewind\\SMB\\Exception\\ConnectException' => $vendorDir . '/icewind/smb/src/Exception/ConnectException.php', + 'Icewind\\SMB\\Exception\\ConnectionException' => $vendorDir . '/icewind/smb/src/Exception/ConnectionException.php', + 'Icewind\\SMB\\Exception\\ConnectionRefusedException' => $vendorDir . '/icewind/smb/src/Exception/ConnectionRefusedException.php', + 'Icewind\\SMB\\Exception\\Exception' => $vendorDir . '/icewind/smb/src/Exception/Exception.php', + 'Icewind\\SMB\\Exception\\FileInUseException' => $vendorDir . '/icewind/smb/src/Exception/FileInUseException.php', + 'Icewind\\SMB\\Exception\\ForbiddenException' => $vendorDir . '/icewind/smb/src/Exception/ForbiddenException.php', + 'Icewind\\SMB\\Exception\\HostDownException' => $vendorDir . '/icewind/smb/src/Exception/HostDownException.php', + 'Icewind\\SMB\\Exception\\InvalidHostException' => $vendorDir . '/icewind/smb/src/Exception/InvalidHostException.php', + 'Icewind\\SMB\\Exception\\InvalidPathException' => $vendorDir . '/icewind/smb/src/Exception/InvalidPathException.php', + 'Icewind\\SMB\\Exception\\InvalidRequestException' => $vendorDir . '/icewind/smb/src/Exception/InvalidRequestException.php', + 'Icewind\\SMB\\Exception\\InvalidTypeException' => $vendorDir . '/icewind/smb/src/Exception/InvalidTypeException.php', + 'Icewind\\SMB\\Exception\\NoLoginServerException' => $vendorDir . '/icewind/smb/src/Exception/NoLoginServerException.php', + 'Icewind\\SMB\\Exception\\NoRouteToHostException' => $vendorDir . '/icewind/smb/src/Exception/NoRouteToHostException.php', + 'Icewind\\SMB\\Exception\\NotEmptyException' => $vendorDir . '/icewind/smb/src/Exception/NotEmptyException.php', + 'Icewind\\SMB\\Exception\\NotFoundException' => $vendorDir . '/icewind/smb/src/Exception/NotFoundException.php', + 'Icewind\\SMB\\Exception\\TimedOutException' => $vendorDir . '/icewind/smb/src/Exception/TimedOutException.php', + 'Icewind\\SMB\\FileInfo' => $vendorDir . '/icewind/smb/src/FileInfo.php', + 'Icewind\\SMB\\IFileInfo' => $vendorDir . '/icewind/smb/src/IFileInfo.php', + 'Icewind\\SMB\\IShare' => $vendorDir . '/icewind/smb/src/IShare.php', + 'Icewind\\SMB\\NativeFileInfo' => $vendorDir . '/icewind/smb/src/NativeFileInfo.php', + 'Icewind\\SMB\\NativeServer' => $vendorDir . '/icewind/smb/src/NativeServer.php', + 'Icewind\\SMB\\NativeShare' => $vendorDir . '/icewind/smb/src/NativeShare.php', + 'Icewind\\SMB\\NativeState' => $vendorDir . '/icewind/smb/src/NativeState.php', + 'Icewind\\SMB\\NativeStream' => $vendorDir . '/icewind/smb/src/NativeStream.php', + 'Icewind\\SMB\\Parser' => $vendorDir . '/icewind/smb/src/Parser.php', + 'Icewind\\SMB\\RawConnection' => $vendorDir . '/icewind/smb/src/RawConnection.php', + 'Icewind\\SMB\\Server' => $vendorDir . '/icewind/smb/src/Server.php', + 'Icewind\\SMB\\Share' => $vendorDir . '/icewind/smb/src/Share.php', + 'Icewind\\SMB\\TimeZoneProvider' => $vendorDir . '/icewind/smb/src/TimeZoneProvider.php', + 'Icewind\\Streams\\CallbackWrapper' => $vendorDir . '/icewind/streams/src/CallbackWrapper.php', + 'Icewind\\Streams\\Directory' => $vendorDir . '/icewind/streams/src/Directory.php', + 'Icewind\\Streams\\File' => $vendorDir . '/icewind/streams/src/File.php', + 'Icewind\\Streams\\IteratorDirectory' => $vendorDir . '/icewind/streams/src/IteratorDirectory.php', + 'Icewind\\Streams\\NullWrapper' => $vendorDir . '/icewind/streams/src/NullWrapper.php', + 'Icewind\\Streams\\Tests\\CallbackWrapper' => $vendorDir . '/icewind/streams/tests/CallbackWrapper.php', + 'Icewind\\Streams\\Tests\\IteratorDirectory' => $vendorDir . '/icewind/streams/tests/IteratorDirectory.php', + 'Icewind\\Streams\\Tests\\NullWrapper' => $vendorDir . '/icewind/streams/tests/NullWrapper.php', + 'Icewind\\Streams\\Tests\\Wrapper' => $vendorDir . '/icewind/streams/tests/Wrapper.php', + 'Icewind\\Streams\\Wrapper' => $vendorDir . '/icewind/streams/src/Wrapper.php', ); diff --git a/apps/files_external/3rdparty/composer/autoload_real.php b/apps/files_external/3rdparty/composer/autoload_real.php index 3391b322be0..9e8b3b558e5 100644 --- a/apps/files_external/3rdparty/composer/autoload_real.php +++ b/apps/files_external/3rdparty/composer/autoload_real.php @@ -38,6 +38,7 @@ class ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3 $loader->addClassMap($classMap); } + $loader->setClassMapAuthoritative(true); $loader->register(true); return $loader; diff --git a/apps/files_external/appinfo/app.php b/apps/files_external/appinfo/app.php index aa39d79a85e..a7d8f4f668d 100644 --- a/apps/files_external/appinfo/app.php +++ b/apps/files_external/appinfo/app.php @@ -1,8 +1,8 @@ <?php /** * @author Christian Berendt <berendt@b1-systems.de> - * @author Jan-Christoph Borchardt <hey@jancborchardt.net> * @author j-ed <juergen@eisfair.org> + * @author Jan-Christoph Borchardt <hey@jancborchardt.net> * @author Michael Gapczynski <GapczynskiM@gmail.com> * @author Robin Appelman <icewind@owncloud.com> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> diff --git a/apps/files_external/appinfo/application.php b/apps/files_external/appinfo/application.php index c7deaaf270e..1d6e0d03400 100644 --- a/apps/files_external/appinfo/application.php +++ b/apps/files_external/appinfo/application.php @@ -2,7 +2,7 @@ /** * @author Morris Jobke <hey@morrisjobke.de> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Ross Nicoll <jrn@jrn.me.uk> * @author Vincent Petry <pvince81@owncloud.com> * diff --git a/apps/files_external/appinfo/info.xml b/apps/files_external/appinfo/info.xml index 8518cc89298..bb494a2eaba 100644 --- a/apps/files_external/appinfo/info.xml +++ b/apps/files_external/appinfo/info.xml @@ -14,12 +14,13 @@ <admin>admin-external-storage</admin> </documentation> <rememberlogin>false</rememberlogin> + <version>0.4.0</version> <types> <filesystem/> </types> <ocsid>166048</ocsid> <dependencies> - <owncloud min-version="8" /> + <owncloud min-version="9.0" /> </dependencies> </info> diff --git a/apps/files_external/appinfo/routes.php b/apps/files_external/appinfo/routes.php index a98a63c711d..39ded1dc2ec 100644 --- a/apps/files_external/appinfo/routes.php +++ b/apps/files_external/appinfo/routes.php @@ -4,7 +4,7 @@ * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Ross Nicoll <jrn@jrn.me.uk> * @author Vincent Petry <pvince81@owncloud.com> * diff --git a/apps/files_external/appinfo/version b/apps/files_external/appinfo/version deleted file mode 100644 index 7179039691c..00000000000 --- a/apps/files_external/appinfo/version +++ /dev/null @@ -1 +0,0 @@ -0.2.3 diff --git a/apps/files_external/css/settings.css b/apps/files_external/css/settings.css index fb7789a537d..35c7a395c58 100644 --- a/apps/files_external/css/settings.css +++ b/apps/files_external/css/settings.css @@ -57,16 +57,27 @@ td.mountPoint, td.backend { width:160px; } height: 32px; padding: 3px; } +.select2-results .select2-result-label > span { + display: block; + position: relative; +} .select2-results .select2-result-label .avatardiv { display:inline-block; } .select2-results .select2-result-label .avatardiv + span { + position: absolute; + top: 5px; margin-left: 10px; } .select2-results .select2-result-label .avatardiv[data-type="group"] + span { vertical-align: top; top: 6px; - position: relative; + position: absolute; + max-width: 80%; + left: 30px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } #externalStorage .mountOptionsToggle .dropdown { diff --git a/apps/files_external/l10n/ast.js b/apps/files_external/l10n/ast.js index 21eba8c59d5..b663657e029 100644 --- a/apps/files_external/l10n/ast.js +++ b/apps/files_external/l10n/ast.js @@ -55,7 +55,6 @@ OC.L10N.register( "Available for" : "Disponible pa", "Delete" : "Desaniciar", "Add storage" : "Amestar almacenamientu", - "Enable User External Storage" : "Habilitar almacenamientu esterno d'usuariu", "Allow users to mount the following external storage" : "Permitir a los usuarios montar el siguiente almacenamientu esternu" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/ast.json b/apps/files_external/l10n/ast.json index 2464f44ca63..0e98fe5a24a 100644 --- a/apps/files_external/l10n/ast.json +++ b/apps/files_external/l10n/ast.json @@ -53,7 +53,6 @@ "Available for" : "Disponible pa", "Delete" : "Desaniciar", "Add storage" : "Amestar almacenamientu", - "Enable User External Storage" : "Habilitar almacenamientu esterno d'usuariu", "Allow users to mount the following external storage" : "Permitir a los usuarios montar el siguiente almacenamientu esternu" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/az.js b/apps/files_external/l10n/az.js index f51a743d874..700e36a4aef 100644 --- a/apps/files_external/l10n/az.js +++ b/apps/files_external/l10n/az.js @@ -60,7 +60,6 @@ OC.L10N.register( "Advanced settings" : "İrəliləmiş quraşdırmalar", "Delete" : "Sil", "Add storage" : "Deponu əlavə et", - "Enable User External Storage" : "İstifadəçi kənar deponu aktivləşdir", "Allow users to mount the following external storage" : "Göstərilən kənar deponun bərkidilməsi üçün istifadəçilərə izin ver" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/az.json b/apps/files_external/l10n/az.json index 317b7948570..6cccabb2dd6 100644 --- a/apps/files_external/l10n/az.json +++ b/apps/files_external/l10n/az.json @@ -58,7 +58,6 @@ "Advanced settings" : "İrəliləmiş quraşdırmalar", "Delete" : "Sil", "Add storage" : "Deponu əlavə et", - "Enable User External Storage" : "İstifadəçi kənar deponu aktivləşdir", "Allow users to mount the following external storage" : "Göstərilən kənar deponun bərkidilməsi üçün istifadəçilərə izin ver" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/bg_BG.js b/apps/files_external/l10n/bg_BG.js index 8a416271bce..f14b13d2a0e 100644 --- a/apps/files_external/l10n/bg_BG.js +++ b/apps/files_external/l10n/bg_BG.js @@ -62,7 +62,6 @@ OC.L10N.register( "Advanced settings" : "Разширени настройки", "Delete" : "Изтрий", "Add storage" : "Добави дисково пространство", - "Enable User External Storage" : "Разреши Потребителско Външно Дисково Пространство", "Allow users to mount the following external storage" : "Разреши на потребителите да прикачват следното външно дисково пространство" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/bg_BG.json b/apps/files_external/l10n/bg_BG.json index c9191e6d402..6cce8fd5cfa 100644 --- a/apps/files_external/l10n/bg_BG.json +++ b/apps/files_external/l10n/bg_BG.json @@ -60,7 +60,6 @@ "Advanced settings" : "Разширени настройки", "Delete" : "Изтрий", "Add storage" : "Добави дисково пространство", - "Enable User External Storage" : "Разреши Потребителско Външно Дисково Пространство", "Allow users to mount the following external storage" : "Разреши на потребителите да прикачват следното външно дисково пространство" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/bn_BD.js b/apps/files_external/l10n/bn_BD.js index 3676a1a8b51..32d40f3fb8a 100644 --- a/apps/files_external/l10n/bn_BD.js +++ b/apps/files_external/l10n/bn_BD.js @@ -34,7 +34,6 @@ OC.L10N.register( "External Storage" : "বাহ্যিক সংরক্ষণাগার", "Folder name" : "ফোলডারের নাম", "Configuration" : "কনফিগারেসন", - "Delete" : "মুছে", - "Enable User External Storage" : "ব্যবহারকারীর বাহ্যিক সংরক্ষণাগার সক্রিয় কর" + "Delete" : "মুছে" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/bn_BD.json b/apps/files_external/l10n/bn_BD.json index 5b7d07da5a2..3a2d8d4f2af 100644 --- a/apps/files_external/l10n/bn_BD.json +++ b/apps/files_external/l10n/bn_BD.json @@ -32,7 +32,6 @@ "External Storage" : "বাহ্যিক সংরক্ষণাগার", "Folder name" : "ফোলডারের নাম", "Configuration" : "কনফিগারেসন", - "Delete" : "মুছে", - "Enable User External Storage" : "ব্যবহারকারীর বাহ্যিক সংরক্ষণাগার সক্রিয় কর" + "Delete" : "মুছে" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/ca.js b/apps/files_external/l10n/ca.js index 36ea0086c1f..b14057ea420 100644 --- a/apps/files_external/l10n/ca.js +++ b/apps/files_external/l10n/ca.js @@ -69,7 +69,6 @@ OC.L10N.register( "Advanced settings" : "Configuració avançada", "Delete" : "Esborra", "Add storage" : "Afegeix emmagatzemament", - "Enable User External Storage" : "Habilita l'emmagatzemament extern d'usuari", "Allow users to mount the following external storage" : "Permet als usuaris muntar els dispositius externs següents" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/ca.json b/apps/files_external/l10n/ca.json index ffbacca550f..cce51970c18 100644 --- a/apps/files_external/l10n/ca.json +++ b/apps/files_external/l10n/ca.json @@ -67,7 +67,6 @@ "Advanced settings" : "Configuració avançada", "Delete" : "Esborra", "Add storage" : "Afegeix emmagatzemament", - "Enable User External Storage" : "Habilita l'emmagatzemament extern d'usuari", "Allow users to mount the following external storage" : "Permet als usuaris muntar els dispositius externs següents" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/cs_CZ.js b/apps/files_external/l10n/cs_CZ.js index efa5f227a90..a6b9404d73d 100644 --- a/apps/files_external/l10n/cs_CZ.js +++ b/apps/files_external/l10n/cs_CZ.js @@ -10,7 +10,12 @@ OC.L10N.register( "Storage with id \"%i\" not found" : "Úložiště s id \"%i\" nebylo nalezeno", "Invalid backend or authentication mechanism class" : "Neplatný backend nebo třída ověřovacího mechanismu", "Invalid mount point" : "Neplatný přípojný bod", + "Objectstore forbidden" : "Úložiště objektů zakázáno", "Invalid storage backend \"%s\"" : "Neplatná služba úložiště \"%s\"", + "Not permitted to use backend \"%s\"" : "Nebylo povoleno použítí služby \"%s\"", + "Not permitted to use authentication mechanism \"%s\"" : "Nebylo povoleno použití ověřovacího mechanismu \"%s\"", + "Unsatisfied backend parameters" : "Neuspokojivé parametry služby", + "Unsatisfied authentication mechanism parameters" : "Neuspokojivé parametry ověřovacího mechanismu", "Personal" : "Osobní", "System" : "Systém", "Grant access" : "Povolit přístup", @@ -94,7 +99,7 @@ OC.L10N.register( "Advanced settings" : "Pokročilá nastavení", "Delete" : "Smazat", "Add storage" : "Přidat úložiště", - "Enable User External Storage" : "Zapnout externí uživatelské úložiště", + "Allow users to mount external storages" : "Povolit uživatelům připojení externích úložišť", "Allow users to mount the following external storage" : "Povolit uživatelů připojit následující externí úložiště" }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"); diff --git a/apps/files_external/l10n/cs_CZ.json b/apps/files_external/l10n/cs_CZ.json index 8aa349522f9..6c7e4c5b8d5 100644 --- a/apps/files_external/l10n/cs_CZ.json +++ b/apps/files_external/l10n/cs_CZ.json @@ -8,7 +8,12 @@ "Storage with id \"%i\" not found" : "Úložiště s id \"%i\" nebylo nalezeno", "Invalid backend or authentication mechanism class" : "Neplatný backend nebo třída ověřovacího mechanismu", "Invalid mount point" : "Neplatný přípojný bod", + "Objectstore forbidden" : "Úložiště objektů zakázáno", "Invalid storage backend \"%s\"" : "Neplatná služba úložiště \"%s\"", + "Not permitted to use backend \"%s\"" : "Nebylo povoleno použítí služby \"%s\"", + "Not permitted to use authentication mechanism \"%s\"" : "Nebylo povoleno použití ověřovacího mechanismu \"%s\"", + "Unsatisfied backend parameters" : "Neuspokojivé parametry služby", + "Unsatisfied authentication mechanism parameters" : "Neuspokojivé parametry ověřovacího mechanismu", "Personal" : "Osobní", "System" : "Systém", "Grant access" : "Povolit přístup", @@ -92,7 +97,7 @@ "Advanced settings" : "Pokročilá nastavení", "Delete" : "Smazat", "Add storage" : "Přidat úložiště", - "Enable User External Storage" : "Zapnout externí uživatelské úložiště", + "Allow users to mount external storages" : "Povolit uživatelům připojení externích úložišť", "Allow users to mount the following external storage" : "Povolit uživatelů připojit následující externí úložiště" },"pluralForm" :"nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" }
\ No newline at end of file diff --git a/apps/files_external/l10n/da.js b/apps/files_external/l10n/da.js index 3d0c15e578d..ba5d572a206 100644 --- a/apps/files_external/l10n/da.js +++ b/apps/files_external/l10n/da.js @@ -101,7 +101,6 @@ OC.L10N.register( "Advanced settings" : "Avancerede indstillinger", "Delete" : "Slet", "Add storage" : "Tilføj lager", - "Enable User External Storage" : "Aktivér ekstern opbevaring for brugere", "Allow users to mount the following external storage" : "Tillad brugere at montere følgende som eksternt lager" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/da.json b/apps/files_external/l10n/da.json index 0c70ebcf77a..c819040bc74 100644 --- a/apps/files_external/l10n/da.json +++ b/apps/files_external/l10n/da.json @@ -99,7 +99,6 @@ "Advanced settings" : "Avancerede indstillinger", "Delete" : "Slet", "Add storage" : "Tilføj lager", - "Enable User External Storage" : "Aktivér ekstern opbevaring for brugere", "Allow users to mount the following external storage" : "Tillad brugere at montere følgende som eksternt lager" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/de.js b/apps/files_external/l10n/de.js index 633b499934e..c26322495d6 100644 --- a/apps/files_external/l10n/de.js +++ b/apps/files_external/l10n/de.js @@ -87,7 +87,6 @@ OC.L10N.register( "Advanced settings" : "Erweiterte Einstellungen", "Delete" : "Löschen", "Add storage" : "Speicher hinzufügen", - "Enable User External Storage" : "Externen Speicher für Benutzer aktivieren", "Allow users to mount the following external storage" : "Erlaube es Benutzern, den folgenden externen Speicher einzubinden" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/de.json b/apps/files_external/l10n/de.json index 94a89346c5d..adec32d072b 100644 --- a/apps/files_external/l10n/de.json +++ b/apps/files_external/l10n/de.json @@ -85,7 +85,6 @@ "Advanced settings" : "Erweiterte Einstellungen", "Delete" : "Löschen", "Add storage" : "Speicher hinzufügen", - "Enable User External Storage" : "Externen Speicher für Benutzer aktivieren", "Allow users to mount the following external storage" : "Erlaube es Benutzern, den folgenden externen Speicher einzubinden" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/de_DE.js b/apps/files_external/l10n/de_DE.js index db8c77aa35c..c30fcb95919 100644 --- a/apps/files_external/l10n/de_DE.js +++ b/apps/files_external/l10n/de_DE.js @@ -70,7 +70,6 @@ OC.L10N.register( "Advanced settings" : "Erweiterte Einstellungen", "Delete" : "Löschen", "Add storage" : "Speicher hinzufügen", - "Enable User External Storage" : "Externen Speicher für Benutzer aktivieren", "Allow users to mount the following external storage" : "Erlauben Sie Benutzern, folgende externe Speicher einzubinden" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/de_DE.json b/apps/files_external/l10n/de_DE.json index c3a254be26f..7fec06bf90f 100644 --- a/apps/files_external/l10n/de_DE.json +++ b/apps/files_external/l10n/de_DE.json @@ -68,7 +68,6 @@ "Advanced settings" : "Erweiterte Einstellungen", "Delete" : "Löschen", "Add storage" : "Speicher hinzufügen", - "Enable User External Storage" : "Externen Speicher für Benutzer aktivieren", "Allow users to mount the following external storage" : "Erlauben Sie Benutzern, folgende externe Speicher einzubinden" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/el.js b/apps/files_external/l10n/el.js index 4e62178830b..8b4d089a964 100644 --- a/apps/files_external/l10n/el.js +++ b/apps/files_external/l10n/el.js @@ -101,7 +101,6 @@ OC.L10N.register( "Advanced settings" : "Ρυθμίσεις για προχωρημένους", "Delete" : "Διαγραφή", "Add storage" : "Προσθηκη αποθηκευσης", - "Enable User External Storage" : "Ενεργοποίηση Εξωτερικού Αποθηκευτικού Χώρου Χρήστη", "Allow users to mount the following external storage" : "Χορήγηση άδειας στους χρήστες να συνδέσουν τα παρακάτω εξωτερικά μέσα αποθήκευσης" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/el.json b/apps/files_external/l10n/el.json index 8aa6c987225..74a9aa298ef 100644 --- a/apps/files_external/l10n/el.json +++ b/apps/files_external/l10n/el.json @@ -99,7 +99,6 @@ "Advanced settings" : "Ρυθμίσεις για προχωρημένους", "Delete" : "Διαγραφή", "Add storage" : "Προσθηκη αποθηκευσης", - "Enable User External Storage" : "Ενεργοποίηση Εξωτερικού Αποθηκευτικού Χώρου Χρήστη", "Allow users to mount the following external storage" : "Χορήγηση άδειας στους χρήστες να συνδέσουν τα παρακάτω εξωτερικά μέσα αποθήκευσης" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/en_GB.js b/apps/files_external/l10n/en_GB.js index 444d0db1d52..9829bd3e8c7 100644 --- a/apps/files_external/l10n/en_GB.js +++ b/apps/files_external/l10n/en_GB.js @@ -70,7 +70,6 @@ OC.L10N.register( "Advanced settings" : "Advanced settings", "Delete" : "Delete", "Add storage" : "Add storage", - "Enable User External Storage" : "Enable User External Storage", "Allow users to mount the following external storage" : "Allow users to mount the following external storage" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/en_GB.json b/apps/files_external/l10n/en_GB.json index 8af92769bdf..88467528431 100644 --- a/apps/files_external/l10n/en_GB.json +++ b/apps/files_external/l10n/en_GB.json @@ -68,7 +68,6 @@ "Advanced settings" : "Advanced settings", "Delete" : "Delete", "Add storage" : "Add storage", - "Enable User External Storage" : "Enable User External Storage", "Allow users to mount the following external storage" : "Allow users to mount the following external storage" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/eo.js b/apps/files_external/l10n/eo.js index 8a8884777e6..ce3dd21c012 100644 --- a/apps/files_external/l10n/eo.js +++ b/apps/files_external/l10n/eo.js @@ -40,7 +40,6 @@ OC.L10N.register( "Available for" : "Disponebla por", "Delete" : "Forigi", "Add storage" : "Aldoni memorilon", - "Enable User External Storage" : "Kapabligi malenan memorilon de uzanto", "Allow users to mount the following external storage" : "Permesi uzantojn munti la jenajn malenajn memorilojn" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/eo.json b/apps/files_external/l10n/eo.json index 6741a31e72a..d8c620d24c2 100644 --- a/apps/files_external/l10n/eo.json +++ b/apps/files_external/l10n/eo.json @@ -38,7 +38,6 @@ "Available for" : "Disponebla por", "Delete" : "Forigi", "Add storage" : "Aldoni memorilon", - "Enable User External Storage" : "Kapabligi malenan memorilon de uzanto", "Allow users to mount the following external storage" : "Permesi uzantojn munti la jenajn malenajn memorilojn" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/es.js b/apps/files_external/l10n/es.js index 21e9476f77a..cb31cd7c53f 100644 --- a/apps/files_external/l10n/es.js +++ b/apps/files_external/l10n/es.js @@ -88,7 +88,7 @@ OC.L10N.register( "Advanced settings" : "Configuración avanzada", "Delete" : "Eliminar", "Add storage" : "Añadir almacenamiento", - "Enable User External Storage" : "Habilitar almacenamiento externo de usuario", + "Allow users to mount external storages" : "Permitir a los usuarios montar almacenamientos externos", "Allow users to mount the following external storage" : "Permitir a los usuarios montar el siguiente almacenamiento externo" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/es.json b/apps/files_external/l10n/es.json index 5f89e008ed5..d71effbd84a 100644 --- a/apps/files_external/l10n/es.json +++ b/apps/files_external/l10n/es.json @@ -86,7 +86,7 @@ "Advanced settings" : "Configuración avanzada", "Delete" : "Eliminar", "Add storage" : "Añadir almacenamiento", - "Enable User External Storage" : "Habilitar almacenamiento externo de usuario", + "Allow users to mount external storages" : "Permitir a los usuarios montar almacenamientos externos", "Allow users to mount the following external storage" : "Permitir a los usuarios montar el siguiente almacenamiento externo" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/es_AR.js b/apps/files_external/l10n/es_AR.js index 383b511a75f..fd242104c8c 100644 --- a/apps/files_external/l10n/es_AR.js +++ b/apps/files_external/l10n/es_AR.js @@ -23,7 +23,6 @@ OC.L10N.register( "Folder name" : "Nombre de la carpeta", "Configuration" : "Configuración", "Delete" : "Borrar", - "Add storage" : "Añadir almacenamiento", - "Enable User External Storage" : "Habilitar almacenamiento de usuario externo" + "Add storage" : "Añadir almacenamiento" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/es_AR.json b/apps/files_external/l10n/es_AR.json index 0389f98e647..d9e91a3af47 100644 --- a/apps/files_external/l10n/es_AR.json +++ b/apps/files_external/l10n/es_AR.json @@ -21,7 +21,6 @@ "Folder name" : "Nombre de la carpeta", "Configuration" : "Configuración", "Delete" : "Borrar", - "Add storage" : "Añadir almacenamiento", - "Enable User External Storage" : "Habilitar almacenamiento de usuario externo" + "Add storage" : "Añadir almacenamiento" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/es_MX.js b/apps/files_external/l10n/es_MX.js index 574ba7443d8..9682e360a58 100644 --- a/apps/files_external/l10n/es_MX.js +++ b/apps/files_external/l10n/es_MX.js @@ -22,7 +22,6 @@ OC.L10N.register( "Folder name" : "Nombre de la carpeta", "Configuration" : "Configuración", "Delete" : "Eliminar", - "Add storage" : "Añadir almacenamiento", - "Enable User External Storage" : "Habilitar almacenamiento externo de usuario" + "Add storage" : "Añadir almacenamiento" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/es_MX.json b/apps/files_external/l10n/es_MX.json index ab4fd8153c4..81b2f408d11 100644 --- a/apps/files_external/l10n/es_MX.json +++ b/apps/files_external/l10n/es_MX.json @@ -20,7 +20,6 @@ "Folder name" : "Nombre de la carpeta", "Configuration" : "Configuración", "Delete" : "Eliminar", - "Add storage" : "Añadir almacenamiento", - "Enable User External Storage" : "Habilitar almacenamiento externo de usuario" + "Add storage" : "Añadir almacenamiento" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/et_EE.js b/apps/files_external/l10n/et_EE.js index 69ddf1a9ab2..ae66daa37a6 100644 --- a/apps/files_external/l10n/et_EE.js +++ b/apps/files_external/l10n/et_EE.js @@ -80,7 +80,6 @@ OC.L10N.register( "Advanced settings" : "Lisavalikud", "Delete" : "Kustuta", "Add storage" : "Lisa andmehoidla", - "Enable User External Storage" : "Luba kasutajatele väline salvestamine", "Allow users to mount the following external storage" : "Võimalda kasutajatel ühendada järgmist välist andmehoidlat" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/et_EE.json b/apps/files_external/l10n/et_EE.json index 3538ef2e7ad..f2cc31e46e1 100644 --- a/apps/files_external/l10n/et_EE.json +++ b/apps/files_external/l10n/et_EE.json @@ -78,7 +78,6 @@ "Advanced settings" : "Lisavalikud", "Delete" : "Kustuta", "Add storage" : "Lisa andmehoidla", - "Enable User External Storage" : "Luba kasutajatele väline salvestamine", "Allow users to mount the following external storage" : "Võimalda kasutajatel ühendada järgmist välist andmehoidlat" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/eu.js b/apps/files_external/l10n/eu.js index fd31a4b6db3..599229a92b3 100644 --- a/apps/files_external/l10n/eu.js +++ b/apps/files_external/l10n/eu.js @@ -54,7 +54,6 @@ OC.L10N.register( "Available for" : "Hauentzat eskuragarri", "Delete" : "Ezabatu", "Add storage" : "Gehitu biltegiratzea", - "Enable User External Storage" : "Gaitu erabiltzaileentzako kanpo biltegiratzea", "Allow users to mount the following external storage" : "Baimendu erabiltzaileak hurrengo kanpo biltegiratzeak muntatzen" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/eu.json b/apps/files_external/l10n/eu.json index 2270fa96d08..5a568893060 100644 --- a/apps/files_external/l10n/eu.json +++ b/apps/files_external/l10n/eu.json @@ -52,7 +52,6 @@ "Available for" : "Hauentzat eskuragarri", "Delete" : "Ezabatu", "Add storage" : "Gehitu biltegiratzea", - "Enable User External Storage" : "Gaitu erabiltzaileentzako kanpo biltegiratzea", "Allow users to mount the following external storage" : "Baimendu erabiltzaileak hurrengo kanpo biltegiratzeak muntatzen" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/fa.js b/apps/files_external/l10n/fa.js index efcb4fea048..a18f7cdcf5c 100644 --- a/apps/files_external/l10n/fa.js +++ b/apps/files_external/l10n/fa.js @@ -59,7 +59,6 @@ OC.L10N.register( "Available for" : "در دسترس برای", "Advanced settings" : "تنظیمات پیشرفته", "Delete" : "حذف", - "Add storage" : "اضافه کردن حافظه", - "Enable User External Storage" : "فعال سازی حافظه خارجی کاربر" + "Add storage" : "اضافه کردن حافظه" }, "nplurals=1; plural=0;"); diff --git a/apps/files_external/l10n/fa.json b/apps/files_external/l10n/fa.json index 9e4e3b00365..4bc16f72f54 100644 --- a/apps/files_external/l10n/fa.json +++ b/apps/files_external/l10n/fa.json @@ -57,7 +57,6 @@ "Available for" : "در دسترس برای", "Advanced settings" : "تنظیمات پیشرفته", "Delete" : "حذف", - "Add storage" : "اضافه کردن حافظه", - "Enable User External Storage" : "فعال سازی حافظه خارجی کاربر" + "Add storage" : "اضافه کردن حافظه" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/apps/files_external/l10n/fi_FI.js b/apps/files_external/l10n/fi_FI.js index 610005fa925..18e4db069de 100644 --- a/apps/files_external/l10n/fi_FI.js +++ b/apps/files_external/l10n/fi_FI.js @@ -78,7 +78,7 @@ OC.L10N.register( "Advanced settings" : "Lisäasetukset", "Delete" : "Poista", "Add storage" : "Lisää tallennustila", - "Enable User External Storage" : "Ota käyttöön ulkopuoliset tallennuspaikat", + "Allow users to mount external storages" : "Salli käyttäjien liittää erillisiä tallennustiloja", "Allow users to mount the following external storage" : "Salli käyttäjien liittää seuraavat erilliset tallennusvälineet" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/fi_FI.json b/apps/files_external/l10n/fi_FI.json index f8961048e2f..d879826f901 100644 --- a/apps/files_external/l10n/fi_FI.json +++ b/apps/files_external/l10n/fi_FI.json @@ -76,7 +76,7 @@ "Advanced settings" : "Lisäasetukset", "Delete" : "Poista", "Add storage" : "Lisää tallennustila", - "Enable User External Storage" : "Ota käyttöön ulkopuoliset tallennuspaikat", + "Allow users to mount external storages" : "Salli käyttäjien liittää erillisiä tallennustiloja", "Allow users to mount the following external storage" : "Salli käyttäjien liittää seuraavat erilliset tallennusvälineet" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/fr.js b/apps/files_external/l10n/fr.js index 464508ad298..2ca3c2d2219 100644 --- a/apps/files_external/l10n/fr.js +++ b/apps/files_external/l10n/fr.js @@ -35,7 +35,7 @@ OC.L10N.register( "Saved" : "Sauvegardé", "Access key" : "Clé d'accès", "Secret key" : "Clé secrète", - "Builtin" : "Inclus", + "Builtin" : "Intégré", "None" : "Aucun", "OAuth1" : "OAuth1", "App key" : "App key", @@ -46,6 +46,9 @@ OC.L10N.register( "OpenStack" : "OpenStack", "Username" : "Nom d'utilisateur", "Password" : "Mot de passe", + "Tenant name" : "Tenant name", + "Identity endpoint URL" : "Identity endpoint URL", + "Rackspace" : "Rackspace", "API key" : "Clé API", "Username and password" : "Nom d'utilisateur et mot de passe", "Session credentials" : "Informations d'identification de session", @@ -71,7 +74,7 @@ OC.L10N.register( "Location" : "Emplacement", "ownCloud" : "ownCloud", "SFTP" : "SFTP", - "Root" : "Root", + "Root" : "Racine", "SFTP with secret key login" : "SFTP avec identification par clé", "SMB / CIFS" : "SMB / CIFS", "Share" : "Partage", @@ -80,6 +83,7 @@ OC.L10N.register( "Username as share" : "Nom d'utilisateur comme nom de partage", "OpenStack Object Storage" : "OpenStack Object Storage", "Service name" : "Nom du service", + "Request timeout (seconds)" : "Timeout des requêtes (en secondes)", "<b>Note:</b> " : "<b>Attention :</b>", "<b>Note:</b> The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Attention :</b> La prise en charge de cURL par PHP n'est pas activée ou installée. Le montage de %s n'est pas possible. Contactez votre administrateur système pour l'installer.", "<b>Note:</b> The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Attention : </b> La prise en charge du FTP par PHP n'est pas activée ou installée. Le montage de %s n'est pas possible. Contactez votre administrateur système pour l'installer.", @@ -97,7 +101,7 @@ OC.L10N.register( "Advanced settings" : "Paramètres avancés", "Delete" : "Supprimer", "Add storage" : "Ajouter un support de stockage", - "Enable User External Storage" : "Autoriser les utilisateurs à ajouter des stockages externes", + "Allow users to mount external storages" : "Autoriser les utilisateurs à monter des espaces de stockage externes", "Allow users to mount the following external storage" : "Autoriser les utilisateurs à monter les stockages externes suivants" }, "nplurals=2; plural=(n > 1);"); diff --git a/apps/files_external/l10n/fr.json b/apps/files_external/l10n/fr.json index 993ea0b95f4..5b36ff92741 100644 --- a/apps/files_external/l10n/fr.json +++ b/apps/files_external/l10n/fr.json @@ -33,7 +33,7 @@ "Saved" : "Sauvegardé", "Access key" : "Clé d'accès", "Secret key" : "Clé secrète", - "Builtin" : "Inclus", + "Builtin" : "Intégré", "None" : "Aucun", "OAuth1" : "OAuth1", "App key" : "App key", @@ -44,6 +44,9 @@ "OpenStack" : "OpenStack", "Username" : "Nom d'utilisateur", "Password" : "Mot de passe", + "Tenant name" : "Tenant name", + "Identity endpoint URL" : "Identity endpoint URL", + "Rackspace" : "Rackspace", "API key" : "Clé API", "Username and password" : "Nom d'utilisateur et mot de passe", "Session credentials" : "Informations d'identification de session", @@ -69,7 +72,7 @@ "Location" : "Emplacement", "ownCloud" : "ownCloud", "SFTP" : "SFTP", - "Root" : "Root", + "Root" : "Racine", "SFTP with secret key login" : "SFTP avec identification par clé", "SMB / CIFS" : "SMB / CIFS", "Share" : "Partage", @@ -78,6 +81,7 @@ "Username as share" : "Nom d'utilisateur comme nom de partage", "OpenStack Object Storage" : "OpenStack Object Storage", "Service name" : "Nom du service", + "Request timeout (seconds)" : "Timeout des requêtes (en secondes)", "<b>Note:</b> " : "<b>Attention :</b>", "<b>Note:</b> The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Attention :</b> La prise en charge de cURL par PHP n'est pas activée ou installée. Le montage de %s n'est pas possible. Contactez votre administrateur système pour l'installer.", "<b>Note:</b> The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Attention : </b> La prise en charge du FTP par PHP n'est pas activée ou installée. Le montage de %s n'est pas possible. Contactez votre administrateur système pour l'installer.", @@ -95,7 +99,7 @@ "Advanced settings" : "Paramètres avancés", "Delete" : "Supprimer", "Add storage" : "Ajouter un support de stockage", - "Enable User External Storage" : "Autoriser les utilisateurs à ajouter des stockages externes", + "Allow users to mount external storages" : "Autoriser les utilisateurs à monter des espaces de stockage externes", "Allow users to mount the following external storage" : "Autoriser les utilisateurs à monter les stockages externes suivants" },"pluralForm" :"nplurals=2; plural=(n > 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/gl.js b/apps/files_external/l10n/gl.js index 1fa5f41d64a..5827a6ab57f 100644 --- a/apps/files_external/l10n/gl.js +++ b/apps/files_external/l10n/gl.js @@ -70,7 +70,6 @@ OC.L10N.register( "Advanced settings" : "Axustes avanzados", "Delete" : "Eliminar", "Add storage" : "Engadir almacenamento", - "Enable User External Storage" : "Activar o almacenamento externo do usuario", "Allow users to mount the following external storage" : "Permitirlle aos usuarios montar o seguinte almacenamento externo" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/gl.json b/apps/files_external/l10n/gl.json index a4cacf44faf..29dde23c329 100644 --- a/apps/files_external/l10n/gl.json +++ b/apps/files_external/l10n/gl.json @@ -68,7 +68,6 @@ "Advanced settings" : "Axustes avanzados", "Delete" : "Eliminar", "Add storage" : "Engadir almacenamento", - "Enable User External Storage" : "Activar o almacenamento externo do usuario", "Allow users to mount the following external storage" : "Permitirlle aos usuarios montar o seguinte almacenamento externo" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/he.js b/apps/files_external/l10n/he.js index 2de5539e7d1..613f2688ee4 100644 --- a/apps/files_external/l10n/he.js +++ b/apps/files_external/l10n/he.js @@ -21,7 +21,6 @@ OC.L10N.register( "External Storage" : "אחסון חיצוני", "Folder name" : "שם התיקייה", "Configuration" : "הגדרות", - "Delete" : "מחיקה", - "Enable User External Storage" : "הפעלת אחסון חיצוני למשתמשים" + "Delete" : "מחיקה" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/he.json b/apps/files_external/l10n/he.json index 4b4e289c53e..928329f1d4e 100644 --- a/apps/files_external/l10n/he.json +++ b/apps/files_external/l10n/he.json @@ -19,7 +19,6 @@ "External Storage" : "אחסון חיצוני", "Folder name" : "שם התיקייה", "Configuration" : "הגדרות", - "Delete" : "מחיקה", - "Enable User External Storage" : "הפעלת אחסון חיצוני למשתמשים" + "Delete" : "מחיקה" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/hr.js b/apps/files_external/l10n/hr.js index 651470a58fe..4ab71f76ffb 100644 --- a/apps/files_external/l10n/hr.js +++ b/apps/files_external/l10n/hr.js @@ -52,7 +52,6 @@ OC.L10N.register( "Available for" : "Dostupno za", "Delete" : "Izbrišite", "Add storage" : "Dodajte spremište", - "Enable User External Storage" : "Omogućite korisničko vanjsko spremište", "Allow users to mount the following external storage" : "Dopustite korisnicima postavljanje sljedećeg vanjskog spremišta" }, "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/apps/files_external/l10n/hr.json b/apps/files_external/l10n/hr.json index 80722b18ca4..e8b8dcd3f61 100644 --- a/apps/files_external/l10n/hr.json +++ b/apps/files_external/l10n/hr.json @@ -50,7 +50,6 @@ "Available for" : "Dostupno za", "Delete" : "Izbrišite", "Add storage" : "Dodajte spremište", - "Enable User External Storage" : "Omogućite korisničko vanjsko spremište", "Allow users to mount the following external storage" : "Dopustite korisnicima postavljanje sljedećeg vanjskog spremišta" },"pluralForm" :"nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;" }
\ No newline at end of file diff --git a/apps/files_external/l10n/hu_HU.js b/apps/files_external/l10n/hu_HU.js index 563267f66fd..0a307f8a609 100644 --- a/apps/files_external/l10n/hu_HU.js +++ b/apps/files_external/l10n/hu_HU.js @@ -40,7 +40,6 @@ OC.L10N.register( "Configuration" : "Beállítások", "Available for" : "Elérhető számukra", "Delete" : "Törlés", - "Add storage" : "Tároló becsatolása", - "Enable User External Storage" : "Külső tárolók engedélyezése a felhasználók részére" + "Add storage" : "Tároló becsatolása" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/hu_HU.json b/apps/files_external/l10n/hu_HU.json index a3608dba7b7..4012c0eb490 100644 --- a/apps/files_external/l10n/hu_HU.json +++ b/apps/files_external/l10n/hu_HU.json @@ -38,7 +38,6 @@ "Configuration" : "Beállítások", "Available for" : "Elérhető számukra", "Delete" : "Törlés", - "Add storage" : "Tároló becsatolása", - "Enable User External Storage" : "Külső tárolók engedélyezése a felhasználók részére" + "Add storage" : "Tároló becsatolása" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/id.js b/apps/files_external/l10n/id.js index e324a70e98e..b01e13e6085 100644 --- a/apps/files_external/l10n/id.js +++ b/apps/files_external/l10n/id.js @@ -101,7 +101,6 @@ OC.L10N.register( "Advanced settings" : "Pengaturan Lanjutan", "Delete" : "Hapus", "Add storage" : "Tambahkan penyimpanan", - "Enable User External Storage" : "Aktifkan Penyimpanan Eksternal Pengguna", "Allow users to mount the following external storage" : "Izinkan pengguna untuk mengaitkan penyimpanan eksternal berikut" }, "nplurals=1; plural=0;"); diff --git a/apps/files_external/l10n/id.json b/apps/files_external/l10n/id.json index 83f81014b1e..383850199ac 100644 --- a/apps/files_external/l10n/id.json +++ b/apps/files_external/l10n/id.json @@ -99,7 +99,6 @@ "Advanced settings" : "Pengaturan Lanjutan", "Delete" : "Hapus", "Add storage" : "Tambahkan penyimpanan", - "Enable User External Storage" : "Aktifkan Penyimpanan Eksternal Pengguna", "Allow users to mount the following external storage" : "Izinkan pengguna untuk mengaitkan penyimpanan eksternal berikut" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/apps/files_external/l10n/is.js b/apps/files_external/l10n/is.js index 8af1f2ac0cf..a1edd11fc1c 100644 --- a/apps/files_external/l10n/is.js +++ b/apps/files_external/l10n/is.js @@ -17,7 +17,6 @@ OC.L10N.register( "External Storage" : "Ytri gagnageymsla", "Folder name" : "Nafn möppu", "Configuration" : "Uppsetning", - "Delete" : "Eyða", - "Enable User External Storage" : "Virkja ytra gagnasvæði notenda" + "Delete" : "Eyða" }, "nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);"); diff --git a/apps/files_external/l10n/is.json b/apps/files_external/l10n/is.json index 8abd02bdf0a..6d52ed3a896 100644 --- a/apps/files_external/l10n/is.json +++ b/apps/files_external/l10n/is.json @@ -15,7 +15,6 @@ "External Storage" : "Ytri gagnageymsla", "Folder name" : "Nafn möppu", "Configuration" : "Uppsetning", - "Delete" : "Eyða", - "Enable User External Storage" : "Virkja ytra gagnasvæði notenda" + "Delete" : "Eyða" },"pluralForm" :"nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/it.js b/apps/files_external/l10n/it.js index c39e1764909..a5f8afb474b 100644 --- a/apps/files_external/l10n/it.js +++ b/apps/files_external/l10n/it.js @@ -101,7 +101,7 @@ OC.L10N.register( "Advanced settings" : "Impostazioni avanzate", "Delete" : "Elimina", "Add storage" : "Aggiungi archiviazione", - "Enable User External Storage" : "Abilita la memoria esterna dell'utente", - "Allow users to mount the following external storage" : "Consenti agli utenti di montare la seguente memoria esterna" + "Allow users to mount external storages" : "Consenti agli utenti di montare archiviazioni esterne", + "Allow users to mount the following external storage" : "Consenti agli utenti di montare la seguente archiviazione esterna" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/it.json b/apps/files_external/l10n/it.json index ca3786bd3f5..2f6dad37d7c 100644 --- a/apps/files_external/l10n/it.json +++ b/apps/files_external/l10n/it.json @@ -99,7 +99,7 @@ "Advanced settings" : "Impostazioni avanzate", "Delete" : "Elimina", "Add storage" : "Aggiungi archiviazione", - "Enable User External Storage" : "Abilita la memoria esterna dell'utente", - "Allow users to mount the following external storage" : "Consenti agli utenti di montare la seguente memoria esterna" + "Allow users to mount external storages" : "Consenti agli utenti di montare archiviazioni esterne", + "Allow users to mount the following external storage" : "Consenti agli utenti di montare la seguente archiviazione esterna" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/ja.js b/apps/files_external/l10n/ja.js index c74b55fcddd..ac5d1bfe6f8 100644 --- a/apps/files_external/l10n/ja.js +++ b/apps/files_external/l10n/ja.js @@ -1,12 +1,15 @@ OC.L10N.register( "files_external", { + "Please provide a valid app key and secret." : "有効なアプリのキーとパスワードを入力してください。", "Step 1 failed. Exception: %s" : "ステップ 1 の実行に失敗しました。例外: %s", "Step 2 failed. Exception: %s" : "ステップ 2 の実行に失敗しました。例外: %s", "External storage" : "外部ストレージ", "Storage with id \"%i\" not found" : "ストレージID \"%i\" が見つかりません", "Invalid mount point" : "無効なマウントポイント", "Invalid storage backend \"%s\"" : "\"%s\" のストレージバックエンドが不正", + "Not permitted to use backend \"%s\"" : "バックエンド %s を使うための権限がありません", + "Unsatisfied authentication mechanism parameters" : "認証のためのパラメータが不十分です", "Personal" : "個人", "System" : "システム", "Grant access" : "アクセスを許可", @@ -22,14 +25,22 @@ OC.L10N.register( "All users. Type to select user or group." : "すべてのユーザー。ユーザー、グループを追加", "(group)" : "(グループ)", "Saved" : "保存されました", + "Access key" : "アクセスキー", + "Secret key" : "シークレットキー", "None" : "なし", + "OAuth1" : "OAuth1", "App key" : "アプリキー", "App secret" : "アプリシークレット", + "OAuth2" : "OAuth2", "Client ID" : "クライアントID", "Client secret" : "クライアント秘密キー", + "OpenStack" : "OpenStack", "Username" : "ユーザー名", "Password" : "パスワード", + "Rackspace" : "Rackspace", "API key" : "APIキー", + "Username and password" : "ユーザー名とパスワード", + "RSA public key" : "RSA公開鍵", "Public key" : "公開鍵", "Amazon S3" : "Amazon S3", "Bucket" : "バケット名", @@ -43,17 +54,24 @@ OC.L10N.register( "Remote subfolder" : "リモートサブフォルダー", "Secure https://" : "セキュア https://", "Dropbox" : "Dropbox", + "FTP" : "FTP", "Host" : "ホスト", "Secure ftps://" : "Secure ftps://", + "Google Drive" : "Google Drive", "Local" : "ローカル", "Location" : "位置", "ownCloud" : "ownCloud", + "SFTP" : "SFTP", "Root" : "ルート", "SFTP with secret key login" : "秘密鍵でSFTPログイン", + "SMB / CIFS" : "SMB / CIFS", "Share" : "共有", + "Domain" : "ドメイン", "SMB / CIFS using OC login" : "ownCloudログインを利用したSMB / CIFS", "Username as share" : "共有名", "OpenStack Object Storage" : "OpenStack ObjectStorage", + "Service name" : "サービス名", + "Request timeout (seconds)" : "リクエストがタイムアウトするまでの秒数", "<b>Note:</b> " : "<b>注意:</b> ", "<b>Note:</b> The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>注意:</b> PHPにcURLのエクステンションが入っていないか、有効ではありません。%s をマウントすることができません。このシステムの管理者にインストールをお願いしてください。", "<b>Note:</b> The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>注意:</b> PHPにFTPのエクステンションが入っていないか、有効ではありません。%s をマウントすることができません。このシステムの管理者にインストールをお願いしてください。", @@ -65,12 +83,12 @@ OC.L10N.register( "Scope" : "スコープ", "External Storage" : "外部ストレージ", "Folder name" : "フォルダー名", + "Authentication" : "認証", "Configuration" : "設定", "Available for" : "利用可能", "Advanced settings" : "詳細設定", "Delete" : "削除", "Add storage" : "ストレージを追加", - "Enable User External Storage" : "ユーザーの外部ストレージを有効にする", "Allow users to mount the following external storage" : "ユーザーに以下の外部ストレージのマウントを許可する" }, "nplurals=1; plural=0;"); diff --git a/apps/files_external/l10n/ja.json b/apps/files_external/l10n/ja.json index 50bfe5c5245..6664b3d97db 100644 --- a/apps/files_external/l10n/ja.json +++ b/apps/files_external/l10n/ja.json @@ -1,10 +1,13 @@ { "translations": { + "Please provide a valid app key and secret." : "有効なアプリのキーとパスワードを入力してください。", "Step 1 failed. Exception: %s" : "ステップ 1 の実行に失敗しました。例外: %s", "Step 2 failed. Exception: %s" : "ステップ 2 の実行に失敗しました。例外: %s", "External storage" : "外部ストレージ", "Storage with id \"%i\" not found" : "ストレージID \"%i\" が見つかりません", "Invalid mount point" : "無効なマウントポイント", "Invalid storage backend \"%s\"" : "\"%s\" のストレージバックエンドが不正", + "Not permitted to use backend \"%s\"" : "バックエンド %s を使うための権限がありません", + "Unsatisfied authentication mechanism parameters" : "認証のためのパラメータが不十分です", "Personal" : "個人", "System" : "システム", "Grant access" : "アクセスを許可", @@ -20,14 +23,22 @@ "All users. Type to select user or group." : "すべてのユーザー。ユーザー、グループを追加", "(group)" : "(グループ)", "Saved" : "保存されました", + "Access key" : "アクセスキー", + "Secret key" : "シークレットキー", "None" : "なし", + "OAuth1" : "OAuth1", "App key" : "アプリキー", "App secret" : "アプリシークレット", + "OAuth2" : "OAuth2", "Client ID" : "クライアントID", "Client secret" : "クライアント秘密キー", + "OpenStack" : "OpenStack", "Username" : "ユーザー名", "Password" : "パスワード", + "Rackspace" : "Rackspace", "API key" : "APIキー", + "Username and password" : "ユーザー名とパスワード", + "RSA public key" : "RSA公開鍵", "Public key" : "公開鍵", "Amazon S3" : "Amazon S3", "Bucket" : "バケット名", @@ -41,17 +52,24 @@ "Remote subfolder" : "リモートサブフォルダー", "Secure https://" : "セキュア https://", "Dropbox" : "Dropbox", + "FTP" : "FTP", "Host" : "ホスト", "Secure ftps://" : "Secure ftps://", + "Google Drive" : "Google Drive", "Local" : "ローカル", "Location" : "位置", "ownCloud" : "ownCloud", + "SFTP" : "SFTP", "Root" : "ルート", "SFTP with secret key login" : "秘密鍵でSFTPログイン", + "SMB / CIFS" : "SMB / CIFS", "Share" : "共有", + "Domain" : "ドメイン", "SMB / CIFS using OC login" : "ownCloudログインを利用したSMB / CIFS", "Username as share" : "共有名", "OpenStack Object Storage" : "OpenStack ObjectStorage", + "Service name" : "サービス名", + "Request timeout (seconds)" : "リクエストがタイムアウトするまでの秒数", "<b>Note:</b> " : "<b>注意:</b> ", "<b>Note:</b> The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>注意:</b> PHPにcURLのエクステンションが入っていないか、有効ではありません。%s をマウントすることができません。このシステムの管理者にインストールをお願いしてください。", "<b>Note:</b> The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>注意:</b> PHPにFTPのエクステンションが入っていないか、有効ではありません。%s をマウントすることができません。このシステムの管理者にインストールをお願いしてください。", @@ -63,12 +81,12 @@ "Scope" : "スコープ", "External Storage" : "外部ストレージ", "Folder name" : "フォルダー名", + "Authentication" : "認証", "Configuration" : "設定", "Available for" : "利用可能", "Advanced settings" : "詳細設定", "Delete" : "削除", "Add storage" : "ストレージを追加", - "Enable User External Storage" : "ユーザーの外部ストレージを有効にする", "Allow users to mount the following external storage" : "ユーザーに以下の外部ストレージのマウントを許可する" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/apps/files_external/l10n/ka_GE.js b/apps/files_external/l10n/ka_GE.js index 6f38743a215..824295cb93d 100644 --- a/apps/files_external/l10n/ka_GE.js +++ b/apps/files_external/l10n/ka_GE.js @@ -22,7 +22,6 @@ OC.L10N.register( "Folder name" : "ფოლდერის სახელი", "Configuration" : "კონფიგურაცია", "Delete" : "წაშლა", - "Add storage" : "საცავის დამატება", - "Enable User External Storage" : "მომხმარებლის ექსტერნალ საცავის აქტივირება" + "Add storage" : "საცავის დამატება" }, "nplurals=1; plural=0;"); diff --git a/apps/files_external/l10n/ka_GE.json b/apps/files_external/l10n/ka_GE.json index 7ca96f6db5d..73ad2cfd0c5 100644 --- a/apps/files_external/l10n/ka_GE.json +++ b/apps/files_external/l10n/ka_GE.json @@ -20,7 +20,6 @@ "Folder name" : "ფოლდერის სახელი", "Configuration" : "კონფიგურაცია", "Delete" : "წაშლა", - "Add storage" : "საცავის დამატება", - "Enable User External Storage" : "მომხმარებლის ექსტერნალ საცავის აქტივირება" + "Add storage" : "საცავის დამატება" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/apps/files_external/l10n/ko.js b/apps/files_external/l10n/ko.js index 3faf53bf1e5..0d5bf1b4622 100644 --- a/apps/files_external/l10n/ko.js +++ b/apps/files_external/l10n/ko.js @@ -101,7 +101,6 @@ OC.L10N.register( "Advanced settings" : "고급 설정", "Delete" : "삭제", "Add storage" : "저장소 추가", - "Enable User External Storage" : "사용자 외부 저장소 사용", "Allow users to mount the following external storage" : "사용자가 다음 외부 저장소를 마운트할 수 있도록 허용" }, "nplurals=1; plural=0;"); diff --git a/apps/files_external/l10n/ko.json b/apps/files_external/l10n/ko.json index 039190cf4f9..2695f209730 100644 --- a/apps/files_external/l10n/ko.json +++ b/apps/files_external/l10n/ko.json @@ -99,7 +99,6 @@ "Advanced settings" : "고급 설정", "Delete" : "삭제", "Add storage" : "저장소 추가", - "Enable User External Storage" : "사용자 외부 저장소 사용", "Allow users to mount the following external storage" : "사용자가 다음 외부 저장소를 마운트할 수 있도록 허용" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/apps/files_external/l10n/lt_LT.js b/apps/files_external/l10n/lt_LT.js index 741d507db42..b6d334d792d 100644 --- a/apps/files_external/l10n/lt_LT.js +++ b/apps/files_external/l10n/lt_LT.js @@ -23,7 +23,6 @@ OC.L10N.register( "Folder name" : "Katalogo pavadinimas", "Configuration" : "Konfigūracija", "Delete" : "Ištrinti", - "Add storage" : "Pridėti saugyklą", - "Enable User External Storage" : "Įjungti vartotojų išorines saugyklas" + "Add storage" : "Pridėti saugyklą" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/apps/files_external/l10n/lt_LT.json b/apps/files_external/l10n/lt_LT.json index 152c48a1035..f1c46b145ee 100644 --- a/apps/files_external/l10n/lt_LT.json +++ b/apps/files_external/l10n/lt_LT.json @@ -21,7 +21,6 @@ "Folder name" : "Katalogo pavadinimas", "Configuration" : "Konfigūracija", "Delete" : "Ištrinti", - "Add storage" : "Pridėti saugyklą", - "Enable User External Storage" : "Įjungti vartotojų išorines saugyklas" + "Add storage" : "Pridėti saugyklą" },"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/lv.js b/apps/files_external/l10n/lv.js index 2f694de2249..7b4d94be6c7 100644 --- a/apps/files_external/l10n/lv.js +++ b/apps/files_external/l10n/lv.js @@ -21,7 +21,6 @@ OC.L10N.register( "Folder name" : "Mapes nosaukums", "Configuration" : "Konfigurācija", "Delete" : "Dzēst", - "Add storage" : "Pievienot krātuvi", - "Enable User External Storage" : "Aktivēt lietotāja ārējo krātuvi" + "Add storage" : "Pievienot krātuvi" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);"); diff --git a/apps/files_external/l10n/lv.json b/apps/files_external/l10n/lv.json index 5b281307ec3..7ad68595d73 100644 --- a/apps/files_external/l10n/lv.json +++ b/apps/files_external/l10n/lv.json @@ -19,7 +19,6 @@ "Folder name" : "Mapes nosaukums", "Configuration" : "Konfigurācija", "Delete" : "Dzēst", - "Add storage" : "Pievienot krātuvi", - "Enable User External Storage" : "Aktivēt lietotāja ārējo krātuvi" + "Add storage" : "Pievienot krātuvi" },"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/mk.js b/apps/files_external/l10n/mk.js index 3a62a2b580b..e75f1afa620 100644 --- a/apps/files_external/l10n/mk.js +++ b/apps/files_external/l10n/mk.js @@ -22,7 +22,6 @@ OC.L10N.register( "External Storage" : "Надворешно складиште", "Folder name" : "Име на папка", "Configuration" : "Конфигурација", - "Delete" : "Избриши", - "Enable User External Storage" : "Овозможи надворешни за корисници" + "Delete" : "Избриши" }, "nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;"); diff --git a/apps/files_external/l10n/mk.json b/apps/files_external/l10n/mk.json index 9fb0994a604..ae095b46310 100644 --- a/apps/files_external/l10n/mk.json +++ b/apps/files_external/l10n/mk.json @@ -20,7 +20,6 @@ "External Storage" : "Надворешно складиште", "Folder name" : "Име на папка", "Configuration" : "Конфигурација", - "Delete" : "Избриши", - "Enable User External Storage" : "Овозможи надворешни за корисници" + "Delete" : "Избриши" },"pluralForm" :"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;" }
\ No newline at end of file diff --git a/apps/files_external/l10n/nb_NO.js b/apps/files_external/l10n/nb_NO.js index 2057f257adc..1859d5a7267 100644 --- a/apps/files_external/l10n/nb_NO.js +++ b/apps/files_external/l10n/nb_NO.js @@ -101,7 +101,7 @@ OC.L10N.register( "Advanced settings" : "Avanserte innstillinger", "Delete" : "Slett", "Add storage" : "Legg til lagringsplass", - "Enable User External Storage" : "Aktiver ekstern lagring for bruker", + "Allow users to mount external storages" : "Tillat at brukere kobler opp eksterne lagre", "Allow users to mount the following external storage" : "Tillat brukere å koble opp følgende eksterne lagring" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/nb_NO.json b/apps/files_external/l10n/nb_NO.json index 36556fd9912..669751aa00f 100644 --- a/apps/files_external/l10n/nb_NO.json +++ b/apps/files_external/l10n/nb_NO.json @@ -99,7 +99,7 @@ "Advanced settings" : "Avanserte innstillinger", "Delete" : "Slett", "Add storage" : "Legg til lagringsplass", - "Enable User External Storage" : "Aktiver ekstern lagring for bruker", + "Allow users to mount external storages" : "Tillat at brukere kobler opp eksterne lagre", "Allow users to mount the following external storage" : "Tillat brukere å koble opp følgende eksterne lagring" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/nds.js b/apps/files_external/l10n/nds.js index 717417fdc2b..f7f1da30c60 100644 --- a/apps/files_external/l10n/nds.js +++ b/apps/files_external/l10n/nds.js @@ -96,7 +96,6 @@ OC.L10N.register( "Advanced settings" : "Erweiterte Einstellungen", "Delete" : "Löschen", "Add storage" : "Speicher hinzufügen", - "Enable User External Storage" : "Externen Speicher für Nutzer aktivieren", "Allow users to mount the following external storage" : "Erlaube Benutzern folgenden externen Speicher einzuhängen" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/nds.json b/apps/files_external/l10n/nds.json index b7c196ef64c..bfadfc0c123 100644 --- a/apps/files_external/l10n/nds.json +++ b/apps/files_external/l10n/nds.json @@ -94,7 +94,6 @@ "Advanced settings" : "Erweiterte Einstellungen", "Delete" : "Löschen", "Add storage" : "Speicher hinzufügen", - "Enable User External Storage" : "Externen Speicher für Nutzer aktivieren", "Allow users to mount the following external storage" : "Erlaube Benutzern folgenden externen Speicher einzuhängen" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/nl.js b/apps/files_external/l10n/nl.js index 02661996d5e..bfcf493ee04 100644 --- a/apps/files_external/l10n/nl.js +++ b/apps/files_external/l10n/nl.js @@ -101,7 +101,7 @@ OC.L10N.register( "Advanced settings" : "Geavanceerde instellingen", "Delete" : "Verwijder", "Add storage" : "Toevoegen opslag", - "Enable User External Storage" : "Externe opslag voor gebruikers activeren", + "Allow users to mount external storages" : "Sta gebruikers toe om een externe opslag aan te koppelen.", "Allow users to mount the following external storage" : "Sta gebruikers toe de volgende externe opslag aan te koppelen" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/nl.json b/apps/files_external/l10n/nl.json index ef7f42b1f38..e3ff8826ef5 100644 --- a/apps/files_external/l10n/nl.json +++ b/apps/files_external/l10n/nl.json @@ -99,7 +99,7 @@ "Advanced settings" : "Geavanceerde instellingen", "Delete" : "Verwijder", "Add storage" : "Toevoegen opslag", - "Enable User External Storage" : "Externe opslag voor gebruikers activeren", + "Allow users to mount external storages" : "Sta gebruikers toe om een externe opslag aan te koppelen.", "Allow users to mount the following external storage" : "Sta gebruikers toe de volgende externe opslag aan te koppelen" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/oc.js b/apps/files_external/l10n/oc.js index b7bc419fdd7..4771b564edb 100644 --- a/apps/files_external/l10n/oc.js +++ b/apps/files_external/l10n/oc.js @@ -1,25 +1,41 @@ OC.L10N.register( "files_external", { + "Fetching request tokens failed. Verify that your app key and secret are correct." : "L'obtencion dels getons de requèsta a fracassat. Verificatz que vòstra clau d'aplicacion e vòstre senhal son corrèctes.", + "Fetching access tokens failed. Verify that your app key and secret are correct." : "L'obtencion dels getons d'aacès a fracassat. Verificatz que vòstra clau d'aplicacion e vòstre senhal son corrèctes.", + "Please provide a valid app key and secret." : "Provesissètz una clau d'aplicacion e un senhal valids.", "Step 1 failed. Exception: %s" : "L’etapa 1 a fracassat. Error : %s", "Step 2 failed. Exception: %s" : "L’etapa 2 a fracassat. Error : %s", "External storage" : "Emmagazinatge extèrne", "Storage with id \"%i\" not found" : "Emmagazinatge amb l'id \"%i\" pas trobat", + "Invalid backend or authentication mechanism class" : "Servici o metòde d'autentificacion pas valable", "Invalid mount point" : "Punt de montatge invalid", + "Objectstore forbidden" : "\"Objectstore\" interdich", "Invalid storage backend \"%s\"" : "Servici d'emmagazinatge invalid : \"%s\"", + "Not permitted to use backend \"%s\"" : "Pas autorizat a utilizar lo servici \"%s\"", + "Not permitted to use authentication mechanism \"%s\"" : "Pas autorizat a utilizar lo mecanisme d'autentificacion \"%s\"", + "Unsatisfied backend parameters" : "Paramètres mancants pel servici", + "Unsatisfied authentication mechanism parameters" : "Paramètres mancants pel metòde d'autentificacion", "Personal" : "Personal", "System" : "Sistèma", "Grant access" : "Autorizar l'accès", "Access granted" : "Accès autorizat", + "Error configuring OAuth1" : "Error al moment de la configuracion d'OAuth1", + "Error configuring OAuth2" : "Error al moment de la configuracion d'OAuth2", "Generate keys" : "Generar de claus", "Error generating key pair" : "Error al moment de la generacion de las claus", "Enable encryption" : "Activar lo chiframent", "Enable previews" : "Activar las previsualizacions", + "Check for changes" : "Recercar las modificacions", "Never" : "Pas jamai", + "Once every direct access" : "Un còp a cada accès dirècte", + "Every time the filesystem is used" : "A cada còp que lo sistèma de fichièrs es utilizat", + "All users. Type to select user or group." : "Totes los utilizaires. Clicatz aicí per restrénher.", "(group)" : "(grop)", - "Saved" : "Salvat", + "Saved" : "Enregistrat", "Access key" : "Clau d'accès", "Secret key" : "Clau secreta", + "Builtin" : "Integrat", "None" : "Pas cap", "OAuth1" : "OAuth1", "App key" : "App key", @@ -27,9 +43,15 @@ OC.L10N.register( "OAuth2" : "OAuth2", "Client ID" : "ID Client", "Client secret" : "Secret client", + "OpenStack" : "OpenStack", "Username" : "Nom d'utilizaire", "Password" : "Senhal", + "Tenant name" : "Tenant name", + "Identity endpoint URL" : "Identity endpoint URL", + "Rackspace" : "Rackspace", "API key" : "Clau API", + "Username and password" : "Nom d'utilizaire e senhal", + "Session credentials" : "Informacions d'identificacion de session", "RSA public key" : "Clau publica RSA", "Public key" : "Clau publica", "Amazon S3" : "Amazon S3", @@ -52,6 +74,7 @@ OC.L10N.register( "Location" : "Emplaçament", "ownCloud" : "ownCloud", "SFTP" : "SFTP", + "Root" : "Raiç", "SFTP with secret key login" : "SFTP amb un identificant secret", "SMB / CIFS" : "SMB / CIFS", "Share" : "Partejar", @@ -60,6 +83,7 @@ OC.L10N.register( "Username as share" : "Nom d'utilizaire coma nom de partiment", "OpenStack Object Storage" : "OpenStack Object Storage", "Service name" : "Nom del servici", + "Request timeout (seconds)" : "Timeout de las requèstas (en segondas)", "<b>Note:</b> " : "<b>Atencion :</b>", "<b>Note:</b> The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Atencion :</b> La presa en carga de cURL per PHP es pas activada o installada. Lo montatge de %s es pas possible. Contactatz vòstre administrator sistèma per l'installar.", "<b>Note:</b> The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Atencion : </b> La presa en carga del FTP per PHP es pas activada o installada. Lo montatge de %s es pas possible. Contactatz vòstre administrator sistèma per l'installar.", @@ -77,7 +101,7 @@ OC.L10N.register( "Advanced settings" : "Paramètres avançats", "Delete" : "Suprimir", "Add storage" : "Apondre un supòrt d'emmagazinatge", - "Enable User External Storage" : "Autorizar los utilizaires a apondre d'emmagazinatges extèrnes", + "Allow users to mount external storages" : "Autorizar los utilizaires a montar los emmagazinatges extèrnes", "Allow users to mount the following external storage" : "Autorizar los utilizaires a montar los emmagazinatges extèrnes seguents" }, "nplurals=2; plural=(n > 1);"); diff --git a/apps/files_external/l10n/oc.json b/apps/files_external/l10n/oc.json index 05670245634..f89691dddde 100644 --- a/apps/files_external/l10n/oc.json +++ b/apps/files_external/l10n/oc.json @@ -1,23 +1,39 @@ { "translations": { + "Fetching request tokens failed. Verify that your app key and secret are correct." : "L'obtencion dels getons de requèsta a fracassat. Verificatz que vòstra clau d'aplicacion e vòstre senhal son corrèctes.", + "Fetching access tokens failed. Verify that your app key and secret are correct." : "L'obtencion dels getons d'aacès a fracassat. Verificatz que vòstra clau d'aplicacion e vòstre senhal son corrèctes.", + "Please provide a valid app key and secret." : "Provesissètz una clau d'aplicacion e un senhal valids.", "Step 1 failed. Exception: %s" : "L’etapa 1 a fracassat. Error : %s", "Step 2 failed. Exception: %s" : "L’etapa 2 a fracassat. Error : %s", "External storage" : "Emmagazinatge extèrne", "Storage with id \"%i\" not found" : "Emmagazinatge amb l'id \"%i\" pas trobat", + "Invalid backend or authentication mechanism class" : "Servici o metòde d'autentificacion pas valable", "Invalid mount point" : "Punt de montatge invalid", + "Objectstore forbidden" : "\"Objectstore\" interdich", "Invalid storage backend \"%s\"" : "Servici d'emmagazinatge invalid : \"%s\"", + "Not permitted to use backend \"%s\"" : "Pas autorizat a utilizar lo servici \"%s\"", + "Not permitted to use authentication mechanism \"%s\"" : "Pas autorizat a utilizar lo mecanisme d'autentificacion \"%s\"", + "Unsatisfied backend parameters" : "Paramètres mancants pel servici", + "Unsatisfied authentication mechanism parameters" : "Paramètres mancants pel metòde d'autentificacion", "Personal" : "Personal", "System" : "Sistèma", "Grant access" : "Autorizar l'accès", "Access granted" : "Accès autorizat", + "Error configuring OAuth1" : "Error al moment de la configuracion d'OAuth1", + "Error configuring OAuth2" : "Error al moment de la configuracion d'OAuth2", "Generate keys" : "Generar de claus", "Error generating key pair" : "Error al moment de la generacion de las claus", "Enable encryption" : "Activar lo chiframent", "Enable previews" : "Activar las previsualizacions", + "Check for changes" : "Recercar las modificacions", "Never" : "Pas jamai", + "Once every direct access" : "Un còp a cada accès dirècte", + "Every time the filesystem is used" : "A cada còp que lo sistèma de fichièrs es utilizat", + "All users. Type to select user or group." : "Totes los utilizaires. Clicatz aicí per restrénher.", "(group)" : "(grop)", - "Saved" : "Salvat", + "Saved" : "Enregistrat", "Access key" : "Clau d'accès", "Secret key" : "Clau secreta", + "Builtin" : "Integrat", "None" : "Pas cap", "OAuth1" : "OAuth1", "App key" : "App key", @@ -25,9 +41,15 @@ "OAuth2" : "OAuth2", "Client ID" : "ID Client", "Client secret" : "Secret client", + "OpenStack" : "OpenStack", "Username" : "Nom d'utilizaire", "Password" : "Senhal", + "Tenant name" : "Tenant name", + "Identity endpoint URL" : "Identity endpoint URL", + "Rackspace" : "Rackspace", "API key" : "Clau API", + "Username and password" : "Nom d'utilizaire e senhal", + "Session credentials" : "Informacions d'identificacion de session", "RSA public key" : "Clau publica RSA", "Public key" : "Clau publica", "Amazon S3" : "Amazon S3", @@ -50,6 +72,7 @@ "Location" : "Emplaçament", "ownCloud" : "ownCloud", "SFTP" : "SFTP", + "Root" : "Raiç", "SFTP with secret key login" : "SFTP amb un identificant secret", "SMB / CIFS" : "SMB / CIFS", "Share" : "Partejar", @@ -58,6 +81,7 @@ "Username as share" : "Nom d'utilizaire coma nom de partiment", "OpenStack Object Storage" : "OpenStack Object Storage", "Service name" : "Nom del servici", + "Request timeout (seconds)" : "Timeout de las requèstas (en segondas)", "<b>Note:</b> " : "<b>Atencion :</b>", "<b>Note:</b> The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Atencion :</b> La presa en carga de cURL per PHP es pas activada o installada. Lo montatge de %s es pas possible. Contactatz vòstre administrator sistèma per l'installar.", "<b>Note:</b> The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Atencion : </b> La presa en carga del FTP per PHP es pas activada o installada. Lo montatge de %s es pas possible. Contactatz vòstre administrator sistèma per l'installar.", @@ -75,7 +99,7 @@ "Advanced settings" : "Paramètres avançats", "Delete" : "Suprimir", "Add storage" : "Apondre un supòrt d'emmagazinatge", - "Enable User External Storage" : "Autorizar los utilizaires a apondre d'emmagazinatges extèrnes", + "Allow users to mount external storages" : "Autorizar los utilizaires a montar los emmagazinatges extèrnes", "Allow users to mount the following external storage" : "Autorizar los utilizaires a montar los emmagazinatges extèrnes seguents" },"pluralForm" :"nplurals=2; plural=(n > 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/pl.js b/apps/files_external/l10n/pl.js index ac24a2b9006..1f7cc6b1979 100644 --- a/apps/files_external/l10n/pl.js +++ b/apps/files_external/l10n/pl.js @@ -70,7 +70,6 @@ OC.L10N.register( "Advanced settings" : "Ustawienia zaawansowane", "Delete" : "Usuń", "Add storage" : "Dodaj zasoby dyskowe", - "Enable User External Storage" : "Włącz zewnętrzne zasoby dyskowe użytkownika", "Allow users to mount the following external storage" : "Pozwól użytkownikom montować następujące zewnętrzne zasoby dyskowe" }, "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/apps/files_external/l10n/pl.json b/apps/files_external/l10n/pl.json index 750e7384d68..5b5059a5d18 100644 --- a/apps/files_external/l10n/pl.json +++ b/apps/files_external/l10n/pl.json @@ -68,7 +68,6 @@ "Advanced settings" : "Ustawienia zaawansowane", "Delete" : "Usuń", "Add storage" : "Dodaj zasoby dyskowe", - "Enable User External Storage" : "Włącz zewnętrzne zasoby dyskowe użytkownika", "Allow users to mount the following external storage" : "Pozwól użytkownikom montować następujące zewnętrzne zasoby dyskowe" },"pluralForm" :"nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/pt_BR.js b/apps/files_external/l10n/pt_BR.js index 3d392a688ee..3abc6d280d9 100644 --- a/apps/files_external/l10n/pt_BR.js +++ b/apps/files_external/l10n/pt_BR.js @@ -101,7 +101,7 @@ OC.L10N.register( "Advanced settings" : "Configurações avançadas", "Delete" : "Excluir", "Add storage" : "Adicionar Armazenamento", - "Enable User External Storage" : "Habilitar Armazenamento Externo do Usuário", + "Allow users to mount external storages" : "Permitir que usuários possam realizar armazenamentos externos", "Allow users to mount the following external storage" : "Permitir que usuários montem o seguinte armazenamento externo" }, "nplurals=2; plural=(n > 1);"); diff --git a/apps/files_external/l10n/pt_BR.json b/apps/files_external/l10n/pt_BR.json index 9289ac7f388..31f71df8be9 100644 --- a/apps/files_external/l10n/pt_BR.json +++ b/apps/files_external/l10n/pt_BR.json @@ -99,7 +99,7 @@ "Advanced settings" : "Configurações avançadas", "Delete" : "Excluir", "Add storage" : "Adicionar Armazenamento", - "Enable User External Storage" : "Habilitar Armazenamento Externo do Usuário", + "Allow users to mount external storages" : "Permitir que usuários possam realizar armazenamentos externos", "Allow users to mount the following external storage" : "Permitir que usuários montem o seguinte armazenamento externo" },"pluralForm" :"nplurals=2; plural=(n > 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/pt_PT.js b/apps/files_external/l10n/pt_PT.js index 6ea55871262..3d6d9377cc9 100644 --- a/apps/files_external/l10n/pt_PT.js +++ b/apps/files_external/l10n/pt_PT.js @@ -89,7 +89,6 @@ OC.L10N.register( "Advanced settings" : "Definições avançadas", "Delete" : "Apagar", "Add storage" : "Adicionar armazenamento", - "Enable User External Storage" : "Ativar Armazenamento Externo para o Utilizador", "Allow users to mount the following external storage" : "Permitir que os utilizadores montem o seguinte armazenamento externo" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/pt_PT.json b/apps/files_external/l10n/pt_PT.json index 8711356815f..ae3cf899ed8 100644 --- a/apps/files_external/l10n/pt_PT.json +++ b/apps/files_external/l10n/pt_PT.json @@ -87,7 +87,6 @@ "Advanced settings" : "Definições avançadas", "Delete" : "Apagar", "Add storage" : "Adicionar armazenamento", - "Enable User External Storage" : "Ativar Armazenamento Externo para o Utilizador", "Allow users to mount the following external storage" : "Permitir que os utilizadores montem o seguinte armazenamento externo" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/ro.js b/apps/files_external/l10n/ro.js index 89f07797d96..d8ba040824c 100644 --- a/apps/files_external/l10n/ro.js +++ b/apps/files_external/l10n/ro.js @@ -31,7 +31,6 @@ OC.L10N.register( "Configuration" : "Configurație", "Delete" : "Șterge", "Add storage" : "Adauga stocare", - "Enable User External Storage" : "Permite stocare externă pentru utilizatori", "Allow users to mount the following external storage" : "Permite utilizatorilor să monteze următoarea unitate de stocare" }, "nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"); diff --git a/apps/files_external/l10n/ro.json b/apps/files_external/l10n/ro.json index 912e6d99b9e..cbe2826def4 100644 --- a/apps/files_external/l10n/ro.json +++ b/apps/files_external/l10n/ro.json @@ -29,7 +29,6 @@ "Configuration" : "Configurație", "Delete" : "Șterge", "Add storage" : "Adauga stocare", - "Enable User External Storage" : "Permite stocare externă pentru utilizatori", "Allow users to mount the following external storage" : "Permite utilizatorilor să monteze următoarea unitate de stocare" },"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));" }
\ No newline at end of file diff --git a/apps/files_external/l10n/ru.js b/apps/files_external/l10n/ru.js index 7e4bfc18730..5550ea780ab 100644 --- a/apps/files_external/l10n/ru.js +++ b/apps/files_external/l10n/ru.js @@ -101,7 +101,6 @@ OC.L10N.register( "Advanced settings" : "Расширенные настройки", "Delete" : "Удалить", "Add storage" : "Добавить хранилище", - "Enable User External Storage" : "Включить пользовательские внешние хранилища", "Allow users to mount the following external storage" : "Разрешить пользователям монтировать следующие сервисы хранения данных" }, "nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"); diff --git a/apps/files_external/l10n/ru.json b/apps/files_external/l10n/ru.json index 50754b309ec..52792a3f09a 100644 --- a/apps/files_external/l10n/ru.json +++ b/apps/files_external/l10n/ru.json @@ -99,7 +99,6 @@ "Advanced settings" : "Расширенные настройки", "Delete" : "Удалить", "Add storage" : "Добавить хранилище", - "Enable User External Storage" : "Включить пользовательские внешние хранилища", "Allow users to mount the following external storage" : "Разрешить пользователям монтировать следующие сервисы хранения данных" },"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/si_LK.js b/apps/files_external/l10n/si_LK.js index 80a6026ad75..82b68af82eb 100644 --- a/apps/files_external/l10n/si_LK.js +++ b/apps/files_external/l10n/si_LK.js @@ -18,7 +18,6 @@ OC.L10N.register( "External Storage" : "භාහිර ගබඩාව", "Folder name" : "ෆොල්ඩරයේ නම", "Configuration" : "වින්යාසය", - "Delete" : "මකා දමන්න", - "Enable User External Storage" : "පරිශීලක භාහිර ගබඩාවන් සක්රිය කරන්න" + "Delete" : "මකා දමන්න" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/si_LK.json b/apps/files_external/l10n/si_LK.json index bc3a9ee11db..2150ac0f9b2 100644 --- a/apps/files_external/l10n/si_LK.json +++ b/apps/files_external/l10n/si_LK.json @@ -16,7 +16,6 @@ "External Storage" : "භාහිර ගබඩාව", "Folder name" : "ෆොල්ඩරයේ නම", "Configuration" : "වින්යාසය", - "Delete" : "මකා දමන්න", - "Enable User External Storage" : "පරිශීලක භාහිර ගබඩාවන් සක්රිය කරන්න" + "Delete" : "මකා දමන්න" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/sk_SK.js b/apps/files_external/l10n/sk_SK.js index 2f9732961fb..41f9c866196 100644 --- a/apps/files_external/l10n/sk_SK.js +++ b/apps/files_external/l10n/sk_SK.js @@ -100,7 +100,6 @@ OC.L10N.register( "Advanced settings" : "Rozšírené nastavenia", "Delete" : "Zmazať", "Add storage" : "Pridať úložisko", - "Enable User External Storage" : "Povoliť externé úložisko", "Allow users to mount the following external storage" : "Povoliť používateľom pripojiť tieto externé úložiská" }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"); diff --git a/apps/files_external/l10n/sk_SK.json b/apps/files_external/l10n/sk_SK.json index b2b21ffe1bb..4072136fa53 100644 --- a/apps/files_external/l10n/sk_SK.json +++ b/apps/files_external/l10n/sk_SK.json @@ -98,7 +98,6 @@ "Advanced settings" : "Rozšírené nastavenia", "Delete" : "Zmazať", "Add storage" : "Pridať úložisko", - "Enable User External Storage" : "Povoliť externé úložisko", "Allow users to mount the following external storage" : "Povoliť používateľom pripojiť tieto externé úložiská" },"pluralForm" :"nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" }
\ No newline at end of file diff --git a/apps/files_external/l10n/sl.js b/apps/files_external/l10n/sl.js index c971920298a..f9513a5f11c 100644 --- a/apps/files_external/l10n/sl.js +++ b/apps/files_external/l10n/sl.js @@ -70,7 +70,6 @@ OC.L10N.register( "Advanced settings" : "Napredne nastavitve", "Delete" : "Izbriši", "Add storage" : "Dodaj shrambo", - "Enable User External Storage" : "Omogoči zunanjo uporabniško podatkovno shrambo", "Allow users to mount the following external storage" : "Dovoli uporabnikom priklapljanje navedenih zunanjih shramb." }, "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"); diff --git a/apps/files_external/l10n/sl.json b/apps/files_external/l10n/sl.json index 1a8df6097d8..ca97f27b234 100644 --- a/apps/files_external/l10n/sl.json +++ b/apps/files_external/l10n/sl.json @@ -68,7 +68,6 @@ "Advanced settings" : "Napredne nastavitve", "Delete" : "Izbriši", "Add storage" : "Dodaj shrambo", - "Enable User External Storage" : "Omogoči zunanjo uporabniško podatkovno shrambo", "Allow users to mount the following external storage" : "Dovoli uporabnikom priklapljanje navedenih zunanjih shramb." },"pluralForm" :"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/sq.js b/apps/files_external/l10n/sq.js index 61262e8c8b1..7edf0b5e67b 100644 --- a/apps/files_external/l10n/sq.js +++ b/apps/files_external/l10n/sq.js @@ -1,19 +1,105 @@ OC.L10N.register( "files_external", { + "Fetching request tokens failed. Verify that your app key and secret are correct." : "Dhënia e elementëve të kërkuar dështoi. Verifikoni që kyçi dhe e fshehta juaj për aplikacionin janë të sakta.", + "Fetching access tokens failed. Verify that your app key and secret are correct." : "Dhënia e elementëve të hyrjes dështoi. Verifikoni që kyçi dhe e fshehta juaj për aplikacionin janë të sakta.", + "Please provide a valid app key and secret." : "Ju lutemi jepni një kyç dhe një të fshehtë aplikacioni të vlefshme.", + "Step 1 failed. Exception: %s" : "Hapi 1 dështoi. Përjashtim: %s", + "Step 2 failed. Exception: %s" : "Hapi 2 dështoi. Përjashtim: %s", + "External storage" : "Depozitë e jashtme", + "Storage with id \"%i\" not found" : "S’u gjet depozitë me id \"%i\"", + "Invalid backend or authentication mechanism class" : "Mekanizëm shërbimi ose klasë mekanizmi mirëfilltësimi e palvefshme", + "Invalid mount point" : "Pikë montimi e pavlefshme", + "Objectstore forbidden" : "Objectstore e ndaluar", + "Invalid storage backend \"%s\"" : "Mekanizëm shërbimi depozite i pavlefshëm \"%s\"", + "Not permitted to use backend \"%s\"" : "I palejuar të përdorë mekanizmin e shërbimit \"%s\"", + "Not permitted to use authentication mechanism \"%s\"" : "S’i lejohet të përdorë mekanizmin e mirëfilltësimit \"%s\"", + "Unsatisfied backend parameters" : "Parametra mekanizmi shërbimi të paplotësuar", + "Unsatisfied authentication mechanism parameters" : "Parametra mekanizmi mirëfilltësimi të papërmbushur", "Personal" : "Personale", + "System" : "Sistem", + "Grant access" : "Akordoji hyrje", + "Access granted" : "Hyrja u akordua", + "Error configuring OAuth1" : "Gabim gjatë formësimit të OAuth1", + "Error configuring OAuth2" : "Gabim gjatë formësimit të OAuth2", + "Generate keys" : "Prodho kyçe", + "Error generating key pair" : "Gabim gjatë prodhimit të çiftit të kyçeve", + "Enable encryption" : "Aktivizoni fshehtëzim", + "Enable previews" : "Aktivizoni paraparje", + "Check for changes" : "Kontrollo për ndryshime", + "Never" : "Kurrë", + "Once every direct access" : "Çdo herë pas hyrjesh të drejtpërdrejta", + "Every time the filesystem is used" : "Sa herë që përdoret sistemi i kartelave", + "All users. Type to select user or group." : "Krejt përdoruesit. Shtypni që të përzgjidhet përdorues ose grup.", + "(group)" : "(grup)", "Saved" : "U ruajt", - "None" : "Asgjë", - "Username" : "Përdoruesi", - "Password" : "fjalëkalim", - "Port" : "Porta", + "Access key" : "Kyç hyrjesh", + "Secret key" : "Kyç i fshehtë", + "Builtin" : "I brendshëm", + "None" : "Asnjë", + "OAuth1" : "OAuth1", + "App key" : "Kyç aplikacioni", + "App secret" : "E fshehtë aplikacioni", + "OAuth2" : "OAuth2", + "Client ID" : "ID klienti", + "Client secret" : "E fshehtë klienti", + "OpenStack" : "OpenStack", + "Username" : "Emër përdoruesi", + "Password" : "Fjalëkalim", + "Rackspace" : "Rackspace", + "API key" : "Kyç API", + "Username and password" : "Emër përdoruesi dhe fjalëkalim", + "Session credentials" : "Kredenciale sesioni", + "RSA public key" : "Kyç publik RSA ", + "Public key" : "Kyç publik", + "Amazon S3" : "Amazon S3", + "Bucket" : "Bucket", + "Hostname" : "Strehëemër", + "Port" : "Portë", + "Region" : "Rajon", + "Enable SSL" : "Aktivizo SSL-në", + "Enable Path Style" : "Aktivizo Stile Shtegu", "WebDAV" : "WebDAV", - "URL" : "URL-i", - "Host" : "Pritësi", - "Location" : "Vendndodhja", - "Share" : "Ndaj", - "Name" : "Emri", - "Folder name" : "Emri i Skedarit", - "Delete" : "Elimino" + "URL" : "URL", + "Remote subfolder" : "Nëndosje e largët", + "Secure https://" : "https:// e sigurt", + "Dropbox" : "Dropbox", + "FTP" : "FTP", + "Host" : "Strehë", + "Secure ftps://" : "ftps:// e sigurt", + "Google Drive" : "Google Drive", + "Local" : "Vendore", + "Location" : "Vendndodhje", + "ownCloud" : "ownCloud", + "SFTP" : "SFTP", + "Root" : "Rrënjë", + "SFTP with secret key login" : "SFTP me hyrje me kyç të fshehtë", + "SMB / CIFS" : "SMB / CIFS", + "Share" : "Ndajeni me të tjerët", + "Domain" : "Përkatësi", + "SMB / CIFS using OC login" : "SMB / CIFS me përdorim hyrjeje OC", + "Username as share" : "Emër përdoruesi si emër ndarjeje", + "OpenStack Object Storage" : "Depozitë OpenStack Object", + "Service name" : "Emër shërbimi", + "Request timeout (seconds)" : "Kohë skadimi kërkese (sekonda)", + "<b>Note:</b> " : "<b>Shënim:</b> ", + "<b>Note:</b> The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Shënim:</b> S’është aktivizuar ose instaluar mbulimi i cURL-ve në PHP. Montimi i %s s’është i mundur. Ju lutemi, kërkojini përgjegjësit të sistemit tuaj ta instalojë.", + "<b>Note:</b> The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Shënim:</b> S’është aktivizuar ose instaluar mbulimi i FTP-së në PHP. Montimi i %s s’është i mundur. Ju lutemi, kërkojini përgjegjësit të sistemit tuaj ta instalojë.", + "<b>Note:</b> \"%s\" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Shënim:</b> S’është instaluar \"%s\". Montimi i %s s’është i mundur. Ju lutemi, kërkojini përgjegjësit të sistemit tuaj ta instalojë.", + "No external storage configured" : "Pa depozita të jashtme të formësuara", + "You can add external storages in the personal settings" : "Depozita të jashtme mund të shtoni që prej rregullimeve personale", + "Name" : "Emër", + "Storage type" : "Lloj depozite", + "Scope" : "Shtrirje", + "External Storage" : "Depozitë e Jashtme", + "Folder name" : "Emër dosjeje", + "Authentication" : "Mirëfilltësim", + "Configuration" : "Formësim", + "Available for" : "E gatshme për", + "Advanced settings" : "Rregullime të mëtejshme", + "Delete" : "Fshije", + "Add storage" : "Shtoni depozitë", + "Allow users to mount external storages" : "Lejoju përdoruesve të montojnë depozita të jashtme", + "Allow users to mount the following external storage" : "Lejoju përdoruesve të montojnë depozitën e jashtme vijuese" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/sq.json b/apps/files_external/l10n/sq.json index 20ebd508480..3d597974f48 100644 --- a/apps/files_external/l10n/sq.json +++ b/apps/files_external/l10n/sq.json @@ -1,17 +1,103 @@ { "translations": { + "Fetching request tokens failed. Verify that your app key and secret are correct." : "Dhënia e elementëve të kërkuar dështoi. Verifikoni që kyçi dhe e fshehta juaj për aplikacionin janë të sakta.", + "Fetching access tokens failed. Verify that your app key and secret are correct." : "Dhënia e elementëve të hyrjes dështoi. Verifikoni që kyçi dhe e fshehta juaj për aplikacionin janë të sakta.", + "Please provide a valid app key and secret." : "Ju lutemi jepni një kyç dhe një të fshehtë aplikacioni të vlefshme.", + "Step 1 failed. Exception: %s" : "Hapi 1 dështoi. Përjashtim: %s", + "Step 2 failed. Exception: %s" : "Hapi 2 dështoi. Përjashtim: %s", + "External storage" : "Depozitë e jashtme", + "Storage with id \"%i\" not found" : "S’u gjet depozitë me id \"%i\"", + "Invalid backend or authentication mechanism class" : "Mekanizëm shërbimi ose klasë mekanizmi mirëfilltësimi e palvefshme", + "Invalid mount point" : "Pikë montimi e pavlefshme", + "Objectstore forbidden" : "Objectstore e ndaluar", + "Invalid storage backend \"%s\"" : "Mekanizëm shërbimi depozite i pavlefshëm \"%s\"", + "Not permitted to use backend \"%s\"" : "I palejuar të përdorë mekanizmin e shërbimit \"%s\"", + "Not permitted to use authentication mechanism \"%s\"" : "S’i lejohet të përdorë mekanizmin e mirëfilltësimit \"%s\"", + "Unsatisfied backend parameters" : "Parametra mekanizmi shërbimi të paplotësuar", + "Unsatisfied authentication mechanism parameters" : "Parametra mekanizmi mirëfilltësimi të papërmbushur", "Personal" : "Personale", + "System" : "Sistem", + "Grant access" : "Akordoji hyrje", + "Access granted" : "Hyrja u akordua", + "Error configuring OAuth1" : "Gabim gjatë formësimit të OAuth1", + "Error configuring OAuth2" : "Gabim gjatë formësimit të OAuth2", + "Generate keys" : "Prodho kyçe", + "Error generating key pair" : "Gabim gjatë prodhimit të çiftit të kyçeve", + "Enable encryption" : "Aktivizoni fshehtëzim", + "Enable previews" : "Aktivizoni paraparje", + "Check for changes" : "Kontrollo për ndryshime", + "Never" : "Kurrë", + "Once every direct access" : "Çdo herë pas hyrjesh të drejtpërdrejta", + "Every time the filesystem is used" : "Sa herë që përdoret sistemi i kartelave", + "All users. Type to select user or group." : "Krejt përdoruesit. Shtypni që të përzgjidhet përdorues ose grup.", + "(group)" : "(grup)", "Saved" : "U ruajt", - "None" : "Asgjë", - "Username" : "Përdoruesi", - "Password" : "fjalëkalim", - "Port" : "Porta", + "Access key" : "Kyç hyrjesh", + "Secret key" : "Kyç i fshehtë", + "Builtin" : "I brendshëm", + "None" : "Asnjë", + "OAuth1" : "OAuth1", + "App key" : "Kyç aplikacioni", + "App secret" : "E fshehtë aplikacioni", + "OAuth2" : "OAuth2", + "Client ID" : "ID klienti", + "Client secret" : "E fshehtë klienti", + "OpenStack" : "OpenStack", + "Username" : "Emër përdoruesi", + "Password" : "Fjalëkalim", + "Rackspace" : "Rackspace", + "API key" : "Kyç API", + "Username and password" : "Emër përdoruesi dhe fjalëkalim", + "Session credentials" : "Kredenciale sesioni", + "RSA public key" : "Kyç publik RSA ", + "Public key" : "Kyç publik", + "Amazon S3" : "Amazon S3", + "Bucket" : "Bucket", + "Hostname" : "Strehëemër", + "Port" : "Portë", + "Region" : "Rajon", + "Enable SSL" : "Aktivizo SSL-në", + "Enable Path Style" : "Aktivizo Stile Shtegu", "WebDAV" : "WebDAV", - "URL" : "URL-i", - "Host" : "Pritësi", - "Location" : "Vendndodhja", - "Share" : "Ndaj", - "Name" : "Emri", - "Folder name" : "Emri i Skedarit", - "Delete" : "Elimino" + "URL" : "URL", + "Remote subfolder" : "Nëndosje e largët", + "Secure https://" : "https:// e sigurt", + "Dropbox" : "Dropbox", + "FTP" : "FTP", + "Host" : "Strehë", + "Secure ftps://" : "ftps:// e sigurt", + "Google Drive" : "Google Drive", + "Local" : "Vendore", + "Location" : "Vendndodhje", + "ownCloud" : "ownCloud", + "SFTP" : "SFTP", + "Root" : "Rrënjë", + "SFTP with secret key login" : "SFTP me hyrje me kyç të fshehtë", + "SMB / CIFS" : "SMB / CIFS", + "Share" : "Ndajeni me të tjerët", + "Domain" : "Përkatësi", + "SMB / CIFS using OC login" : "SMB / CIFS me përdorim hyrjeje OC", + "Username as share" : "Emër përdoruesi si emër ndarjeje", + "OpenStack Object Storage" : "Depozitë OpenStack Object", + "Service name" : "Emër shërbimi", + "Request timeout (seconds)" : "Kohë skadimi kërkese (sekonda)", + "<b>Note:</b> " : "<b>Shënim:</b> ", + "<b>Note:</b> The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Shënim:</b> S’është aktivizuar ose instaluar mbulimi i cURL-ve në PHP. Montimi i %s s’është i mundur. Ju lutemi, kërkojini përgjegjësit të sistemit tuaj ta instalojë.", + "<b>Note:</b> The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Shënim:</b> S’është aktivizuar ose instaluar mbulimi i FTP-së në PHP. Montimi i %s s’është i mundur. Ju lutemi, kërkojini përgjegjësit të sistemit tuaj ta instalojë.", + "<b>Note:</b> \"%s\" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Shënim:</b> S’është instaluar \"%s\". Montimi i %s s’është i mundur. Ju lutemi, kërkojini përgjegjësit të sistemit tuaj ta instalojë.", + "No external storage configured" : "Pa depozita të jashtme të formësuara", + "You can add external storages in the personal settings" : "Depozita të jashtme mund të shtoni që prej rregullimeve personale", + "Name" : "Emër", + "Storage type" : "Lloj depozite", + "Scope" : "Shtrirje", + "External Storage" : "Depozitë e Jashtme", + "Folder name" : "Emër dosjeje", + "Authentication" : "Mirëfilltësim", + "Configuration" : "Formësim", + "Available for" : "E gatshme për", + "Advanced settings" : "Rregullime të mëtejshme", + "Delete" : "Fshije", + "Add storage" : "Shtoni depozitë", + "Allow users to mount external storages" : "Lejoju përdoruesve të montojnë depozita të jashtme", + "Allow users to mount the following external storage" : "Lejoju përdoruesve të montojnë depozitën e jashtme vijuese" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/sr.js b/apps/files_external/l10n/sr.js index 633bfbcb423..02596d8ab22 100644 --- a/apps/files_external/l10n/sr.js +++ b/apps/files_external/l10n/sr.js @@ -69,7 +69,6 @@ OC.L10N.register( "Advanced settings" : "Напредне поставке", "Delete" : "Обриши", "Add storage" : "Додај складиште", - "Enable User External Storage" : "Укључи корисничко спољашње складиште", "Allow users to mount the following external storage" : "Дозволи корисницима да монтирају следећа спољашња складишта" }, "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/apps/files_external/l10n/sr.json b/apps/files_external/l10n/sr.json index 820bec758df..f1c12eeb0f7 100644 --- a/apps/files_external/l10n/sr.json +++ b/apps/files_external/l10n/sr.json @@ -67,7 +67,6 @@ "Advanced settings" : "Напредне поставке", "Delete" : "Обриши", "Add storage" : "Додај складиште", - "Enable User External Storage" : "Укључи корисничко спољашње складиште", "Allow users to mount the following external storage" : "Дозволи корисницима да монтирају следећа спољашња складишта" },"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/sr@latin.js b/apps/files_external/l10n/sr@latin.js index 0d1c69f4b75..880a33c1614 100644 --- a/apps/files_external/l10n/sr@latin.js +++ b/apps/files_external/l10n/sr@latin.js @@ -50,7 +50,6 @@ OC.L10N.register( "Available for" : "Dostupno za", "Delete" : "Obriši", "Add storage" : "Dodaj skladište", - "Enable User External Storage" : "Omogući korisničko spoljašnje skladište", "Allow users to mount the following external storage" : "Omogući korisnicima da namontiraju sledeće spoljašnje skladište" }, "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/apps/files_external/l10n/sr@latin.json b/apps/files_external/l10n/sr@latin.json index 747a9f1082d..760100867eb 100644 --- a/apps/files_external/l10n/sr@latin.json +++ b/apps/files_external/l10n/sr@latin.json @@ -48,7 +48,6 @@ "Available for" : "Dostupno za", "Delete" : "Obriši", "Add storage" : "Dodaj skladište", - "Enable User External Storage" : "Omogući korisničko spoljašnje skladište", "Allow users to mount the following external storage" : "Omogući korisnicima da namontiraju sledeće spoljašnje skladište" },"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/sv.js b/apps/files_external/l10n/sv.js index 2defbf76c8c..09786533e87 100644 --- a/apps/files_external/l10n/sv.js +++ b/apps/files_external/l10n/sv.js @@ -55,7 +55,6 @@ OC.L10N.register( "Available for" : "Tillgänglig för", "Delete" : "Radera", "Add storage" : "Lägg till lagring", - "Enable User External Storage" : "Aktivera extern lagring för användare", "Allow users to mount the following external storage" : "Tillåt användare att montera följande extern lagring" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/sv.json b/apps/files_external/l10n/sv.json index ec8b7ef37fb..ddb7439cb8d 100644 --- a/apps/files_external/l10n/sv.json +++ b/apps/files_external/l10n/sv.json @@ -53,7 +53,6 @@ "Available for" : "Tillgänglig för", "Delete" : "Radera", "Add storage" : "Lägg till lagring", - "Enable User External Storage" : "Aktivera extern lagring för användare", "Allow users to mount the following external storage" : "Tillåt användare att montera följande extern lagring" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/ta_LK.js b/apps/files_external/l10n/ta_LK.js index 382d5e943a1..8ab0f6d513c 100644 --- a/apps/files_external/l10n/ta_LK.js +++ b/apps/files_external/l10n/ta_LK.js @@ -18,7 +18,6 @@ OC.L10N.register( "External Storage" : "வெளி சேமிப்பு", "Folder name" : "கோப்புறை பெயர்", "Configuration" : "தகவமைப்பு", - "Delete" : "நீக்குக", - "Enable User External Storage" : "பயனாளர் வெளி சேமிப்பை இயலுமைப்படுத்துக" + "Delete" : "நீக்குக" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_external/l10n/ta_LK.json b/apps/files_external/l10n/ta_LK.json index c838cbe0173..dd1c1003728 100644 --- a/apps/files_external/l10n/ta_LK.json +++ b/apps/files_external/l10n/ta_LK.json @@ -16,7 +16,6 @@ "External Storage" : "வெளி சேமிப்பு", "Folder name" : "கோப்புறை பெயர்", "Configuration" : "தகவமைப்பு", - "Delete" : "நீக்குக", - "Enable User External Storage" : "பயனாளர் வெளி சேமிப்பை இயலுமைப்படுத்துக" + "Delete" : "நீக்குக" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/th_TH.js b/apps/files_external/l10n/th_TH.js index 9879265a709..c027e63642f 100644 --- a/apps/files_external/l10n/th_TH.js +++ b/apps/files_external/l10n/th_TH.js @@ -101,7 +101,6 @@ OC.L10N.register( "Advanced settings" : "ตั้งค่าขั้นสูง", "Delete" : "ลบ", "Add storage" : "เพิ่มพื้นที่จัดเก็บข้อมูล", - "Enable User External Storage" : "เปิดให้มีการใช้พื้นที่จัดเก็บข้อมูลของผู้ใช้งานจากภายนอกได้", "Allow users to mount the following external storage" : "อนุญาตให้ผู้ใช้ติดตั้งจัดเก็บข้อมูลภายนอกต่อไปนี้" }, "nplurals=1; plural=0;"); diff --git a/apps/files_external/l10n/th_TH.json b/apps/files_external/l10n/th_TH.json index 8c00efd7bb2..34bf501d7af 100644 --- a/apps/files_external/l10n/th_TH.json +++ b/apps/files_external/l10n/th_TH.json @@ -99,7 +99,6 @@ "Advanced settings" : "ตั้งค่าขั้นสูง", "Delete" : "ลบ", "Add storage" : "เพิ่มพื้นที่จัดเก็บข้อมูล", - "Enable User External Storage" : "เปิดให้มีการใช้พื้นที่จัดเก็บข้อมูลของผู้ใช้งานจากภายนอกได้", "Allow users to mount the following external storage" : "อนุญาตให้ผู้ใช้ติดตั้งจัดเก็บข้อมูลภายนอกต่อไปนี้" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/apps/files_external/l10n/tr.js b/apps/files_external/l10n/tr.js index 6eada0b32f3..619e5975ed8 100644 --- a/apps/files_external/l10n/tr.js +++ b/apps/files_external/l10n/tr.js @@ -101,7 +101,6 @@ OC.L10N.register( "Advanced settings" : "Gelişmiş ayarlar", "Delete" : "Sil", "Add storage" : "Depo ekle", - "Enable User External Storage" : "Kullanıcılar için Harici Depolamayı Etkinleştir", "Allow users to mount the following external storage" : "Kullanıcıların aşağıdaki harici depolamayı bağlamalarına izin ver" }, "nplurals=2; plural=(n > 1);"); diff --git a/apps/files_external/l10n/tr.json b/apps/files_external/l10n/tr.json index 7fd764d0165..cb315a333c2 100644 --- a/apps/files_external/l10n/tr.json +++ b/apps/files_external/l10n/tr.json @@ -99,7 +99,6 @@ "Advanced settings" : "Gelişmiş ayarlar", "Delete" : "Sil", "Add storage" : "Depo ekle", - "Enable User External Storage" : "Kullanıcılar için Harici Depolamayı Etkinleştir", "Allow users to mount the following external storage" : "Kullanıcıların aşağıdaki harici depolamayı bağlamalarına izin ver" },"pluralForm" :"nplurals=2; plural=(n > 1);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/uk.js b/apps/files_external/l10n/uk.js index 68b1787346a..f8ca8dc360c 100644 --- a/apps/files_external/l10n/uk.js +++ b/apps/files_external/l10n/uk.js @@ -64,7 +64,6 @@ OC.L10N.register( "Advanced settings" : "Розширені налаштування", "Delete" : "Видалити", "Add storage" : "Додати сховище", - "Enable User External Storage" : "Активувати користувацькі зовнішні сховища", "Allow users to mount the following external storage" : "Дозволити користувачам монтувати наступні зовнішні сховища" }, "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/apps/files_external/l10n/uk.json b/apps/files_external/l10n/uk.json index 5765a30dc59..c34d0d2c82f 100644 --- a/apps/files_external/l10n/uk.json +++ b/apps/files_external/l10n/uk.json @@ -62,7 +62,6 @@ "Advanced settings" : "Розширені налаштування", "Delete" : "Видалити", "Add storage" : "Додати сховище", - "Enable User External Storage" : "Активувати користувацькі зовнішні сховища", "Allow users to mount the following external storage" : "Дозволити користувачам монтувати наступні зовнішні сховища" },"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }
\ No newline at end of file diff --git a/apps/files_external/l10n/vi.js b/apps/files_external/l10n/vi.js index 49c746851eb..f3fdb39136c 100644 --- a/apps/files_external/l10n/vi.js +++ b/apps/files_external/l10n/vi.js @@ -22,7 +22,6 @@ OC.L10N.register( "Folder name" : "Tên thư mục", "Configuration" : "Cấu hình", "Delete" : "Xóa", - "Add storage" : "Thêm bộ nhớ", - "Enable User External Storage" : "Kích hoạt tính năng lưu trữ ngoài" + "Add storage" : "Thêm bộ nhớ" }, "nplurals=1; plural=0;"); diff --git a/apps/files_external/l10n/vi.json b/apps/files_external/l10n/vi.json index 4daddc7ae57..fdba39fc95e 100644 --- a/apps/files_external/l10n/vi.json +++ b/apps/files_external/l10n/vi.json @@ -20,7 +20,6 @@ "Folder name" : "Tên thư mục", "Configuration" : "Cấu hình", "Delete" : "Xóa", - "Add storage" : "Thêm bộ nhớ", - "Enable User External Storage" : "Kích hoạt tính năng lưu trữ ngoài" + "Add storage" : "Thêm bộ nhớ" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/apps/files_external/l10n/zh_CN.js b/apps/files_external/l10n/zh_CN.js index 3ba25300d95..4b99fc170a2 100644 --- a/apps/files_external/l10n/zh_CN.js +++ b/apps/files_external/l10n/zh_CN.js @@ -42,7 +42,6 @@ OC.L10N.register( "Available for" : "可用于", "Delete" : "删除", "Add storage" : "增加存储", - "Enable User External Storage" : "启用用户外部存储", "Allow users to mount the following external storage" : "允许用户挂载以下外部存储" }, "nplurals=1; plural=0;"); diff --git a/apps/files_external/l10n/zh_CN.json b/apps/files_external/l10n/zh_CN.json index 94301573ab9..fddc688c5c2 100644 --- a/apps/files_external/l10n/zh_CN.json +++ b/apps/files_external/l10n/zh_CN.json @@ -40,7 +40,6 @@ "Available for" : "可用于", "Delete" : "删除", "Add storage" : "增加存储", - "Enable User External Storage" : "启用用户外部存储", "Allow users to mount the following external storage" : "允许用户挂载以下外部存储" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/apps/files_external/l10n/zh_TW.js b/apps/files_external/l10n/zh_TW.js index e4e01cd2d59..d94c98cc026 100644 --- a/apps/files_external/l10n/zh_TW.js +++ b/apps/files_external/l10n/zh_TW.js @@ -6,6 +6,7 @@ OC.L10N.register( "System" : "系統", "Grant access" : "允許存取", "Access granted" : "允許存取", + "Enable encryption" : "啟用加密", "Saved" : "已儲存", "None" : "無", "Username" : "使用者名稱", @@ -33,7 +34,6 @@ OC.L10N.register( "Available for" : "可用的", "Delete" : "刪除", "Add storage" : "增加儲存區", - "Enable User External Storage" : "啓用使用者外部儲存", "Allow users to mount the following external storage" : "允許使用者自行掛載以下的外部儲存" }, "nplurals=1; plural=0;"); diff --git a/apps/files_external/l10n/zh_TW.json b/apps/files_external/l10n/zh_TW.json index 1ed049bc079..0d1c184f63d 100644 --- a/apps/files_external/l10n/zh_TW.json +++ b/apps/files_external/l10n/zh_TW.json @@ -4,6 +4,7 @@ "System" : "系統", "Grant access" : "允許存取", "Access granted" : "允許存取", + "Enable encryption" : "啟用加密", "Saved" : "已儲存", "None" : "無", "Username" : "使用者名稱", @@ -31,7 +32,6 @@ "Available for" : "可用的", "Delete" : "刪除", "Add storage" : "增加儲存區", - "Enable User External Storage" : "啓用使用者外部儲存", "Allow users to mount the following external storage" : "允許使用者自行掛載以下的外部儲存" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/apps/files_external/lib/dropbox.php b/apps/files_external/lib/dropbox.php index bf66ab78207..df8a0255134 100644 --- a/apps/files_external/lib/dropbox.php +++ b/apps/files_external/lib/dropbox.php @@ -64,7 +64,7 @@ class Dropbox extends \OC\Files\Storage\Common { * @param string $path */ private function deleteMetaData($path) { - $path = $this->root.$path; + $path = ltrim($this->root.$path, '/'); if (isset($this->metaData[$path])) { unset($this->metaData[$path]); return true; @@ -72,6 +72,10 @@ class Dropbox extends \OC\Files\Storage\Common { return false; } + private function setMetaData($path, $metaData) { + $this->metaData[ltrim($path, '/')] = $metaData; + } + /** * Returns the path's metadata * @param string $path path for which to return the metadata @@ -80,7 +84,7 @@ class Dropbox extends \OC\Files\Storage\Common { * false, null if the file doesn't exist or "false" if the operation failed */ private function getDropBoxMetaData($path, $list = false) { - $path = $this->root.$path; + $path = ltrim($this->root.$path, '/'); if ( ! $list && isset($this->metaData[$path])) { return $this->metaData[$path]; } else { @@ -96,14 +100,14 @@ class Dropbox extends \OC\Files\Storage\Common { // Cache folder's contents foreach ($response['contents'] as $file) { if (!isset($file['is_deleted']) || !$file['is_deleted']) { - $this->metaData[$path.'/'.basename($file['path'])] = $file; + $this->setMetaData($path.'/'.basename($file['path']), $file); $contents[] = $file; } } unset($response['contents']); } if (!isset($response['is_deleted']) || !$response['is_deleted']) { - $this->metaData[$path] = $response; + $this->setMetaData($path, $response); } // Return contents of folder only return $contents; @@ -116,7 +120,7 @@ class Dropbox extends \OC\Files\Storage\Common { $response = $this->dropbox->getMetaData($requestPath, 'false'); if (!isset($response['is_deleted']) || !$response['is_deleted']) { - $this->metaData[$path] = $response; + $this->setMetaData($path, $response); return $response; } return null; @@ -288,6 +292,7 @@ class Dropbox extends \OC\Files\Storage\Common { try { $this->dropbox->putFile(self::$tempFiles[$tmpFile], $handle); unlink($tmpFile); + $this->deleteMetaData(self::$tempFiles[$tmpFile]); } catch (\Exception $exception) { \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); } diff --git a/apps/files_external/templates/settings.php b/apps/files_external/templates/settings.php index 141994d0050..cac73cb87ee 100644 --- a/apps/files_external/templates/settings.php +++ b/apps/files_external/templates/settings.php @@ -26,7 +26,7 @@ <?php $checkboxId = uniqid("checkbox_"); ?> <input type="checkbox" id="<?php p($checkboxId); ?>" - <?php if (!empty($classes)): ?> class="<?php p(implode(' ', $classes)); ?>"<?php endif; ?> + <?php if (!empty($classes)): ?> class="checkbox <?php p(implode(' ', $classes)); ?>"<?php endif; ?> data-parameter="<?php p($parameter->getName()); ?>" <?php if ($value === true): ?> checked="checked"<?php endif; ?> /> @@ -191,9 +191,9 @@ <?php if ($_['isAdminPage']): ?> <br /> - <input type="checkbox" name="allowUserMounting" id="allowUserMounting" + <input type="checkbox" name="allowUserMounting" id="allowUserMounting" class="checkbox" value="1" <?php if ($_['allowUserMounting'] == 'yes') print_unescaped(' checked="checked"'); ?> /> - <label for="allowUserMounting"><?php p($l->t('Enable User External Storage')); ?></label> <span id="userMountingMsg" class="msg"></span> + <label for="allowUserMounting"><?php p($l->t('Allow users to mount external storages')); ?></label> <span id="userMountingMsg" class="msg"></span> <p id="userMountingBackends"<?php if ($_['allowUserMounting'] != 'yes'): ?> class="hidden"<?php endif; ?>> <?php p($l->t('Allow users to mount the following external storage')); ?><br /> @@ -201,7 +201,7 @@ <?php if ($deprecateTo = $backend->getDeprecateTo()): ?> <input type="hidden" id="allowUserMountingBackends<?php p($i); ?>" name="allowUserMountingBackends[]" value="<?php p($backend->getIdentifier()); ?>" data-deprecate-to="<?php p($deprecateTo->getIdentifier()); ?>" /> <?php else: ?> - <input type="checkbox" id="allowUserMountingBackends<?php p($i); ?>" name="allowUserMountingBackends[]" value="<?php p($backend->getIdentifier()); ?>" <?php if ($backend->isVisibleFor(BackendService::VISIBILITY_PERSONAL)) print_unescaped(' checked="checked"'); ?> /> + <input type="checkbox" id="allowUserMountingBackends<?php p($i); ?>" class="checkbox" name="allowUserMountingBackends[]" value="<?php p($backend->getIdentifier()); ?>" <?php if ($backend->isVisibleFor(BackendService::VISIBILITY_PERSONAL)) print_unescaped(' checked="checked"'); ?> /> <label for="allowUserMountingBackends<?php p($i); ?>"><?php p($backend->getText()); ?></label> <br /> <?php endif; ?> <?php $i++; ?> diff --git a/apps/files_external/tests/service/userglobalstoragesservicetest.php b/apps/files_external/tests/service/userglobalstoragesservicetest.php index 7c2516f0477..e88764d0f78 100644 --- a/apps/files_external/tests/service/userglobalstoragesservicetest.php +++ b/apps/files_external/tests/service/userglobalstoragesservicetest.php @@ -1,6 +1,7 @@ <?php /** * @author Robin McCorkell <rmccorkell@karoshi.org.uk> + * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 @@ -155,8 +156,9 @@ class UserGlobalStoragesServiceTest extends GlobalStoragesServiceTest { /** * @expectedException \DomainException + * @dataProvider deleteStorageDataProvider */ - public function testDeleteStorage() { + public function testDeleteStorage($backendOptions, $rustyStorageId, $expectedCountAfterDeletion) { $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); @@ -164,7 +166,7 @@ class UserGlobalStoragesServiceTest extends GlobalStoragesServiceTest { $storage->setMountPoint('mountpoint'); $storage->setBackend($backend); $storage->setAuthMechanism($authMechanism); - $storage->setBackendOptions(['password' => 'testPassword']); + $storage->setBackendOptions($backendOptions); $newStorage = $this->globalStoragesService->addStorage($storage); $this->assertEquals(1, $newStorage->getId()); diff --git a/apps/files_sharing/ajax/external.php b/apps/files_sharing/ajax/external.php index c80f0e0b288..0f8a3d56cf0 100644 --- a/apps/files_sharing/ajax/external.php +++ b/apps/files_sharing/ajax/external.php @@ -5,7 +5,7 @@ * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_sharing/ajax/list.php b/apps/files_sharing/ajax/list.php index 9819048b881..c7f0bde5d4a 100644 --- a/apps/files_sharing/ajax/list.php +++ b/apps/files_sharing/ajax/list.php @@ -3,7 +3,7 @@ * @author Joas Schilling <nickvergessen@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * diff --git a/apps/files_sharing/ajax/publicpreview.php b/apps/files_sharing/ajax/publicpreview.php index 62157e0ac0e..2902969b21f 100644 --- a/apps/files_sharing/ajax/publicpreview.php +++ b/apps/files_sharing/ajax/publicpreview.php @@ -4,7 +4,7 @@ * @author Georg Ehrke <georg@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_sharing/api/local.php b/apps/files_sharing/api/local.php index 42e77570f95..bb5136a0c99 100644 --- a/apps/files_sharing/api/local.php +++ b/apps/files_sharing/api/local.php @@ -4,7 +4,7 @@ * @author Joas Schilling <nickvergessen@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * diff --git a/apps/files_sharing/api/remote.php b/apps/files_sharing/api/remote.php index d67920c3521..41ebb6e2eab 100644 --- a/apps/files_sharing/api/remote.php +++ b/apps/files_sharing/api/remote.php @@ -1,7 +1,7 @@ <?php /** * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/api/server2server.php b/apps/files_sharing/api/server2server.php index a74b329aed5..93998ad774e 100644 --- a/apps/files_sharing/api/server2server.php +++ b/apps/files_sharing/api/server2server.php @@ -3,6 +3,7 @@ * @author Arthur Schiwon <blizzz@owncloud.com> * @author Björn Schießle <schiessle@owncloud.com> * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * * @copyright Copyright (c) 2015, ownCloud, Inc. @@ -25,6 +26,7 @@ namespace OCA\Files_Sharing\API; use OCA\Files_Sharing\Activity; +use OCP\Files\NotFoundException; class Server2Server { @@ -53,6 +55,7 @@ class Server2Server { return new \OC_OCS_Result(null, 400, 'The mountpoint name contains invalid characters.'); } + // FIXME this should be a method in the user management instead \OCP\Util::writeLog('files_sharing', 'shareWith before, ' . $shareWith, \OCP\Util::DEBUG); \OCP\Util::emitHook( '\OCA\Files_Sharing\API\Server2Server', @@ -263,7 +266,11 @@ class Server2Server { private function getFile($user, $fileSource) { \OC_Util::setupFS($user); - $file = \OC\Files\Filesystem::getPath($fileSource); + try { + $file = \OC\Files\Filesystem::getPath($fileSource); + } catch (NotFoundException $e) { + $file = null; + } $args = \OC\Files\Filesystem::is_dir($file) ? array('dir' => $file) : array('dir' => dirname($file), 'scrollto' => $file); $link = \OCP\Util::linkToAbsolute('files', 'index.php', $args); diff --git a/apps/files_sharing/api/sharees.php b/apps/files_sharing/api/sharees.php index b34aef72163..21f68d9b253 100644 --- a/apps/files_sharing/api/sharees.php +++ b/apps/files_sharing/api/sharees.php @@ -1,7 +1,7 @@ <?php /** * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/appinfo/application.php b/apps/files_sharing/appinfo/application.php index 9dc0e0618b5..545a9425083 100644 --- a/apps/files_sharing/appinfo/application.php +++ b/apps/files_sharing/appinfo/application.php @@ -3,7 +3,8 @@ * @author Joas Schilling <nickvergessen@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Robin Appelman <icewind@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> + * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/appinfo/info.xml b/apps/files_sharing/appinfo/info.xml index 28c64cbb1b4..72e56456961 100644 --- a/apps/files_sharing/appinfo/info.xml +++ b/apps/files_sharing/appinfo/info.xml @@ -9,14 +9,16 @@ Turning the feature off removes shared files and folders on the server for all s </description> <licence>AGPL</licence> <author>Michael Gapczynski, Bjoern Schiessle</author> - <requiremin>4.93</requiremin> <shipped>true</shipped> <default_enable/> + <version>0.8.1</version> <types> <filesystem/> </types> + <dependencies> + <owncloud min-version="9.0" /> + </dependencies> <public> <files>public.php</files> - <webdav>publicwebdav.php</webdav> </public> </info> diff --git a/apps/files_sharing/appinfo/install.php b/apps/files_sharing/appinfo/install.php index f076a17e444..5185ae883f3 100644 --- a/apps/files_sharing/appinfo/install.php +++ b/apps/files_sharing/appinfo/install.php @@ -1,6 +1,7 @@ <?php /** * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 @@ -20,3 +21,4 @@ */ \OC::$server->getJobList()->add('OCA\Files_sharing\Lib\DeleteOrphanedSharesJob'); +\OC::$server->getJobList()->add('OCA\Files_sharing\ExpireSharesJob'); diff --git a/apps/files_sharing/appinfo/routes.php b/apps/files_sharing/appinfo/routes.php index a466c4fc6cc..db7aa126c4e 100644 --- a/apps/files_sharing/appinfo/routes.php +++ b/apps/files_sharing/appinfo/routes.php @@ -5,7 +5,7 @@ * @author Joas Schilling <nickvergessen@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Robin Appelman <icewind@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_sharing/appinfo/update.php b/apps/files_sharing/appinfo/update.php index 0eb3224c1ca..8bff20cc442 100644 --- a/apps/files_sharing/appinfo/update.php +++ b/apps/files_sharing/appinfo/update.php @@ -2,6 +2,7 @@ /** * @author Björn Schießle <schiessle@owncloud.com> * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 @@ -30,3 +31,4 @@ if (version_compare($installedVersion, '0.6.0', '<')) { } \OC::$server->getJobList()->add('OCA\Files_sharing\Lib\DeleteOrphanedSharesJob'); +\OC::$server->getJobList()->add('OCA\Files_sharing\ExpireSharesJob'); diff --git a/apps/files_sharing/appinfo/version b/apps/files_sharing/appinfo/version deleted file mode 100644 index 844f6a91acb..00000000000 --- a/apps/files_sharing/appinfo/version +++ /dev/null @@ -1 +0,0 @@ -0.6.3 diff --git a/apps/files_sharing/css/sharetabview.css b/apps/files_sharing/css/sharetabview.css index 1745eba5846..7597004e684 100644 --- a/apps/files_sharing/css/sharetabview.css +++ b/apps/files_sharing/css/sharetabview.css @@ -6,7 +6,6 @@ .shareTabView .shareWithLoading { padding-left: 10px; - position: relative; right: 30px; top: 2px; } @@ -73,9 +72,18 @@ } .shareTabView .icon-loading-small { - position: absolute; display: inline-block; z-index: 1; background-color: white; - padding: 2px; + padding: 2px 0; +} + +.shareTabView .shareWithList .icon-loading-small, +.shareTabView .linkShareView .icon-loading-small { + position: absolute; +} + +.shareTabView .linkPass .icon-loading-small { + margin-top: 9px; } + diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js index 16b4f1589b1..246b639f652 100644 --- a/apps/files_sharing/js/public.js +++ b/apps/files_sharing/js/public.js @@ -54,7 +54,7 @@ OCA.Sharing.PublicApp = { $el, { id: 'files.public', - scrollContainer: $(window), + scrollContainer: $('#content-wrapper'), dragOptions: dragOptions, folderDropOptions: folderDropOptions, fileActions: fileActions, diff --git a/apps/files_sharing/l10n/cs_CZ.js b/apps/files_sharing/l10n/cs_CZ.js index 7c52f1c21ff..daa43cc46e8 100644 --- a/apps/files_sharing/l10n/cs_CZ.js +++ b/apps/files_sharing/l10n/cs_CZ.js @@ -52,6 +52,7 @@ OC.L10N.register( "Shared by %2$s" : "%2$s sdílel(a)", "Shared via public link" : "Sdíleno jako veřejný odkaz", "Shares" : "Sdílení", + "You received %2$s as a remote share from %1$s" : "Obdrželi jste %2$s jako vzdálené sdílení od %1$s", "Accept" : "Přijmout", "Decline" : "Zamítnout", "Share with me through my #ownCloud Federated Cloud ID, see %s" : "Sdílej se mnou pomocí mého #ownCloud sdruženého cloud ID, více na %s", diff --git a/apps/files_sharing/l10n/cs_CZ.json b/apps/files_sharing/l10n/cs_CZ.json index 0e139f7bbf5..f1bd6fa4934 100644 --- a/apps/files_sharing/l10n/cs_CZ.json +++ b/apps/files_sharing/l10n/cs_CZ.json @@ -50,6 +50,7 @@ "Shared by %2$s" : "%2$s sdílel(a)", "Shared via public link" : "Sdíleno jako veřejný odkaz", "Shares" : "Sdílení", + "You received %2$s as a remote share from %1$s" : "Obdrželi jste %2$s jako vzdálené sdílení od %1$s", "Accept" : "Přijmout", "Decline" : "Zamítnout", "Share with me through my #ownCloud Federated Cloud ID, see %s" : "Sdílej se mnou pomocí mého #ownCloud sdruženého cloud ID, více na %s", diff --git a/apps/files_sharing/l10n/fr.js b/apps/files_sharing/l10n/fr.js index 31680b85fcd..9d26127d472 100644 --- a/apps/files_sharing/l10n/fr.js +++ b/apps/files_sharing/l10n/fr.js @@ -44,6 +44,12 @@ OC.L10N.register( "%2$s shared %1$s with you" : "%2$s a partagé %1$s avec vous", "You shared %1$s via link" : "Vous avez partagé %1$s par lien public", "Downloaded via public link" : "Téléchargé par lien public", + "Shared with %2$s" : "Partagé avec %2$s", + "Shared with group %2$s" : "Partagé avec le groupe %2$s", + "Shared with %3$s by %2$s" : "Partagé avec %3$s par %2$s", + "Shared with group %3$s by %2$s" : "Partagé avec le groupe %3$s par %2$s", + "Shared via link by %2$s" : "Partagé via lien par %2$s", + "Shared by %2$s" : "Partagé par %2$s", "Shared via public link" : "Partagé par lien public", "Shares" : "Partages", "You received %2$s as a remote share from %1$s" : "Vous avez obtenu l'accès au partage distant %2$s de %1$s", diff --git a/apps/files_sharing/l10n/fr.json b/apps/files_sharing/l10n/fr.json index 6a3cca61374..0f1edc51dc7 100644 --- a/apps/files_sharing/l10n/fr.json +++ b/apps/files_sharing/l10n/fr.json @@ -42,6 +42,12 @@ "%2$s shared %1$s with you" : "%2$s a partagé %1$s avec vous", "You shared %1$s via link" : "Vous avez partagé %1$s par lien public", "Downloaded via public link" : "Téléchargé par lien public", + "Shared with %2$s" : "Partagé avec %2$s", + "Shared with group %2$s" : "Partagé avec le groupe %2$s", + "Shared with %3$s by %2$s" : "Partagé avec %3$s par %2$s", + "Shared with group %3$s by %2$s" : "Partagé avec le groupe %3$s par %2$s", + "Shared via link by %2$s" : "Partagé via lien par %2$s", + "Shared by %2$s" : "Partagé par %2$s", "Shared via public link" : "Partagé par lien public", "Shares" : "Partages", "You received %2$s as a remote share from %1$s" : "Vous avez obtenu l'accès au partage distant %2$s de %1$s", diff --git a/apps/files_sharing/l10n/ja.js b/apps/files_sharing/l10n/ja.js index 413f4a2ac8c..d2e3bc9b263 100644 --- a/apps/files_sharing/l10n/ja.js +++ b/apps/files_sharing/l10n/ja.js @@ -67,6 +67,7 @@ OC.L10N.register( "Federated Cloud" : "クラウド連携", "Your Federated Cloud ID:" : "あなたのクラウド連携ID:", "Share it:" : "以下で共有:", + "Add to your website" : "ウェブサイトに追加", "Share with me via ownCloud" : "OwnCloud経由で共有", "HTML Code:" : "HTMLコード:" }, diff --git a/apps/files_sharing/l10n/ja.json b/apps/files_sharing/l10n/ja.json index 65d762871de..16ca792e33d 100644 --- a/apps/files_sharing/l10n/ja.json +++ b/apps/files_sharing/l10n/ja.json @@ -65,6 +65,7 @@ "Federated Cloud" : "クラウド連携", "Your Federated Cloud ID:" : "あなたのクラウド連携ID:", "Share it:" : "以下で共有:", + "Add to your website" : "ウェブサイトに追加", "Share with me via ownCloud" : "OwnCloud経由で共有", "HTML Code:" : "HTMLコード:" },"pluralForm" :"nplurals=1; plural=0;" diff --git a/apps/files_sharing/l10n/nb_NO.js b/apps/files_sharing/l10n/nb_NO.js index a8f7fbdbc9b..4f7fb08ef16 100644 --- a/apps/files_sharing/l10n/nb_NO.js +++ b/apps/files_sharing/l10n/nb_NO.js @@ -43,6 +43,14 @@ OC.L10N.register( "%2$s shared %1$s via link" : "%2$s delte %1$s via lenke", "%2$s shared %1$s with you" : "%2$s delte %1$s med deg", "You shared %1$s via link" : "Du delte %1$s via lenke", + "Downloaded via public link" : "Nedlastet via offentlig lenke", + "Shared with %2$s" : "Delt med %2$s", + "Shared with group %2$s" : "Delt med gruppe %2$s", + "Shared with %3$s by %2$s" : "Delt med %3$s av %2$s", + "Shared with group %3$s by %2$s" : "Delt med gruppe %3$s av %2$s", + "Shared via link by %2$s" : "Delt via lenke av %2$s", + "Shared by %2$s" : "Delt av %2$s", + "Shared via public link" : "Delt via offentlig lenke", "Shares" : "Delinger", "You received %2$s as a remote share from %1$s" : "Du mottok %2$s som en ekstern deling fra %1$s", "Accept" : "Aksepter", diff --git a/apps/files_sharing/l10n/nb_NO.json b/apps/files_sharing/l10n/nb_NO.json index e9c67955708..e75f095f354 100644 --- a/apps/files_sharing/l10n/nb_NO.json +++ b/apps/files_sharing/l10n/nb_NO.json @@ -41,6 +41,14 @@ "%2$s shared %1$s via link" : "%2$s delte %1$s via lenke", "%2$s shared %1$s with you" : "%2$s delte %1$s med deg", "You shared %1$s via link" : "Du delte %1$s via lenke", + "Downloaded via public link" : "Nedlastet via offentlig lenke", + "Shared with %2$s" : "Delt med %2$s", + "Shared with group %2$s" : "Delt med gruppe %2$s", + "Shared with %3$s by %2$s" : "Delt med %3$s av %2$s", + "Shared with group %3$s by %2$s" : "Delt med gruppe %3$s av %2$s", + "Shared via link by %2$s" : "Delt via lenke av %2$s", + "Shared by %2$s" : "Delt av %2$s", + "Shared via public link" : "Delt via offentlig lenke", "Shares" : "Delinger", "You received %2$s as a remote share from %1$s" : "Du mottok %2$s som en ekstern deling fra %1$s", "Accept" : "Aksepter", diff --git a/apps/files_sharing/l10n/sq.js b/apps/files_sharing/l10n/sq.js index c5933a0fa5e..413214d58de 100644 --- a/apps/files_sharing/l10n/sq.js +++ b/apps/files_sharing/l10n/sq.js @@ -1,26 +1,84 @@ OC.L10N.register( "files_sharing", { - "Cancel" : "Anullo", + "Server to server sharing is not enabled on this server" : "Ndarja mes shërbyesish s’është e aktivizuar në këtë shërbyes", + "The mountpoint name contains invalid characters." : "Emri i pikës së montimit përmban shenja të pavlefshme.", + "Invalid or untrusted SSL certificate" : "Dëshmi SSL e pavlefshme ose e pabesuar", + "Could not authenticate to remote share, password might be wrong" : "S’bëri dot mirëfilltësimin te ndarja e largët, fjalëkalimi mund të jetë i gabuar", + "Storage not valid" : "Depozitë jo e vlefshme", + "Couldn't add remote share" : "S’shtoi dotë ndarje të largët", + "Shared with you" : "Të ndara me ju", + "Shared with others" : "Të ndara me të tjerët", + "Shared by link" : "Të ndara me lidhje", + "Nothing shared with you yet" : "Ende pa ndarë gjë me ju", + "Files and folders others share with you will show up here" : "Këtu do të shfaqen kartelat dhe dosjet që të jerët ndajnë me ju", + "Nothing shared yet" : "Ende pa ndarë gjë", + "Files and folders you share will show up here" : "Këtu do të shfaqen kartelat dhe dosjet që ndani me të tjerët", + "No shared links" : "Pa lidhje ndarjesh", + "Files and folders you share by link will show up here" : "Këtu do të shfaqen kartelat dhe dosjet që ndani përmes lidhjesh", + "Do you want to add the remote share {name} from {owner}@{remote}?" : "Doni të shtohet ndarja e largët {name} nga {owner}@{remote}?", + "Remote share" : "Ndarje e largët", + "Remote share password" : "Fjalëkalim ndarjeje të largët", + "Cancel" : "Anuloje", + "Add remote share" : "Shtoni ndarje të largët", + "You can upload into this folder" : "Mund të ngarkoni te kjo dosje", + "No ownCloud installation (7 or higher) found at {remote}" : "Te {remote} s’u gjet instalim ownCloud (7 ose më sipër)", + "Invalid ownCloud url" : "URL ownCloud e pavlefshme", "Shared by" : "Ndarë nga", "Sharing" : "Ndarje", - "A file or folder has been <strong>shared</strong>" : "Një skedar ose dosje është <strong>ndarë</strong>", - "You shared %1$s with %2$s" : "Ju ndatë %1$s me %2$s", - "You shared %1$s with group %2$s" : "Ju ndatë %1$s me grupin %2$s", + "A file or folder has been <strong>shared</strong>" : "U <strong>nda me të tjerë</strong> një kartelë ose dosje", + "A file or folder was shared from <strong>another server</strong>" : "Një kartelë ose dosje u nda prej një <strong>shërbyesi tjetër</strong>", + "A public shared file or folder was <strong>downloaded</strong>" : "<strong>U shkarkua</strong> një kartelë ose dosje e ndarë me të tjerët publikisht", + "You received a new remote share %2$s from %1$s" : "Morët një ndarje të largët %2$s nga %1$s", + "You received a new remote share from %s" : "Morët një ndarje të largët nga %s", + "%1$s accepted remote share %2$s" : "%1$s pranoi ndarjen e largët %2$s", + "%1$s declined remote share %2$s" : "%1$s hodhi tej ndarjen e largët %2$s", + "Public shared folder %1$s was downloaded" : "U shkarkua dosja e ndarë publikisht %1$s", + "Public shared file %1$s was downloaded" : "U shkarkua kartela e ndarë publikisht %1$s", + "You shared %1$s with %2$s" : "Ndatë %1$s me %2$s", + "You shared %1$s with group %2$s" : "Ndatë %1$s me grupin %2$s", + "%2$s shared %1$s with %3$s" : "%2$s ndau %1$s me %3$s", + "%2$s shared %1$s with group %3$s" : "%2$s ndau %1$s me grupin %3$s", + "%2$s shared %1$s via link" : "%2$s ndau %1$s përmes një lidhjeje", "%2$s shared %1$s with you" : "%2$s ndau %1$s me ju", - "You shared %1$s via link" : "Ju ndatë %1$s me lidhje", - "Shares" : "ndarjet", + "You shared %1$s via link" : "Ndatë %1$s përmes një lidhjeje", + "Downloaded via public link" : "Shkarkuar përmes një lidhjeje publike", + "Shared with %2$s" : "U nda me %2$s", + "Shared with group %2$s" : "U nda me grupin %2$s", + "Shared with %3$s by %2$s" : "U nda me %3$s nga %2$s", + "Shared with group %3$s by %2$s" : "U nda me grupin %3$s nga %2$s", + "Shared via link by %2$s" : "U nda përmes një lidhje nga %2$s", + "Shared by %2$s" : "U nda nga %2$s", + "Shared via public link" : "U nda përmes një lidhje publike", + "Shares" : "Ndarje", + "You received %2$s as a remote share from %1$s" : "%2$s e morët si një ndarje të largët prej %1$s", + "Accept" : "Pranoje", + "Decline" : "Hidhe poshtë", + "Share with me through my #ownCloud Federated Cloud ID, see %s" : "Ndani me mua përmes ID-së time për #ownCloud Federated Cloud, shihni %s", + "Share with me through my #ownCloud Federated Cloud ID" : "Ndani me mua përmes ID-së time për #ownCloud Federated Cloud", "This share is password-protected" : "Kjo pjesë është e mbrojtur me fjalëkalim", - "The password is wrong. Try again." : "Kodi është i gabuar. Provojeni përsëri.", - "Password" : "Kodi", - "Name" : "Emri", - "Sorry, this link doesn’t seem to work anymore." : "Ju kërkojmë ndjesë, kjo lidhje duket sikur nuk punon më.", + "The password is wrong. Try again." : "Fjalëkalimi është i gabuar. Riprovoni.", + "Password" : "Fjalëkalim", + "No entries found in this folder" : "S’u gjetën zëra në këtë dosje", + "Name" : "Emër", + "Share time" : "Kohë ndarjeje", + "Sorry, this link doesn’t seem to work anymore." : "Na ndjeni, kjo lidhje duket se nuk funksionon më.", "Reasons might be:" : "Arsyet mund të jenë:", - "the item was removed" : "elementi është eliminuar", + "the item was removed" : "objekti është hequr", "the link expired" : "lidhja ka skaduar", - "sharing is disabled" : "ndarja është çaktivizuar", - "For more info, please ask the person who sent this link." : "Për më shumë informacione, ju lutem pyesni personin që iu dërgoi këtë lidhje.", + "sharing is disabled" : "ndarjet janë çaktivizuar", + "For more info, please ask the person who sent this link." : "Për më shumë të dhëna, ju lutemi, pyetni personin që ju dërgoi këtë lidhje.", + "Add to your ownCloud" : "Shtojeni te ownCloud-i juaj", "Download" : "Shkarko", - "Direct link" : "Lidhje direkte" + "Download %s" : "Shkarko %s", + "Direct link" : "Lidhje e drejtpërdrejtë", + "Open documentation" : "Hap dokumentimin", + "Allow users on this server to send shares to other servers" : "Lejoju përdoruesve në këtë shërbyes të dërgojnë ndarje në shërbyes të tjerë", + "Allow users on this server to receive shares from other servers" : "Lejoju përdoruesve në këtë shërbyes të marrin ndarje nga shërbyes të tjerë", + "Your Federated Cloud ID:" : "ID-ja juaj Federated Cloud:", + "Share it:" : "Ndajeni:", + "Add to your website" : "Shtojeni te sajti juaj", + "Share with me via ownCloud" : "Ndani me mua përmes ownCloud-it", + "HTML Code:" : "Kod HTML:" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_sharing/l10n/sq.json b/apps/files_sharing/l10n/sq.json index e7c43d75aca..8722966f5da 100644 --- a/apps/files_sharing/l10n/sq.json +++ b/apps/files_sharing/l10n/sq.json @@ -1,24 +1,82 @@ { "translations": { - "Cancel" : "Anullo", + "Server to server sharing is not enabled on this server" : "Ndarja mes shërbyesish s’është e aktivizuar në këtë shërbyes", + "The mountpoint name contains invalid characters." : "Emri i pikës së montimit përmban shenja të pavlefshme.", + "Invalid or untrusted SSL certificate" : "Dëshmi SSL e pavlefshme ose e pabesuar", + "Could not authenticate to remote share, password might be wrong" : "S’bëri dot mirëfilltësimin te ndarja e largët, fjalëkalimi mund të jetë i gabuar", + "Storage not valid" : "Depozitë jo e vlefshme", + "Couldn't add remote share" : "S’shtoi dotë ndarje të largët", + "Shared with you" : "Të ndara me ju", + "Shared with others" : "Të ndara me të tjerët", + "Shared by link" : "Të ndara me lidhje", + "Nothing shared with you yet" : "Ende pa ndarë gjë me ju", + "Files and folders others share with you will show up here" : "Këtu do të shfaqen kartelat dhe dosjet që të jerët ndajnë me ju", + "Nothing shared yet" : "Ende pa ndarë gjë", + "Files and folders you share will show up here" : "Këtu do të shfaqen kartelat dhe dosjet që ndani me të tjerët", + "No shared links" : "Pa lidhje ndarjesh", + "Files and folders you share by link will show up here" : "Këtu do të shfaqen kartelat dhe dosjet që ndani përmes lidhjesh", + "Do you want to add the remote share {name} from {owner}@{remote}?" : "Doni të shtohet ndarja e largët {name} nga {owner}@{remote}?", + "Remote share" : "Ndarje e largët", + "Remote share password" : "Fjalëkalim ndarjeje të largët", + "Cancel" : "Anuloje", + "Add remote share" : "Shtoni ndarje të largët", + "You can upload into this folder" : "Mund të ngarkoni te kjo dosje", + "No ownCloud installation (7 or higher) found at {remote}" : "Te {remote} s’u gjet instalim ownCloud (7 ose më sipër)", + "Invalid ownCloud url" : "URL ownCloud e pavlefshme", "Shared by" : "Ndarë nga", "Sharing" : "Ndarje", - "A file or folder has been <strong>shared</strong>" : "Një skedar ose dosje është <strong>ndarë</strong>", - "You shared %1$s with %2$s" : "Ju ndatë %1$s me %2$s", - "You shared %1$s with group %2$s" : "Ju ndatë %1$s me grupin %2$s", + "A file or folder has been <strong>shared</strong>" : "U <strong>nda me të tjerë</strong> një kartelë ose dosje", + "A file or folder was shared from <strong>another server</strong>" : "Një kartelë ose dosje u nda prej një <strong>shërbyesi tjetër</strong>", + "A public shared file or folder was <strong>downloaded</strong>" : "<strong>U shkarkua</strong> një kartelë ose dosje e ndarë me të tjerët publikisht", + "You received a new remote share %2$s from %1$s" : "Morët një ndarje të largët %2$s nga %1$s", + "You received a new remote share from %s" : "Morët një ndarje të largët nga %s", + "%1$s accepted remote share %2$s" : "%1$s pranoi ndarjen e largët %2$s", + "%1$s declined remote share %2$s" : "%1$s hodhi tej ndarjen e largët %2$s", + "Public shared folder %1$s was downloaded" : "U shkarkua dosja e ndarë publikisht %1$s", + "Public shared file %1$s was downloaded" : "U shkarkua kartela e ndarë publikisht %1$s", + "You shared %1$s with %2$s" : "Ndatë %1$s me %2$s", + "You shared %1$s with group %2$s" : "Ndatë %1$s me grupin %2$s", + "%2$s shared %1$s with %3$s" : "%2$s ndau %1$s me %3$s", + "%2$s shared %1$s with group %3$s" : "%2$s ndau %1$s me grupin %3$s", + "%2$s shared %1$s via link" : "%2$s ndau %1$s përmes një lidhjeje", "%2$s shared %1$s with you" : "%2$s ndau %1$s me ju", - "You shared %1$s via link" : "Ju ndatë %1$s me lidhje", - "Shares" : "ndarjet", + "You shared %1$s via link" : "Ndatë %1$s përmes një lidhjeje", + "Downloaded via public link" : "Shkarkuar përmes një lidhjeje publike", + "Shared with %2$s" : "U nda me %2$s", + "Shared with group %2$s" : "U nda me grupin %2$s", + "Shared with %3$s by %2$s" : "U nda me %3$s nga %2$s", + "Shared with group %3$s by %2$s" : "U nda me grupin %3$s nga %2$s", + "Shared via link by %2$s" : "U nda përmes një lidhje nga %2$s", + "Shared by %2$s" : "U nda nga %2$s", + "Shared via public link" : "U nda përmes një lidhje publike", + "Shares" : "Ndarje", + "You received %2$s as a remote share from %1$s" : "%2$s e morët si një ndarje të largët prej %1$s", + "Accept" : "Pranoje", + "Decline" : "Hidhe poshtë", + "Share with me through my #ownCloud Federated Cloud ID, see %s" : "Ndani me mua përmes ID-së time për #ownCloud Federated Cloud, shihni %s", + "Share with me through my #ownCloud Federated Cloud ID" : "Ndani me mua përmes ID-së time për #ownCloud Federated Cloud", "This share is password-protected" : "Kjo pjesë është e mbrojtur me fjalëkalim", - "The password is wrong. Try again." : "Kodi është i gabuar. Provojeni përsëri.", - "Password" : "Kodi", - "Name" : "Emri", - "Sorry, this link doesn’t seem to work anymore." : "Ju kërkojmë ndjesë, kjo lidhje duket sikur nuk punon më.", + "The password is wrong. Try again." : "Fjalëkalimi është i gabuar. Riprovoni.", + "Password" : "Fjalëkalim", + "No entries found in this folder" : "S’u gjetën zëra në këtë dosje", + "Name" : "Emër", + "Share time" : "Kohë ndarjeje", + "Sorry, this link doesn’t seem to work anymore." : "Na ndjeni, kjo lidhje duket se nuk funksionon më.", "Reasons might be:" : "Arsyet mund të jenë:", - "the item was removed" : "elementi është eliminuar", + "the item was removed" : "objekti është hequr", "the link expired" : "lidhja ka skaduar", - "sharing is disabled" : "ndarja është çaktivizuar", - "For more info, please ask the person who sent this link." : "Për më shumë informacione, ju lutem pyesni personin që iu dërgoi këtë lidhje.", + "sharing is disabled" : "ndarjet janë çaktivizuar", + "For more info, please ask the person who sent this link." : "Për më shumë të dhëna, ju lutemi, pyetni personin që ju dërgoi këtë lidhje.", + "Add to your ownCloud" : "Shtojeni te ownCloud-i juaj", "Download" : "Shkarko", - "Direct link" : "Lidhje direkte" + "Download %s" : "Shkarko %s", + "Direct link" : "Lidhje e drejtpërdrejtë", + "Open documentation" : "Hap dokumentimin", + "Allow users on this server to send shares to other servers" : "Lejoju përdoruesve në këtë shërbyes të dërgojnë ndarje në shërbyes të tjerë", + "Allow users on this server to receive shares from other servers" : "Lejoju përdoruesve në këtë shërbyes të marrin ndarje nga shërbyes të tjerë", + "Your Federated Cloud ID:" : "ID-ja juaj Federated Cloud:", + "Share it:" : "Ndajeni:", + "Add to your website" : "Shtojeni te sajti juaj", + "Share with me via ownCloud" : "Ndani me mua përmes ownCloud-it", + "HTML Code:" : "Kod HTML:" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_sharing/l10n/sv.js b/apps/files_sharing/l10n/sv.js index 9b1d9ccb591..47f5f6b368c 100644 --- a/apps/files_sharing/l10n/sv.js +++ b/apps/files_sharing/l10n/sv.js @@ -40,6 +40,7 @@ OC.L10N.register( "This share is password-protected" : "Den här delningen är lösenordsskyddad", "The password is wrong. Try again." : "Lösenordet är fel. Försök igen.", "Password" : "Lösenord", + "No entries found in this folder" : "nga Filer hittades i denna mapp", "Name" : "Namn", "Share time" : "Delningstid", "Sorry, this link doesn’t seem to work anymore." : "Tyvärr, denna länk verkar inte fungera längre.", diff --git a/apps/files_sharing/l10n/sv.json b/apps/files_sharing/l10n/sv.json index 0d13069dfba..b20343b3594 100644 --- a/apps/files_sharing/l10n/sv.json +++ b/apps/files_sharing/l10n/sv.json @@ -38,6 +38,7 @@ "This share is password-protected" : "Den här delningen är lösenordsskyddad", "The password is wrong. Try again." : "Lösenordet är fel. Försök igen.", "Password" : "Lösenord", + "No entries found in this folder" : "nga Filer hittades i denna mapp", "Name" : "Namn", "Share time" : "Delningstid", "Sorry, this link doesn’t seem to work anymore." : "Tyvärr, denna länk verkar inte fungera längre.", diff --git a/apps/files_sharing/l10n/zh_TW.js b/apps/files_sharing/l10n/zh_TW.js index bc49d1d8a48..b7e7ca4f91f 100644 --- a/apps/files_sharing/l10n/zh_TW.js +++ b/apps/files_sharing/l10n/zh_TW.js @@ -2,24 +2,57 @@ OC.L10N.register( "files_sharing", { "Server to server sharing is not enabled on this server" : "伺服器對伺服器共享在這台伺服器上面並未啟用", + "Invalid or untrusted SSL certificate" : "無效或是不信任的SSL憑證", + "Could not authenticate to remote share, password might be wrong" : "無法驗證遠端分享,可能是密碼錯誤", + "Storage not valid" : "儲存空間無法使用", "Couldn't add remote share" : "無法加入遠端分享", "Shared with you" : "與你分享", "Shared with others" : "與其他人分享", "Shared by link" : "由連結分享", + "Nothing shared with you yet" : "目前沒有任何與您分享的內容", + "Files and folders others share with you will show up here" : "與您分享的檔案與資料夾將會顯示在這裡", + "Nothing shared yet" : "目前沒有分享內容", + "Files and folders you share will show up here" : "您分享的檔案與資料夾將會顯示在這裡", + "No shared links" : "沒有已分享的連結", + "Files and folders you share by link will show up here" : "您分享的檔案與資料夾連結將會顯示在這裡", "Do you want to add the remote share {name} from {owner}@{remote}?" : "是否要加入來自 {owner}@{remote} 的遠端分享 {name} ?", "Remote share" : "遠端分享", "Remote share password" : "遠端分享密碼", "Cancel" : "取消", "Add remote share" : "加入遠端分享", + "You can upload into this folder" : "你可以上傳內容到此資料夾", "Invalid ownCloud url" : "無效的 ownCloud URL", "Shared by" : "由...分享", "Sharing" : "分享", "A file or folder has been <strong>shared</strong>" : "檔案或目錄已被 <strong>分享</strong>", + "A file or folder was shared from <strong>another server</strong>" : "檔案或目錄已被 <strong>其他伺服器</strong> 分享", + "A public shared file or folder was <strong>downloaded</strong>" : "共享檔案或目錄已被 <strong>下載</strong>", + "You received a new remote share %2$s from %1$s" : "您收到了一個遠端分享 %2$s 來自於 %1$s", + "You received a new remote share from %s" : "您收到了一個遠端分享來自於 %s", + "%1$s accepted remote share %2$s" : "%1$s 接受了遠端分享 %2$s", + "%1$s declined remote share %2$s" : "%1$s 拒絕了遠端分享 %2$s", + "Public shared folder %1$s was downloaded" : "共享資料夾 %1$s 已被下載", + "Public shared file %1$s was downloaded" : "共享檔案 %1$s 已被下載", "You shared %1$s with %2$s" : "您與 %2$s 分享了 %1$s", "You shared %1$s with group %2$s" : "您與 %2$s 群組分享了 %1$s", + "%2$s shared %1$s with %3$s" : "%2$s 與 %3$s 分享了 %1$s", + "%2$s shared %1$s with group %3$s" : "%2$s 與群組 %3$s 分享了 %1$s", + "%2$s shared %1$s via link" : "%2$s 透過連結分享了 %1$s ", "%2$s shared %1$s with you" : "%2$s 與您分享了 %1$s", "You shared %1$s via link" : "您以連結分享了 %1$s", + "Downloaded via public link" : "透過公用連結下載", + "Shared with %2$s" : "與 %2$s 分享", + "Shared with group %2$s" : "與群組 %2$s 分享", + "Shared with %3$s by %2$s" : "透過 %2$s 與 %3$s 分享", + "Shared with group %3$s by %2$s" : "透過 %2$s 與群組 %3$s 分享", + "Shared via link by %2$s" : "%2$s 透過連結分享", + "Shared by %2$s" : "由 %2$s 分享", + "Shared via public link" : "透過公用連結分享", "Shares" : "分享", + "Accept" : "接受", + "Decline" : "拒絕", + "Share with me through my #ownCloud Federated Cloud ID, see %s" : "可透過我的 #ownCloud 聯合 ID,與我分享,請看 %s", + "Share with me through my #ownCloud Federated Cloud ID" : "可透過我的 #ownCloud 聯合 ID,與我分享", "This share is password-protected" : "這個分享有密碼保護", "The password is wrong. Try again." : "請檢查您的密碼並再試一次", "Password" : "密碼", @@ -36,6 +69,15 @@ OC.L10N.register( "Download" : "下載", "Download %s" : "下載 %s", "Direct link" : "直接連結", - "Federated Cloud Sharing" : "聯盟式雲端分享" + "Federated Cloud Sharing" : "聯盟式雲端分享", + "Open documentation" : "開啟文件", + "Allow users on this server to send shares to other servers" : "允許這台伺服器上的使用者發送分享給其他伺服器", + "Allow users on this server to receive shares from other servers" : "允許這台伺服器上的使用者發送接收來自其他伺服器的分享", + "Federated Cloud" : "聯盟式雲端", + "Your Federated Cloud ID:" : "您的雲端聯盟ID:", + "Share it:" : "分享它:", + "Add to your website" : "新增至您的網站", + "Share with me via ownCloud" : "透果ownCloud與我分享", + "HTML Code:" : "HTML 代碼:" }, "nplurals=1; plural=0;"); diff --git a/apps/files_sharing/l10n/zh_TW.json b/apps/files_sharing/l10n/zh_TW.json index 3567ed51df4..944014926d0 100644 --- a/apps/files_sharing/l10n/zh_TW.json +++ b/apps/files_sharing/l10n/zh_TW.json @@ -1,23 +1,56 @@ { "translations": { "Server to server sharing is not enabled on this server" : "伺服器對伺服器共享在這台伺服器上面並未啟用", + "Invalid or untrusted SSL certificate" : "無效或是不信任的SSL憑證", + "Could not authenticate to remote share, password might be wrong" : "無法驗證遠端分享,可能是密碼錯誤", + "Storage not valid" : "儲存空間無法使用", "Couldn't add remote share" : "無法加入遠端分享", "Shared with you" : "與你分享", "Shared with others" : "與其他人分享", "Shared by link" : "由連結分享", + "Nothing shared with you yet" : "目前沒有任何與您分享的內容", + "Files and folders others share with you will show up here" : "與您分享的檔案與資料夾將會顯示在這裡", + "Nothing shared yet" : "目前沒有分享內容", + "Files and folders you share will show up here" : "您分享的檔案與資料夾將會顯示在這裡", + "No shared links" : "沒有已分享的連結", + "Files and folders you share by link will show up here" : "您分享的檔案與資料夾連結將會顯示在這裡", "Do you want to add the remote share {name} from {owner}@{remote}?" : "是否要加入來自 {owner}@{remote} 的遠端分享 {name} ?", "Remote share" : "遠端分享", "Remote share password" : "遠端分享密碼", "Cancel" : "取消", "Add remote share" : "加入遠端分享", + "You can upload into this folder" : "你可以上傳內容到此資料夾", "Invalid ownCloud url" : "無效的 ownCloud URL", "Shared by" : "由...分享", "Sharing" : "分享", "A file or folder has been <strong>shared</strong>" : "檔案或目錄已被 <strong>分享</strong>", + "A file or folder was shared from <strong>another server</strong>" : "檔案或目錄已被 <strong>其他伺服器</strong> 分享", + "A public shared file or folder was <strong>downloaded</strong>" : "共享檔案或目錄已被 <strong>下載</strong>", + "You received a new remote share %2$s from %1$s" : "您收到了一個遠端分享 %2$s 來自於 %1$s", + "You received a new remote share from %s" : "您收到了一個遠端分享來自於 %s", + "%1$s accepted remote share %2$s" : "%1$s 接受了遠端分享 %2$s", + "%1$s declined remote share %2$s" : "%1$s 拒絕了遠端分享 %2$s", + "Public shared folder %1$s was downloaded" : "共享資料夾 %1$s 已被下載", + "Public shared file %1$s was downloaded" : "共享檔案 %1$s 已被下載", "You shared %1$s with %2$s" : "您與 %2$s 分享了 %1$s", "You shared %1$s with group %2$s" : "您與 %2$s 群組分享了 %1$s", + "%2$s shared %1$s with %3$s" : "%2$s 與 %3$s 分享了 %1$s", + "%2$s shared %1$s with group %3$s" : "%2$s 與群組 %3$s 分享了 %1$s", + "%2$s shared %1$s via link" : "%2$s 透過連結分享了 %1$s ", "%2$s shared %1$s with you" : "%2$s 與您分享了 %1$s", "You shared %1$s via link" : "您以連結分享了 %1$s", + "Downloaded via public link" : "透過公用連結下載", + "Shared with %2$s" : "與 %2$s 分享", + "Shared with group %2$s" : "與群組 %2$s 分享", + "Shared with %3$s by %2$s" : "透過 %2$s 與 %3$s 分享", + "Shared with group %3$s by %2$s" : "透過 %2$s 與群組 %3$s 分享", + "Shared via link by %2$s" : "%2$s 透過連結分享", + "Shared by %2$s" : "由 %2$s 分享", + "Shared via public link" : "透過公用連結分享", "Shares" : "分享", + "Accept" : "接受", + "Decline" : "拒絕", + "Share with me through my #ownCloud Federated Cloud ID, see %s" : "可透過我的 #ownCloud 聯合 ID,與我分享,請看 %s", + "Share with me through my #ownCloud Federated Cloud ID" : "可透過我的 #ownCloud 聯合 ID,與我分享", "This share is password-protected" : "這個分享有密碼保護", "The password is wrong. Try again." : "請檢查您的密碼並再試一次", "Password" : "密碼", @@ -34,6 +67,15 @@ "Download" : "下載", "Download %s" : "下載 %s", "Direct link" : "直接連結", - "Federated Cloud Sharing" : "聯盟式雲端分享" + "Federated Cloud Sharing" : "聯盟式雲端分享", + "Open documentation" : "開啟文件", + "Allow users on this server to send shares to other servers" : "允許這台伺服器上的使用者發送分享給其他伺服器", + "Allow users on this server to receive shares from other servers" : "允許這台伺服器上的使用者發送接收來自其他伺服器的分享", + "Federated Cloud" : "聯盟式雲端", + "Your Federated Cloud ID:" : "您的雲端聯盟ID:", + "Share it:" : "分享它:", + "Add to your website" : "新增至您的網站", + "Share with me via ownCloud" : "透果ownCloud與我分享", + "HTML Code:" : "HTML 代碼:" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index fa6c2d4c4f5..2e615e231f1 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -8,7 +8,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Scrutinizer Auto-Fixer <auto-fixer@scrutinizer-ci.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> @@ -32,6 +32,7 @@ namespace OC\Files\Cache; +use OC\User\NoUserException; use OCP\Share_Backend_Collection; /** @@ -64,7 +65,12 @@ class Shared_Cache extends Cache { } $source = \OC_Share_Backend_File::getSource($target, $this->storage->getShare()); if (isset($source['path']) && isset($source['fileOwner'])) { - \OC\Files\Filesystem::initMountPoints($source['fileOwner']); + try { + \OC\Files\Filesystem::initMountPoints($source['fileOwner']); + } catch(NoUserException $e) { + \OC::$server->getLogger()->logException($e, ['app' => 'files_sharing']); + return false; + } $mounts = \OC\Files\Filesystem::getMountByNumericId($source['storage']); if (is_array($mounts) and !empty($mounts)) { $fullPath = $mounts[0]->getMountPoint() . $source['path']; diff --git a/apps/files_sharing/lib/capabilities.php b/apps/files_sharing/lib/capabilities.php index c8ba1273281..220878ec2aa 100644 --- a/apps/files_sharing/lib/capabilities.php +++ b/apps/files_sharing/lib/capabilities.php @@ -1,6 +1,6 @@ <?php /** - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/lib/controllers/externalsharescontroller.php b/apps/files_sharing/lib/controllers/externalsharescontroller.php index 71cc956ec98..edf065ab476 100644 --- a/apps/files_sharing/lib/controllers/externalsharescontroller.php +++ b/apps/files_sharing/lib/controllers/externalsharescontroller.php @@ -3,7 +3,7 @@ * @author Björn Schießle <schiessle@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/lib/controllers/sharecontroller.php b/apps/files_sharing/lib/controllers/sharecontroller.php index 616b64e6c59..4b446d79ada 100644 --- a/apps/files_sharing/lib/controllers/sharecontroller.php +++ b/apps/files_sharing/lib/controllers/sharecontroller.php @@ -7,6 +7,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> + * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 @@ -333,8 +334,7 @@ class ShareController extends Controller { OC_Util::tearDownFS(); OC_Util::setupFS($rootLinkItem['uid_owner']); $path = Filesystem::getPath($linkItem['file_source']); - - if(!empty($path) && Filesystem::isReadable($path)) { + if(Filesystem::isReadable($path)) { return $path; } } diff --git a/apps/files_sharing/lib/exceptions/s2sexception.php b/apps/files_sharing/lib/exceptions/s2sexception.php index d914e9e06db..fe2659d36ff 100644 --- a/apps/files_sharing/lib/exceptions/s2sexception.php +++ b/apps/files_sharing/lib/exceptions/s2sexception.php @@ -2,7 +2,7 @@ /** * @author Björn Schießle <schiessle@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/lib/expiresharesjob.php b/apps/files_sharing/lib/expiresharesjob.php new file mode 100644 index 00000000000..bcd3fbe4605 --- /dev/null +++ b/apps/files_sharing/lib/expiresharesjob.php @@ -0,0 +1,76 @@ +<?php +/** + * @author Roeland Jago Douma <rullzer@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\Files_Sharing; + +use OC\BackgroundJob\TimedJob; + +/** + * Delete all shares that are expired + */ +class ExpireSharesJob extends TimedJob { + + /** + * sets the correct interval for this timed job + */ + public function __construct() { + // Run once a day + $this->setInterval(24 * 60 * 60); + } + + /** + * Makes the background job do its work + * + * @param array $argument unused argument + */ + public function run($argument) { + $connection = \OC::$server->getDatabaseConnection(); + $logger = \OC::$server->getLogger(); + + //Current time + $now = new \DateTime(); + $now = $now->format('Y-m-d H:i:s'); + + /* + * Expire file link shares only (for now) + */ + $qb = $connection->getQueryBuilder(); + $qb->select('id', 'file_source', 'uid_owner', 'item_type') + ->from('share') + ->where( + $qb->expr()->andX( + $qb->expr()->eq('share_type', $qb->expr()->literal(\OCP\Share::SHARE_TYPE_LINK)), + $qb->expr()->lte('expiration', $qb->expr()->literal($now)), + $qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->expr()->literal('file')), + $qb->expr()->eq('item_type', $qb->expr()->literal('folder')) + ) + ) + ); + + $shares = $qb->execute(); + while($share = $shares->fetch()) { + \OCP\Share::unshare($share['item_type'], $share['file_source'], \OCP\Share::SHARE_TYPE_LINK, null, $share['uid_owner']); + } + $shares->closeCursor(); + } + +} diff --git a/apps/files_sharing/lib/external/manager.php b/apps/files_sharing/lib/external/manager.php index 86b8904cc9a..93e2cdb540b 100644 --- a/apps/files_sharing/lib/external/manager.php +++ b/apps/files_sharing/lib/external/manager.php @@ -5,8 +5,7 @@ * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/lib/helper.php b/apps/files_sharing/lib/helper.php index b15f70fcde3..a804737c490 100644 --- a/apps/files_sharing/lib/helper.php +++ b/apps/files_sharing/lib/helper.php @@ -7,7 +7,7 @@ * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. @@ -28,6 +28,8 @@ */ namespace OCA\Files_Sharing; +use OCP\Files\NotFoundException; + class Helper { public static function registerHooks() { @@ -48,6 +50,7 @@ class Helper { * @param string $token string share token * @param string $relativePath optional path relative to the share * @param string $password optional password + * @return array */ public static function setupFromToken($token, $relativePath = null, $password = null) { \OC_User::setIncognitoMode(true); @@ -71,10 +74,11 @@ class Helper { \OCP\JSON::checkUserExists($rootLinkItem['uid_owner']); \OC_Util::tearDownFS(); \OC_Util::setupFS($rootLinkItem['uid_owner']); - $path = \OC\Files\Filesystem::getPath($linkItem['file_source']); } - if ($path === null) { + try { + $path = \OC\Files\Filesystem::getPath($linkItem['file_source']); + } catch (NotFoundException $e) { \OCP\Util::writeLog('share', 'could not resolve linkItem', \OCP\Util::DEBUG); \OC_Response::setStatus(404); \OCP\JSON::error(array('success' => false)); diff --git a/apps/files_sharing/lib/middleware/sharingcheckmiddleware.php b/apps/files_sharing/lib/middleware/sharingcheckmiddleware.php index 9170c08b59d..22b9d32a275 100644 --- a/apps/files_sharing/lib/middleware/sharingcheckmiddleware.php +++ b/apps/files_sharing/lib/middleware/sharingcheckmiddleware.php @@ -2,7 +2,7 @@ /** * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_sharing/lib/propagation/propagationmanager.php b/apps/files_sharing/lib/propagation/propagationmanager.php index 6ed70e93f84..aac9428240d 100644 --- a/apps/files_sharing/lib/propagation/propagationmanager.php +++ b/apps/files_sharing/lib/propagation/propagationmanager.php @@ -1,6 +1,7 @@ <?php /** * @author Robin Appelman <icewind@owncloud.com> + * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/lib/propagation/recipientpropagator.php b/apps/files_sharing/lib/propagation/recipientpropagator.php index 8e064e2ee54..5eacf4c0f6e 100644 --- a/apps/files_sharing/lib/propagation/recipientpropagator.php +++ b/apps/files_sharing/lib/propagation/recipientpropagator.php @@ -1,5 +1,6 @@ <?php /** + * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> * @@ -25,6 +26,7 @@ namespace OCA\Files_Sharing\Propagation; use OC\Files\Cache\ChangePropagator; use OC\Files\View; use OC\Share\Share; +use OCP\Files\NotFoundException; /** * Propagate etags for share recipients @@ -128,6 +130,9 @@ class RecipientPropagator { protected $propagatingIds = []; + /** + * @param int $id + */ public function propagateById($id) { if (isset($this->propagatingIds[$id])) { return; @@ -142,7 +147,13 @@ class RecipientPropagator { if ($share['share_with'] === $this->userId) { $user = $share['uid_owner']; $view = new View('/' . $user . '/files'); - $path = $view->getPath($share['file_source']); + + try { + $path = $view->getPath($share['file_source']); + } catch (NotFoundException $e) { + $path = null; + } + $watcher = new ChangeWatcher($view, $this->manager->getSharePropagator($user)); $watcher->writeHook(['path' => $path]); } diff --git a/apps/files_sharing/lib/share/file.php b/apps/files_sharing/lib/share/file.php index 6c676f47a0c..ffc417db2f4 100644 --- a/apps/files_sharing/lib/share/file.php +++ b/apps/files_sharing/lib/share/file.php @@ -3,10 +3,11 @@ * @author Andreas Fischer <bantu@owncloud.com> * @author Bart Visscher <bartv@thisnet.nl> * @author Björn Schießle <schiessle@owncloud.com> + * @author Lukas Reschke <lukas@owncloud.com> * @author Michael Gapczynski <GapczynskiM@gmail.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * @@ -40,15 +41,16 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { private $path; public function isValidSource($itemSource, $uidOwner) { - $path = \OC\Files\Filesystem::getPath($itemSource); - if ($path) { + try { + $path = \OC\Files\Filesystem::getPath($itemSource); // FIXME: attributes should not be set here, // keeping this pattern for now to avoid unexpected // regressions $this->path = \OC\Files\Filesystem::normalizePath(basename($path)); return true; + } catch (\OCP\Files\NotFoundException $e) { + return false; } - return false; } public function getFilePath($itemSource, $uidOwner) { @@ -57,12 +59,13 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { $this->path = null; return $path; } else { - $path = \OC\Files\Filesystem::getPath($itemSource); - if ($path) { + try { + $path = \OC\Files\Filesystem::getPath($itemSource); return $path; + } catch (\OCP\Files\NotFoundException $e) { + return false; } } - return false; } /** diff --git a/apps/files_sharing/lib/share/folder.php b/apps/files_sharing/lib/share/folder.php index daa5b5e9eb8..5e7cd8099db 100644 --- a/apps/files_sharing/lib/share/folder.php +++ b/apps/files_sharing/lib/share/folder.php @@ -5,7 +5,7 @@ * @author Michael Gapczynski <GapczynskiM@gmail.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/lib/sharedmount.php b/apps/files_sharing/lib/sharedmount.php index 85eb264ce09..a1387957867 100644 --- a/apps/files_sharing/lib/sharedmount.php +++ b/apps/files_sharing/lib/sharedmount.php @@ -4,7 +4,7 @@ * @author Joas Schilling <nickvergessen@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index b0e56f5f054..18e02844179 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -7,7 +7,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author scambra <sergio@entrecables.com> * @author Vincent Petry <pvince81@owncloud.com> * diff --git a/apps/files_sharing/lib/updater.php b/apps/files_sharing/lib/updater.php index d70ed23b941..26044cc1c8e 100644 --- a/apps/files_sharing/lib/updater.php +++ b/apps/files_sharing/lib/updater.php @@ -5,7 +5,7 @@ * @author Michael Gapczynski <GapczynskiM@gmail.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_sharing/templates/list.php b/apps/files_sharing/templates/list.php index 55ad55a0a4f..fa0365c749c 100644 --- a/apps/files_sharing/templates/list.php +++ b/apps/files_sharing/templates/list.php @@ -1,7 +1,4 @@ <?php /** @var $l OC_L10N */ ?> -<div id="controls"> - <div id="file_action_panel"></div> -</div> <div id='notification'></div> <div id="emptycontent" class="hidden"></div> diff --git a/apps/files_sharing/templates/settings-admin.php b/apps/files_sharing/templates/settings-admin.php index 376f2b71aee..31bfa78e67d 100644 --- a/apps/files_sharing/templates/settings-admin.php +++ b/apps/files_sharing/templates/settings-admin.php @@ -9,7 +9,7 @@ href="<?php p(link_to_docs('admin-sharing-federated')); ?>"></a> <p> - <input type="checkbox" name="outgoing_server2server_share_enabled" id="outgoingServer2serverShareEnabled" + <input type="checkbox" name="outgoing_server2server_share_enabled" id="outgoingServer2serverShareEnabled" class="checkbox" value="1" <?php if ($_['outgoingServer2serverShareEnabled']) print_unescaped('checked="checked"'); ?> /> <label for="outgoingServer2serverShareEnabled"> <?php p($l->t('Allow users on this server to send shares to other servers'));?> @@ -17,7 +17,7 @@ </p> <p> - <input type="checkbox" name="incoming_server2server_share_enabled" id="incomingServer2serverShareEnabled" + <input type="checkbox" name="incoming_server2server_share_enabled" id="incomingServer2serverShareEnabled" class="checkbox" value="1" <?php if ($_['incomingServer2serverShareEnabled']) print_unescaped('checked="checked"'); ?> /> <label for="incomingServer2serverShareEnabled"> <?php p($l->t('Allow users on this server to receive shares from other servers'));?> diff --git a/apps/files_sharing/tests/api.php b/apps/files_sharing/tests/api.php index 3809b812051..760bc0591e5 100644 --- a/apps/files_sharing/tests/api.php +++ b/apps/files_sharing/tests/api.php @@ -5,7 +5,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * diff --git a/apps/files_sharing/tests/api/shareestest.php b/apps/files_sharing/tests/api/shareestest.php index 923881d4569..8a35350aeb5 100644 --- a/apps/files_sharing/tests/api/shareestest.php +++ b/apps/files_sharing/tests/api/shareestest.php @@ -1,7 +1,7 @@ <?php /** * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/tests/capabilities.php b/apps/files_sharing/tests/capabilities.php index b151f47a468..8bebde9f2d1 100644 --- a/apps/files_sharing/tests/capabilities.php +++ b/apps/files_sharing/tests/capabilities.php @@ -1,7 +1,7 @@ <?php /** * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/tests/controller/externalsharecontroller.php b/apps/files_sharing/tests/controller/externalsharecontroller.php index 7bc11f7fb94..4913c7308ba 100644 --- a/apps/files_sharing/tests/controller/externalsharecontroller.php +++ b/apps/files_sharing/tests/controller/externalsharecontroller.php @@ -1,7 +1,7 @@ <?php /** * @author Lukas Reschke <lukas@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/tests/controller/sharecontroller.php b/apps/files_sharing/tests/controller/sharecontroller.php index 0b56aafc8a9..db5eb75d761 100644 --- a/apps/files_sharing/tests/controller/sharecontroller.php +++ b/apps/files_sharing/tests/controller/sharecontroller.php @@ -199,8 +199,7 @@ class ShareControllerTest extends \Test\TestCase { } /** - * @expectedException \Exception - * @expectedExceptionMessage No file found belonging to file. + * @expectedException \OCP\Files\NotFoundException */ public function testShowShareWithDeletedFile() { $this->container['UserManager']->expects($this->once()) @@ -216,8 +215,7 @@ class ShareControllerTest extends \Test\TestCase { } /** - * @expectedException \Exception - * @expectedExceptionMessage No file found belonging to file. + * @expectedException \OCP\Files\NotFoundException */ public function testDownloadShareWithDeletedFile() { $this->container['UserManager']->expects($this->once()) diff --git a/apps/files_sharing/tests/etagpropagation.php b/apps/files_sharing/tests/etagpropagation.php index 2490fef03e1..1abf04df84f 100644 --- a/apps/files_sharing/tests/etagpropagation.php +++ b/apps/files_sharing/tests/etagpropagation.php @@ -1,5 +1,7 @@ <?php /** + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lukas Reschke <lukas@owncloud.com> * @author Robin Appelman <icewind@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> * @@ -173,7 +175,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsChanged($users, 'sub1/sub2'); } - private function assertAllUnchaged() { + private function assertAllUnchanged() { $users = [self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]; $this->assertEtagsNotChanged($users); @@ -186,7 +188,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testOwnerWritesToSingleFileShare() { @@ -195,7 +197,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsNotChanged([self::TEST_FILES_SHARING_API_USER4, self::TEST_FILES_SHARING_API_USER3]); $this->assertEtagsChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testOwnerWritesToShareWithReshare() { @@ -204,7 +206,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testOwnerRenameInShare() { @@ -214,7 +216,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testOwnerRenameInReShare() { @@ -223,7 +225,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testOwnerRenameIntoReShare() { @@ -232,7 +234,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testOwnerRenameOutOfReShare() { @@ -241,7 +243,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testOwnerDeleteInShare() { @@ -251,7 +253,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testOwnerDeleteInReShare() { @@ -260,7 +262,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testOwnerUnshares() { @@ -283,7 +285,7 @@ class EtagPropagation extends TestCase { self::TEST_FILES_SHARING_API_USER4, ]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testRecipientUnsharesFromSelf() { @@ -298,7 +300,7 @@ class EtagPropagation extends TestCase { self::TEST_FILES_SHARING_API_USER4, ]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testRecipientWritesToShare() { @@ -308,7 +310,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testRecipientWritesToReshare() { @@ -317,7 +319,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testRecipientWritesToOtherRecipientsReshare() { @@ -326,7 +328,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testRecipientRenameInShare() { @@ -336,7 +338,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testRecipientRenameInReShare() { @@ -345,7 +347,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testRecipientRenameResharedFolder() { @@ -356,7 +358,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsChanged([self::TEST_FILES_SHARING_API_USER2], 'sub1'); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testRecipientDeleteInShare() { @@ -366,7 +368,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testRecipientDeleteInReShare() { @@ -375,7 +377,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testReshareRecipientWritesToReshare() { @@ -384,7 +386,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testReshareRecipientRenameInReShare() { @@ -393,7 +395,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testReshareRecipientDeleteInReShare() { @@ -402,7 +404,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testRecipientUploadInDirectReshare() { @@ -411,7 +413,7 @@ class EtagPropagation extends TestCase { $this->assertEtagsNotChanged([self::TEST_FILES_SHARING_API_USER3]); $this->assertEtagsChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } public function testEtagChangeOnPermissionsChange() { @@ -424,6 +426,6 @@ class EtagPropagation extends TestCase { $this->assertEtagsForFoldersChanged([self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER4]); - $this->assertAllUnchaged(); + $this->assertAllUnchanged(); } } diff --git a/apps/files_sharing/tests/expiresharesjobtest.php b/apps/files_sharing/tests/expiresharesjobtest.php new file mode 100644 index 00000000000..63a2c46f647 --- /dev/null +++ b/apps/files_sharing/tests/expiresharesjobtest.php @@ -0,0 +1,204 @@ +<?php +/** + * @author Roeland Jago Douma <rullzer@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\Files_Sharing\Tests; + +use OCA\Files_Sharing\ExpireSharesJob; + +class ExpireSharesJobTest extends \Test\TestCase { + + /** + * @var ExpireSharesJob + */ + private $job; + + /** + * @var \OCP\IDBConnection + */ + private $connection; + + /** + * @var string + */ + private $user1; + + /** + * @var string + */ + private $user2; + + protected function setup() { + parent::setUp(); + + $this->connection = \OC::$server->getDatabaseConnection(); + // clear occasional leftover shares from other tests + $this->connection->executeUpdate('DELETE FROM `*PREFIX*share`'); + + $this->user1 = $this->getUniqueID('user1_'); + $this->user2 = $this->getUniqueID('user2_'); + + $userManager = \OC::$server->getUserManager(); + $userManager->createUser($this->user1, 'pass'); + $userManager->createUser($this->user2, 'pass'); + + \OC::registerShareHooks(); + + $this->job = new ExpireSharesJob(); + } + + protected function tearDown() { + $this->connection->executeUpdate('DELETE FROM `*PREFIX*share`'); + + $userManager = \OC::$server->getUserManager(); + $user1 = $userManager->get($this->user1); + if($user1) { + $user1->delete(); + } + $user2 = $userManager->get($this->user2); + if($user2) { + $user2->delete(); + } + + $this->logout(); + + parent::tearDown(); + } + + private function getShares() { + $shares = []; + $qb = $this->connection->getQueryBuilder(); + + $result = $qb->select('*') + ->from('share') + ->execute(); + + while ($row = $result->fetch()) { + $shares[] = $row; + } + $result->closeCursor(); + return $shares; + } + + public function dataExpireLinkShare() { + return [ + [false, '', false, false], + [false, '', true, false], + [true, 'P1D', false, true], + [true, 'P1D', true, false], + [true, 'P1W', false, true], + [true, 'P1W', true, false], + [true, 'P1M', false, true], + [true, 'P1M', true, false], + [true, 'P1Y', false, true], + [true, 'P1Y', true, false], + ]; + } + + /** + * @dataProvider dataExpireLinkShare + * + * @param bool addExpiration Should we add an expire date + * @param string $interval The dateInterval + * @param bool $addInterval If true add to the current time if false subtract + * @param bool $shouldExpire Should this share be expired + */ + public function testExpireLinkShare($addExpiration, $interval, $addInterval, $shouldExpire) { + $this->loginAsUser($this->user1); + + $view = new \OC\Files\View('/' . $this->user1 . '/'); + $view->mkdir('files/test'); + + $fileInfo = $view->getFileInfo('files/test'); + + $this->assertNotNull( + \OCP\Share::shareItem('folder', $fileInfo->getId(), \OCP\Share::SHARE_TYPE_LINK, null, \OCP\Constants::PERMISSION_READ), + 'Failed asserting that user 1 successfully shared "test" by link.' + ); + + $shares = $this->getShares(); + $this->assertCount(1, $shares); + reset($shares); + $share = current($shares); + + if ($addExpiration) { + $expire = new \DateTime(); + $expire->setTime(0, 0, 0); + if ($addInterval) { + $expire->add(new \DateInterval($interval)); + } else { + $expire->sub(new \DateInterval($interval)); + } + $expire = $expire->format('Y-m-d 00:00:00'); + + // Set expiration date to yesterday + $qb = $this->connection->getQueryBuilder(); + $qb->update('share') + ->set('expiration', $qb->createParameter('expiration')) + ->where($qb->expr()->eq('id', $qb->createParameter('id'))) + ->setParameter('id', $share['id']) + ->setParameter('expiration', $expire) + ->execute(); + + $shares = $this->getShares(); + $this->assertCount(1, $shares); + } + + $this->logout(); + + $this->job->run([]); + + $shares = $this->getShares(); + + if ($shouldExpire) { + $this->assertCount(0, $shares); + } else { + $this->assertCount(1, $shares); + } + } + + public function testDoNotExpireOtherShares() { + $this->loginAsUser($this->user1); + + $view = new \OC\Files\View('/' . $this->user1 . '/'); + $view->mkdir('files/test'); + + $fileInfo = $view->getFileInfo('files/test'); + + $this->assertNotNull( + \OCP\Share::shareItem('folder', $fileInfo->getId(), \OCP\Share::SHARE_TYPE_USER, $this->user2, \OCP\Constants::PERMISSION_READ), + 'Failed asserting that user 1 successfully shared "test" by link with user2.' + ); + + $shares = $this->getShares(); + $this->assertCount(1, $shares); + reset($shares); + $share = current($shares); + + $this->logout(); + + $this->job->run([]); + + $shares = $this->getShares(); + $this->assertCount(1, $shares); + } + +} + diff --git a/apps/files_sharing/tests/external/managertest.php b/apps/files_sharing/tests/external/managertest.php index 7242779d455..5b93b7494e9 100644 --- a/apps/files_sharing/tests/external/managertest.php +++ b/apps/files_sharing/tests/external/managertest.php @@ -1,6 +1,7 @@ <?php /** * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Robin Appelman <icewind@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_sharing/tests/grouppropagationmanager.php b/apps/files_sharing/tests/grouppropagationmanager.php index 6fc6ef7a532..ea32ca4f7ec 100644 --- a/apps/files_sharing/tests/grouppropagationmanager.php +++ b/apps/files_sharing/tests/grouppropagationmanager.php @@ -1,7 +1,5 @@ <?php /** - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <icewind@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_sharing/tests/middleware/sharingcheckmiddleware.php b/apps/files_sharing/tests/middleware/sharingcheckmiddleware.php index 0269f77d0d5..031f8c1b970 100644 --- a/apps/files_sharing/tests/middleware/sharingcheckmiddleware.php +++ b/apps/files_sharing/tests/middleware/sharingcheckmiddleware.php @@ -2,7 +2,7 @@ /** * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_sharing/tests/sharedmount.php b/apps/files_sharing/tests/sharedmount.php index 6f487892b8f..94c0ad448bc 100644 --- a/apps/files_sharing/tests/sharedmount.php +++ b/apps/files_sharing/tests/sharedmount.php @@ -4,7 +4,7 @@ * @author Joas Schilling <nickvergessen@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * diff --git a/apps/files_sharing/tests/sharedstorage.php b/apps/files_sharing/tests/sharedstorage.php index de510cf1eec..3361d2cbd12 100644 --- a/apps/files_sharing/tests/sharedstorage.php +++ b/apps/files_sharing/tests/sharedstorage.php @@ -47,8 +47,10 @@ class Test_Files_Sharing_Storage extends OCA\Files_sharing\Tests\TestCase { } protected function tearDown() { - $this->view->unlink($this->folder); - $this->view->unlink($this->filename); + if ($this->view) { + $this->view->unlink($this->folder); + $this->view->unlink($this->filename); + } \OC\Files\Filesystem::getLoader()->removeStorageWrapper('oc_trashbin'); @@ -85,8 +87,9 @@ class Test_Files_Sharing_Storage extends OCA\Files_sharing\Tests\TestCase { $this->assertFalse($user2View->is_dir($this->folder)); // delete the local folder - $fullPath = \OC_Config::getValue('datadirectory') . '/' . self::TEST_FILES_SHARING_API_USER2 . '/files/localfolder'; - rmdir($fullPath); + /** @var \OC\Files\Storage\Storage $storage */ + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath('/' . self::TEST_FILES_SHARING_API_USER2 . '/files/localfolder'); + $storage->rmdir($internalPath); //enforce reload of the mount points self::loginHelper(self::TEST_FILES_SHARING_API_USER2); diff --git a/apps/files_sharing/tests/sizepropagation.php b/apps/files_sharing/tests/sizepropagation.php index c596003de76..1d09f69449f 100644 --- a/apps/files_sharing/tests/sizepropagation.php +++ b/apps/files_sharing/tests/sizepropagation.php @@ -1,5 +1,6 @@ <?php /** + * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Robin Appelman <icewind@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_sharing/tests/watcher.php b/apps/files_sharing/tests/watcher.php index 488792db4ef..5e96a3fe68e 100644 --- a/apps/files_sharing/tests/watcher.php +++ b/apps/files_sharing/tests/watcher.php @@ -108,9 +108,8 @@ class Test_Files_Sharing_Watcher extends OCA\Files_sharing\Tests\TestCase { $this->sharedCache->put('', array('mtime' => 10, 'storage_mtime' => 10, 'size' => '-1', 'mimetype' => 'httpd/unix-directory')); // run the propagation code - $result = $this->sharedStorage->getWatcher()->checkUpdate(''); - - $this->assertTrue($result); + $this->sharedStorage->getWatcher()->checkUpdate(''); + $this->sharedStorage->getCache()->correctFolderSize(''); // the owner's parent dirs must have increase size $newSizes = self::getOwnerDirSizes('files/container/shareddir'); @@ -139,9 +138,8 @@ class Test_Files_Sharing_Watcher extends OCA\Files_sharing\Tests\TestCase { $this->sharedCache->put('subdir', array('mtime' => 10, 'storage_mtime' => 10, 'size' => $dataLen, 'mimetype' => 'text/plain')); // run the propagation code - $result = $this->sharedStorage->getWatcher()->checkUpdate('subdir'); - - $this->assertTrue($result); + $this->sharedStorage->getWatcher()->checkUpdate('subdir'); + $this->sharedStorage->getCache()->correctFolderSize('subdir'); // the owner's parent dirs must have increase size $newSizes = self::getOwnerDirSizes('files/container/shareddir/subdir'); diff --git a/apps/files_trashbin/ajax/delete.php b/apps/files_trashbin/ajax/delete.php index 7ce8d632e4b..40d1811717c 100644 --- a/apps/files_trashbin/ajax/delete.php +++ b/apps/files_trashbin/ajax/delete.php @@ -4,7 +4,7 @@ * @author Björn Schießle <schiessle@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Robin Appelman <icewind@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_trashbin/ajax/preview.php b/apps/files_trashbin/ajax/preview.php index 90add53f77f..49d6d93f574 100644 --- a/apps/files_trashbin/ajax/preview.php +++ b/apps/files_trashbin/ajax/preview.php @@ -3,7 +3,7 @@ * @author Björn Schießle <schiessle@owncloud.com> * @author Georg Ehrke <georg@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_trashbin/ajax/undelete.php b/apps/files_trashbin/ajax/undelete.php index 01dc91f9bac..f0e5ce6d889 100644 --- a/apps/files_trashbin/ajax/undelete.php +++ b/apps/files_trashbin/ajax/undelete.php @@ -4,7 +4,7 @@ * @author Björn Schießle <schiessle@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Robin Appelman <icewind@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * diff --git a/apps/files_trashbin/appinfo/application.php b/apps/files_trashbin/appinfo/application.php index 08ab7cd5c1d..59553abfb14 100644 --- a/apps/files_trashbin/appinfo/application.php +++ b/apps/files_trashbin/appinfo/application.php @@ -1,6 +1,6 @@ <?php /** - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Victor Dubiniuk <dubiniuk@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_trashbin/appinfo/info.xml b/apps/files_trashbin/appinfo/info.xml index f8bc5e7d3d2..c4bade081b3 100644 --- a/apps/files_trashbin/appinfo/info.xml +++ b/apps/files_trashbin/appinfo/info.xml @@ -10,11 +10,14 @@ To prevent a user from running out of disk space, the ownCloud Deleted files app <licence>AGPL</licence> <author>Bjoern Schiessle</author> <shipped>true</shipped> - <requiremin>4.9</requiremin> <default_enable/> + <version>0.8.0</version> <types> <filesystem/> </types> + <dependencies> + <owncloud min-version="9.0" /> + </dependencies> <documentation> <user>user-trashbin</user> </documentation> diff --git a/apps/files_trashbin/appinfo/routes.php b/apps/files_trashbin/appinfo/routes.php index caa9f0864a1..f3b97d8687c 100644 --- a/apps/files_trashbin/appinfo/routes.php +++ b/apps/files_trashbin/appinfo/routes.php @@ -1,7 +1,7 @@ <?php /** * @author Lukas Reschke <lukas@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_trashbin/appinfo/version b/apps/files_trashbin/appinfo/version deleted file mode 100644 index 844f6a91acb..00000000000 --- a/apps/files_trashbin/appinfo/version +++ /dev/null @@ -1 +0,0 @@ -0.6.3 diff --git a/apps/files_trashbin/css/trash.css b/apps/files_trashbin/css/trash.css index 15a9249d7b2..053e2e7002c 100644 --- a/apps/files_trashbin/css/trash.css +++ b/apps/files_trashbin/css/trash.css @@ -18,3 +18,21 @@ #app-content-trashbin #filestable .summary .filesize { display: none; } + +#app-navigation > ul { + padding-bottom: 44px; +} + +/* move Deleted Files to bottom of sidebar */ +.nav-trashbin { + position: fixed !important; + bottom: 44px; + width: inherit !important; + background-color: #fff; + border-right: 1px solid #eee; +} +/* double padding to account for Deleted files entry, issue with Firefox */ +.app-files #app-navigation > ul li:nth-last-child(2) { + margin-bottom: 44px; +} + diff --git a/apps/files_trashbin/js/app.js b/apps/files_trashbin/js/app.js index c3be762f095..364b51697bd 100644 --- a/apps/files_trashbin/js/app.js +++ b/apps/files_trashbin/js/app.js @@ -26,7 +26,8 @@ OCA.Trashbin.App = { this.fileList = new OCA.Trashbin.FileList( $('#app-content-trashbin'), { scrollContainer: $('#app-content'), - fileActions: this._createFileActions() + fileActions: this._createFileActions(), + detailsViewEnabled: false } ); }, @@ -43,19 +44,27 @@ OCA.Trashbin.App = { fileActions.setDefault('dir', 'Open'); - fileActions.register('all', 'Restore', OC.PERMISSION_READ, OC.imagePath('core', 'actions/history'), function(filename, context) { - var fileList = context.fileList; - var tr = fileList.findFileEl(filename); - var deleteAction = tr.children("td.date").children(".action.delete"); - deleteAction.removeClass('icon-delete').addClass('icon-loading-small'); - fileList.disableActions(); - $.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), { - files: JSON.stringify([filename]), - dir: fileList.getCurrentDirectory() - }, - _.bind(fileList._removeCallback, fileList) - ); - }, t('files_trashbin', 'Restore')); + fileActions.registerAction({ + name: 'Restore', + displayName: t('files_trashbin', 'Restore'), + type: OCA.Files.FileActions.TYPE_INLINE, + mime: 'all', + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/history'), + actionHandler: function(filename, context) { + var fileList = context.fileList; + var tr = fileList.findFileEl(filename); + var deleteAction = tr.children("td.date").children(".action.delete"); + deleteAction.removeClass('icon-delete').addClass('icon-loading-small'); + fileList.disableActions(); + $.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), { + files: JSON.stringify([filename]), + dir: fileList.getCurrentDirectory() + }, + _.bind(fileList._removeCallback, fileList) + ); + } + }); fileActions.registerAction({ name: 'Delete', diff --git a/apps/files_trashbin/l10n/sq.js b/apps/files_trashbin/l10n/sq.js index be6182a94dd..3a1d0fed3bb 100644 --- a/apps/files_trashbin/l10n/sq.js +++ b/apps/files_trashbin/l10n/sq.js @@ -1,15 +1,19 @@ OC.L10N.register( "files_trashbin", { - "Couldn't delete %s permanently" : "Nuk munda ta eliminoj përfundimisht %s", - "Couldn't restore %s" : "Nuk munda ta rivendos %s", - "Deleted files" : "Skedarë të fshirë ", - "Restore" : "Rivendos", - "Delete permanently" : "Fshi përfundimisht", - "Error" : "Veprim i gabuar", - "restored" : "rivendosur", - "Name" : "Emri", - "Deleted" : "Eliminuar", - "Delete" : "Elimino" + "Couldn't delete %s permanently" : "S’u fshi dot përgjithmonë %s", + "Couldn't restore %s" : "S’u rikthye dot %s", + "Deleted files" : "Kartela të fshira", + "Restore" : "Riktheje", + "Delete" : "Fshije", + "Delete permanently" : "Fshije përgjithmonë", + "Error" : "Gabim", + "restored" : "u rikthye", + "No deleted files" : "Pa kartela të fshira", + "You will be able to recover deleted files from here" : "Që këtu do të jeni në gjendje të rimerrni kartela të fshira", + "No entries found in this folder" : "Në këtë dosje s’u gjetën zëra", + "Select all" : "Përzgjidhi krejt", + "Name" : "Emër", + "Deleted" : "U fshi" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_trashbin/l10n/sq.json b/apps/files_trashbin/l10n/sq.json index 735fd8c86b1..4f2c429cb75 100644 --- a/apps/files_trashbin/l10n/sq.json +++ b/apps/files_trashbin/l10n/sq.json @@ -1,13 +1,17 @@ { "translations": { - "Couldn't delete %s permanently" : "Nuk munda ta eliminoj përfundimisht %s", - "Couldn't restore %s" : "Nuk munda ta rivendos %s", - "Deleted files" : "Skedarë të fshirë ", - "Restore" : "Rivendos", - "Delete permanently" : "Fshi përfundimisht", - "Error" : "Veprim i gabuar", - "restored" : "rivendosur", - "Name" : "Emri", - "Deleted" : "Eliminuar", - "Delete" : "Elimino" + "Couldn't delete %s permanently" : "S’u fshi dot përgjithmonë %s", + "Couldn't restore %s" : "S’u rikthye dot %s", + "Deleted files" : "Kartela të fshira", + "Restore" : "Riktheje", + "Delete" : "Fshije", + "Delete permanently" : "Fshije përgjithmonë", + "Error" : "Gabim", + "restored" : "u rikthye", + "No deleted files" : "Pa kartela të fshira", + "You will be able to recover deleted files from here" : "Që këtu do të jeni në gjendje të rimerrni kartela të fshira", + "No entries found in this folder" : "Në këtë dosje s’u gjetën zëra", + "Select all" : "Përzgjidhi krejt", + "Name" : "Emër", + "Deleted" : "U fshi" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_trashbin/l10n/sv.js b/apps/files_trashbin/l10n/sv.js index 5a432945260..d21f1418073 100644 --- a/apps/files_trashbin/l10n/sv.js +++ b/apps/files_trashbin/l10n/sv.js @@ -5,14 +5,15 @@ OC.L10N.register( "Couldn't restore %s" : "Kunde inte återställa %s", "Deleted files" : "Raderade filer", "Restore" : "Återskapa", + "Delete" : "Radera", "Delete permanently" : "Radera permanent", "Error" : "Fel", "restored" : "återställd", "No deleted files" : "Inga borttagna filer", "You will be able to recover deleted files from here" : "Du kommer kunna återfå raderade filer härifrån", + "No entries found in this folder" : "nga Filer hittades i denna mapp", "Select all" : "Välj allt", "Name" : "Namn", - "Deleted" : "Raderad", - "Delete" : "Radera" + "Deleted" : "Raderad" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_trashbin/l10n/sv.json b/apps/files_trashbin/l10n/sv.json index f1fd0cdf5de..0b9052ea713 100644 --- a/apps/files_trashbin/l10n/sv.json +++ b/apps/files_trashbin/l10n/sv.json @@ -3,14 +3,15 @@ "Couldn't restore %s" : "Kunde inte återställa %s", "Deleted files" : "Raderade filer", "Restore" : "Återskapa", + "Delete" : "Radera", "Delete permanently" : "Radera permanent", "Error" : "Fel", "restored" : "återställd", "No deleted files" : "Inga borttagna filer", "You will be able to recover deleted files from here" : "Du kommer kunna återfå raderade filer härifrån", + "No entries found in this folder" : "nga Filer hittades i denna mapp", "Select all" : "Välj allt", "Name" : "Namn", - "Deleted" : "Raderad", - "Delete" : "Radera" + "Deleted" : "Raderad" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_trashbin/l10n/zh_TW.js b/apps/files_trashbin/l10n/zh_TW.js index 85d0873eddf..e0ad6dbd723 100644 --- a/apps/files_trashbin/l10n/zh_TW.js +++ b/apps/files_trashbin/l10n/zh_TW.js @@ -5,13 +5,15 @@ OC.L10N.register( "Couldn't restore %s" : "無法還原 %s", "Deleted files" : "回收桶", "Restore" : "還原", + "Delete" : "刪除", "Delete permanently" : "永久刪除", "Error" : "錯誤", "restored" : "已還原", + "No deleted files" : "沒有已刪除的檔案", + "You will be able to recover deleted files from here" : "您可以從這裡還原已刪除的檔案", "No entries found in this folder" : "在此資料夾中沒有任何項目", "Select all" : "全選", "Name" : "名稱", - "Deleted" : "已刪除", - "Delete" : "刪除" + "Deleted" : "已刪除" }, "nplurals=1; plural=0;"); diff --git a/apps/files_trashbin/l10n/zh_TW.json b/apps/files_trashbin/l10n/zh_TW.json index 5c744f828c9..6a313220b58 100644 --- a/apps/files_trashbin/l10n/zh_TW.json +++ b/apps/files_trashbin/l10n/zh_TW.json @@ -3,13 +3,15 @@ "Couldn't restore %s" : "無法還原 %s", "Deleted files" : "回收桶", "Restore" : "還原", + "Delete" : "刪除", "Delete permanently" : "永久刪除", "Error" : "錯誤", "restored" : "已還原", + "No deleted files" : "沒有已刪除的檔案", + "You will be able to recover deleted files from here" : "您可以從這裡還原已刪除的檔案", "No entries found in this folder" : "在此資料夾中沒有任何項目", "Select all" : "全選", "Name" : "名稱", - "Deleted" : "已刪除", - "Delete" : "刪除" + "Deleted" : "已刪除" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/apps/files_trashbin/lib/capabilities.php b/apps/files_trashbin/lib/capabilities.php index c991cc8be65..d903066e676 100644 --- a/apps/files_trashbin/lib/capabilities.php +++ b/apps/files_trashbin/lib/capabilities.php @@ -2,7 +2,7 @@ /** * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_trashbin/lib/helper.php b/apps/files_trashbin/lib/helper.php index 3d6a02c7776..d14e97285c5 100644 --- a/apps/files_trashbin/lib/helper.php +++ b/apps/files_trashbin/lib/helper.php @@ -5,7 +5,7 @@ * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Robin Appelman <icewind@owncloud.com> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_trashbin/lib/trashbin.php b/apps/files_trashbin/lib/trashbin.php index ef015c3566a..8f0fe745a45 100644 --- a/apps/files_trashbin/lib/trashbin.php +++ b/apps/files_trashbin/lib/trashbin.php @@ -12,7 +12,7 @@ * @author Qingping Hou <dave2008713@gmail.com> * @author Robin Appelman <icewind@owncloud.com> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Sjors van der Pluijm <sjors@desjors.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Victor Dubiniuk <dubiniuk@owncloud.com> @@ -41,6 +41,7 @@ use OC\Files\Filesystem; use OC\Files\View; use OCA\Files_Trashbin\AppInfo\Application; use OCA\Files_Trashbin\Command\Expire; +use OCP\Files\NotFoundException; class Trashbin { @@ -64,15 +65,24 @@ class Trashbin { self::getUidAndFilename($params['path']); } + /** + * @param string $filename + * @return array + * @throws \OC\User\NoUserException + */ public static function getUidAndFilename($filename) { $uid = \OC\Files\Filesystem::getOwner($filename); \OC\Files\Filesystem::initMountPoints($uid); if ($uid != \OCP\User::getUser()) { $info = \OC\Files\Filesystem::getFileInfo($filename); $ownerView = new \OC\Files\View('/' . $uid . '/files'); - $filename = $ownerView->getPath($info['fileid']); + try { + $filename = $ownerView->getPath($info['fileid']); + } catch (NotFoundException $e) { + $filename = null; + } } - return array($uid, $filename); + return [$uid, $filename]; } /** @@ -581,8 +591,9 @@ class Trashbin { if ($quota === null || $quota === 'none') { $quota = \OC\Files\Filesystem::free_space('/'); $softQuota = false; - if ($quota === \OCP\Files\FileInfo::SPACE_UNKNOWN) { - $quota = 0; + // inf or unknown free space + if ($quota < 0) { + $quota = PHP_INT_MAX; } } else { $quota = \OCP\Util::computerFileSize($quota); diff --git a/apps/files_trashbin/tests/trashbin.php b/apps/files_trashbin/tests/trashbin.php index 757239b8cc4..e28b854ca1f 100644 --- a/apps/files_trashbin/tests/trashbin.php +++ b/apps/files_trashbin/tests/trashbin.php @@ -236,6 +236,8 @@ class Test_Trashbin extends \Test\TestCase { // user2-1.txt should have been expired $this->verifyArray($filesInTrashUser2AfterDelete, array('user2-2.txt', 'user1-4.txt')); + self::loginHelper(self::TEST_TRASHBIN_USER1); + // user1-1.txt and user1-3.txt should have been expired $filesInTrashUser1AfterDelete = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1); @@ -600,22 +602,24 @@ class Test_Trashbin extends \Test\TestCase { // delete source folder list($storage, $internalPath) = $this->rootView->resolvePath('/' . self::TEST_TRASHBIN_USER1 . '/files/folder'); - $folderAbsPath = $storage->getSourcePath($internalPath); - // make folder read-only - chmod($folderAbsPath, 0555); + if ($storage instanceof \OC\Files\Storage\Local) { + $folderAbsPath = $storage->getSourcePath($internalPath); + // make folder read-only + chmod($folderAbsPath, 0555); - $this->assertTrue( - OCA\Files_Trashbin\Trashbin::restore( - 'file1.txt.d' . $trashedFile->getMtime(), - $trashedFile->getName(), - $trashedFile->getMtime() - ) - ); + $this->assertTrue( + OCA\Files_Trashbin\Trashbin::restore( + 'file1.txt.d' . $trashedFile->getMtime(), + $trashedFile->getName(), + $trashedFile->getMtime() + ) + ); - $file = $userFolder->get('file1.txt'); - $this->assertEquals('foo', $file->getContent()); + $file = $userFolder->get('file1.txt'); + $this->assertEquals('foo', $file->getContent()); - chmod($folderAbsPath, 0755); + chmod($folderAbsPath, 0755); + } } /** diff --git a/apps/files_versions/ajax/preview.php b/apps/files_versions/ajax/preview.php index d7bc44f17a2..0da518f3eaa 100644 --- a/apps/files_versions/ajax/preview.php +++ b/apps/files_versions/ajax/preview.php @@ -1,7 +1,7 @@ <?php /** * @author Björn Schießle <schiessle@owncloud.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * diff --git a/apps/files_versions/appinfo/application.php b/apps/files_versions/appinfo/application.php index 00723e621ac..ba0a2ae74cb 100644 --- a/apps/files_versions/appinfo/application.php +++ b/apps/files_versions/appinfo/application.php @@ -1,6 +1,6 @@ <?php /** - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Victor Dubiniuk <dubiniuk@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_versions/appinfo/info.xml b/apps/files_versions/appinfo/info.xml index 8eab29b0ec6..8fe6372279a 100644 --- a/apps/files_versions/appinfo/info.xml +++ b/apps/files_versions/appinfo/info.xml @@ -4,16 +4,19 @@ <name>Versions</name> <licence>AGPL</licence> <author>Frank Karlitschek, Bjoern Schiessle</author> - <requiremin>4.93</requiremin> <shipped>true</shipped> <description> This application enables ownCloud to automatically maintain older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. ownCloud then automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions. In addition to the expiry of versions, ownCloud’s versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, ownCloud will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation. </description> + <version>1.2.0</version> <types> <filesystem/> </types> + <dependencies> + <owncloud min-version="9.0" /> + </dependencies> <documentation> <user>user-versions</user> </documentation> diff --git a/apps/files_versions/appinfo/routes.php b/apps/files_versions/appinfo/routes.php index 9bab86d9224..b2ed477de68 100644 --- a/apps/files_versions/appinfo/routes.php +++ b/apps/files_versions/appinfo/routes.php @@ -4,7 +4,7 @@ * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Tom Needham <tom@owncloud.com> * diff --git a/apps/files_versions/appinfo/version b/apps/files_versions/appinfo/version deleted file mode 100644 index af0b7ddbffd..00000000000 --- a/apps/files_versions/appinfo/version +++ /dev/null @@ -1 +0,0 @@ -1.0.6 diff --git a/apps/files_versions/l10n/ko.js b/apps/files_versions/l10n/ko.js index dca7683d2da..21c30390609 100644 --- a/apps/files_versions/l10n/ko.js +++ b/apps/files_versions/l10n/ko.js @@ -3,7 +3,7 @@ OC.L10N.register( { "Could not revert: %s" : "되돌릴 수 없습니다: %s", "Versions" : "버전", - "Failed to revert {file} to revision {timestamp}." : "{file}을(를) 리비전 {timestamp}으(로) 되돌리는 데 실패하였습니다.", + "Failed to revert {file} to revision {timestamp}." : "{file}을(를) 리비전 {timestamp}으(로) 되돌리는 데 실패했습니다.", "Restore" : "복원", "No other versions available" : "다른 버전을 사용할 수 없습니다", "More versions..." : "더 많은 버전..." diff --git a/apps/files_versions/l10n/ko.json b/apps/files_versions/l10n/ko.json index 1665579ded1..22ad4b685be 100644 --- a/apps/files_versions/l10n/ko.json +++ b/apps/files_versions/l10n/ko.json @@ -1,7 +1,7 @@ { "translations": { "Could not revert: %s" : "되돌릴 수 없습니다: %s", "Versions" : "버전", - "Failed to revert {file} to revision {timestamp}." : "{file}을(를) 리비전 {timestamp}으(로) 되돌리는 데 실패하였습니다.", + "Failed to revert {file} to revision {timestamp}." : "{file}을(를) 리비전 {timestamp}으(로) 되돌리는 데 실패했습니다.", "Restore" : "복원", "No other versions available" : "다른 버전을 사용할 수 없습니다", "More versions..." : "더 많은 버전..." diff --git a/apps/files_versions/l10n/sq.js b/apps/files_versions/l10n/sq.js index 4c9f9487f28..239946a7ef2 100644 --- a/apps/files_versions/l10n/sq.js +++ b/apps/files_versions/l10n/sq.js @@ -1,11 +1,11 @@ OC.L10N.register( "files_versions", { - "Could not revert: %s" : "Nuk mund të ktheje: %s", - "Versions" : "Versioni", - "Failed to revert {file} to revision {timestamp}." : "Dështoi në ktheje {skedar} të rishikimit {kohëstampe}.", - "Restore" : "Rivendos", - "No other versions available" : "Nuk ka versione të tjera në dispozicion", - "More versions..." : "Versione m'shumë..." + "Could not revert: %s" : "S’u rikthye dot: %s", + "Versions" : "Versione", + "Failed to revert {file} to revision {timestamp}." : "Dështoi në rikthimin e {file} te rishikimi {timestamp}.", + "Restore" : "Riktheje", + "No other versions available" : "Nuk ka versione të tjera të gatshme", + "More versions..." : "Më shumë versione…" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_versions/l10n/sq.json b/apps/files_versions/l10n/sq.json index 4e1c813d9bf..ae79f017be2 100644 --- a/apps/files_versions/l10n/sq.json +++ b/apps/files_versions/l10n/sq.json @@ -1,9 +1,9 @@ { "translations": { - "Could not revert: %s" : "Nuk mund të ktheje: %s", - "Versions" : "Versioni", - "Failed to revert {file} to revision {timestamp}." : "Dështoi në ktheje {skedar} të rishikimit {kohëstampe}.", - "Restore" : "Rivendos", - "No other versions available" : "Nuk ka versione të tjera në dispozicion", - "More versions..." : "Versione m'shumë..." + "Could not revert: %s" : "S’u rikthye dot: %s", + "Versions" : "Versione", + "Failed to revert {file} to revision {timestamp}." : "Dështoi në rikthimin e {file} te rishikimi {timestamp}.", + "Restore" : "Riktheje", + "No other versions available" : "Nuk ka versione të tjera të gatshme", + "More versions..." : "Më shumë versione…" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_versions/lib/capabilities.php b/apps/files_versions/lib/capabilities.php index 11b98038f46..ba4de906c70 100644 --- a/apps/files_versions/lib/capabilities.php +++ b/apps/files_versions/lib/capabilities.php @@ -2,7 +2,7 @@ /** * @author Christopher Schäpers <kondou@ts.unde.re> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Tom Needham <tom@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/files_versions/lib/storage.php b/apps/files_versions/lib/storage.php index fd51a54b108..6737bf20f90 100644 --- a/apps/files_versions/lib/storage.php +++ b/apps/files_versions/lib/storage.php @@ -44,6 +44,7 @@ namespace OCA\Files_Versions; use OCA\Files_Versions\AppInfo\Application; use OCA\Files_Versions\Command\Expire; use OCP\Lock\ILockingProvider; +use OCP\Files\NotFoundException; class Storage { @@ -74,15 +75,24 @@ class Storage { /** @var \OCA\Files_Versions\AppInfo\Application */ private static $application; + /** + * @param string $filename + * @return array + * @throws \OC\User\NoUserException + */ public static function getUidAndFilename($filename) { $uid = \OC\Files\Filesystem::getOwner($filename); \OC\Files\Filesystem::initMountPoints($uid); if ( $uid != \OCP\User::getUser() ) { $info = \OC\Files\Filesystem::getFileInfo($filename); $ownerView = new \OC\Files\View('/'.$uid.'/files'); - $filename = $ownerView->getPath($info['fileid']); + try { + $filename = $ownerView->getPath($info['fileid']); + } catch (NotFoundException $e) { + $filename = null; + } } - return array($uid, $filename); + return [$uid, $filename]; } /** @@ -347,7 +357,20 @@ class Storage { $view->lockFile($path1, ILockingProvider::LOCK_EXCLUSIVE); $view->lockFile($path2, ILockingProvider::LOCK_EXCLUSIVE); - $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2); + // TODO add a proper way of overwriting a file while maintaining file ids + if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) { + $source = $storage1->fopen($internalPath1, 'r'); + $target = $storage2->fopen($internalPath2, 'w'); + list(, $result) = \OC_Helper::streamCopy($source, $target); + fclose($source); + fclose($target); + + if ($result !== false) { + $storage1->unlink($internalPath1); + } + } else { + $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2); + } $view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE); $view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE); @@ -663,17 +686,21 @@ class Storage { // calculate available space for version history // subtract size of files and current versions size from quota - if ($softQuota) { - $files_view = new \OC\Files\View('/'.$uid.'/files'); - $rootInfo = $files_view->getFileInfo('/', false); - $free = $quota-$rootInfo['size']; // remaining free space for user - if ( $free > 0 ) { - $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - ($versionsSize + $offset); // how much space can be used for versions + if ($quota >= 0) { + if ($softQuota) { + $files_view = new \OC\Files\View('/' . $uid . '/files'); + $rootInfo = $files_view->getFileInfo('/', false); + $free = $quota - $rootInfo['size']; // remaining free space for user + if ($free > 0) { + $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - ($versionsSize + $offset); // how much space can be used for versions + } else { + $availableSpace = $free - $versionsSize - $offset; + } } else { - $availableSpace = $free - $versionsSize - $offset; + $availableSpace = $quota - $offset; } } else { - $availableSpace = $quota - $offset; + $availableSpace = PHP_INT_MAX; } $allVersions = Storage::getVersions($uid, $filename); diff --git a/apps/files_versions/tests/command/expiretest.php b/apps/files_versions/tests/command/expiretest.php index eb622689c33..eccc1f4c2ad 100644 --- a/apps/files_versions/tests/command/expiretest.php +++ b/apps/files_versions/tests/command/expiretest.php @@ -1,6 +1,7 @@ <?php /** * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 diff --git a/apps/files_versions/tests/versions.php b/apps/files_versions/tests/versions.php index 00d2b75b7a9..b9bc0932a84 100644 --- a/apps/files_versions/tests/versions.php +++ b/apps/files_versions/tests/versions.php @@ -301,11 +301,10 @@ class Test_Files_Versioning extends \Test\TestCase { // execute rename hook of versions app \OC\Files\Filesystem::rename('/folder1/test.txt', '/folder1/folder2/test.txt'); - - self::loginHelper(self::TEST_VERSIONS_USER2); - $this->runCommands(); + self::loginHelper(self::TEST_VERSIONS_USER); + $this->assertFalse($this->rootView->file_exists($v1)); $this->assertFalse($this->rootView->file_exists($v2)); @@ -760,7 +759,11 @@ class Test_Files_Versioning extends \Test\TestCase { ); } - private function createAndCheckVersions($view, $path) { + /** + * @param \OC\Files\View $view + * @param string $path + */ + private function createAndCheckVersions(\OC\Files\View $view, $path) { $view->file_put_contents($path, 'test file'); $view->file_put_contents($path, 'version 1'); $view->file_put_contents($path, 'version 2'); @@ -783,7 +786,6 @@ class Test_Files_Versioning extends \Test\TestCase { /** * @param string $user * @param bool $create - * @param bool $password */ public static function loginHelper($user, $create = false) { diff --git a/apps/provisioning_api/appinfo/info.xml b/apps/provisioning_api/appinfo/info.xml index 7c662c18c09..a77b1f67e21 100644 --- a/apps/provisioning_api/appinfo/info.xml +++ b/apps/provisioning_api/appinfo/info.xml @@ -13,14 +13,17 @@ </description> <licence>AGPL</licence> <author>Tom Needham</author> - <requiremin>8</requiremin> <shipped>true</shipped> <default_enable/> <documentation> <admin>admin-provisioning-api</admin> </documentation> + <version>0.4.0</version> <types> <!-- this is used to disable the feature of enabling an app for specific groups only because this would break this app --> <filesystem/> </types> + <dependencies> + <owncloud min-version="9.0" /> + </dependencies> </info> diff --git a/apps/provisioning_api/appinfo/routes.php b/apps/provisioning_api/appinfo/routes.php index 5c53684fd77..2a4b50dc27c 100644 --- a/apps/provisioning_api/appinfo/routes.php +++ b/apps/provisioning_api/appinfo/routes.php @@ -1,8 +1,9 @@ <?php /** * @author Joas Schilling <nickvergessen@owncloud.com> + * @author michag86 <micha_g@arcor.de> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Tom Needham <tom@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. @@ -33,7 +34,7 @@ $users = new \OCA\Provisioning_API\Users( \OC::$server->getGroupManager(), \OC::$server->getUserSession() ); -API::register('get', '/cloud/users', [$users, 'getUsers'], 'provisioning_api', API::ADMIN_AUTH); +API::register('get', '/cloud/users', [$users, 'getUsers'], 'provisioning_api', API::SUBADMIN_AUTH); API::register('post', '/cloud/users', [$users, 'addUser'], 'provisioning_api', API::ADMIN_AUTH); API::register('get', '/cloud/users/{userid}', [$users, 'getUser'], 'provisioning_api', API::USER_AUTH); API::register('put', '/cloud/users/{userid}', [$users, 'editUser'], 'provisioning_api', API::USER_AUTH); diff --git a/apps/provisioning_api/appinfo/version b/apps/provisioning_api/appinfo/version deleted file mode 100644 index 3b04cfb60da..00000000000 --- a/apps/provisioning_api/appinfo/version +++ /dev/null @@ -1 +0,0 @@ -0.2 diff --git a/apps/provisioning_api/lib/apps.php b/apps/provisioning_api/lib/apps.php index ba920c7a9c9..e0fa63cca34 100644 --- a/apps/provisioning_api/lib/apps.php +++ b/apps/provisioning_api/lib/apps.php @@ -3,7 +3,7 @@ * @author Joas Schilling <nickvergessen@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Tom Needham <tom@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/provisioning_api/lib/groups.php b/apps/provisioning_api/lib/groups.php index 7e7515bc709..5b613562324 100644 --- a/apps/provisioning_api/lib/groups.php +++ b/apps/provisioning_api/lib/groups.php @@ -3,7 +3,7 @@ * @author Joas Schilling <nickvergessen@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Tom Needham <tom@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/provisioning_api/lib/users.php b/apps/provisioning_api/lib/users.php index add6325bde0..527b107ad50 100644 --- a/apps/provisioning_api/lib/users.php +++ b/apps/provisioning_api/lib/users.php @@ -2,8 +2,9 @@ /** * @author Joas Schilling <nickvergessen@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> + * @author michag86 <micha_g@arcor.de> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Tom Needham <tom@owncloud.com> * @@ -29,6 +30,7 @@ namespace OCA\Provisioning_API; use \OC_OCS_Result; use \OC_SubAdmin; use \OC_Helper; +use \OC_Group; use OCP\Files\NotFoundException; class Users { @@ -71,7 +73,31 @@ class Users { $limit = !empty($_GET['limit']) ? $_GET['limit'] : null; $offset = !empty($_GET['offset']) ? $_GET['offset'] : null; - $users = $this->userManager->search($search, $limit, $offset); + // Check if user is logged in + $user = $this->userSession->getUser(); + if ($user === null) { + return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); + } + + // Admin? Or SubAdmin? + if($this->groupManager->isAdmin($user->getUID())){ + $users = $this->userManager->search($search, $limit, $offset); + } else if (\OC_SubAdmin::isSubAdmin($user->getUID())) { + $subAdminOfGroups = \OC_SubAdmin::getSubAdminsGroups($user->getUID()); + + if($offset === null) { + $offset = 0; + } + + $users = []; + foreach ($subAdminOfGroups as $group) { + $users = array_merge($users, $this->groupManager->displayNamesInGroup($group, $search)); + } + + $users = array_slice($users, $offset, $limit); + } else { + return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); + } $users = array_keys($users); return new OC_OCS_Result([ @@ -115,46 +141,28 @@ class Users { return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); } + $data = []; + // Admin? Or SubAdmin? if($this->groupManager->isAdmin($user->getUID()) || OC_SubAdmin::isUserAccessible($user->getUID(), $userId)) { // Check they exist if(!$this->userManager->userExists($userId)) { return new OC_OCS_Result(null, \OCP\API::RESPOND_NOT_FOUND, 'The requested user could not be found'); } - // Show all - $return = [ - 'email', - 'enabled', - ]; - if($user->getUID() !== $userId) { - $return[] = 'quota'; - } + $data['enabled'] = $this->config->getUserValue($userId, 'core', 'enabled', 'true'); } else { // Check they are looking up themselves if($user->getUID() !== $userId) { return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); } - // Return some additional information compared to the core route - $return = array( - 'email', - 'displayname', - ); } // Find the data - $data = []; - $data = self::fillStorageInfo($userId, $data); - $data['enabled'] = $this->config->getUserValue($userId, 'core', 'enabled', 'true'); + $data['quota'] = self::fillStorageInfo($userId); $data['email'] = $this->config->getUserValue($userId, 'settings', 'email'); - $data['displayname'] = $this->userManager->get($parameters['userid'])->getDisplayName(); + $data['displayname'] = $this->userManager->get($userId)->getDisplayName(); - // Return the appropriate data - $responseData = array(); - foreach($return as $key) { - $responseData[$key] = $data[$key]; - } - - return new OC_OCS_Result($responseData); + return new OC_OCS_Result($data); } /** @@ -473,19 +481,20 @@ class Users { * @return mixed * @throws \OCP\Files\NotFoundException */ - private static function fillStorageInfo($userId, $data) { + private static function fillStorageInfo($userId) { + $data = []; try { \OC_Util::tearDownFS(); \OC_Util::setupFS($userId); $storage = OC_Helper::getStorageInfo('/'); - $data['quota'] = [ + $data = [ 'free' => $storage['free'], 'used' => $storage['used'], 'total' => $storage['total'], 'relative' => $storage['relative'], ]; } catch (NotFoundException $ex) { - $data['quota'] = []; + $data = []; } return $data; } diff --git a/apps/provisioning_api/tests/appstest.php b/apps/provisioning_api/tests/appstest.php index 2baea5bbc8c..2e1a86025c2 100644 --- a/apps/provisioning_api/tests/appstest.php +++ b/apps/provisioning_api/tests/appstest.php @@ -2,7 +2,7 @@ /** * @author Joas Schilling <nickvergessen@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Tom Needham <tom@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/provisioning_api/tests/groupstest.php b/apps/provisioning_api/tests/groupstest.php index 4afd246abcd..c75ba76bd35 100644 --- a/apps/provisioning_api/tests/groupstest.php +++ b/apps/provisioning_api/tests/groupstest.php @@ -2,7 +2,7 @@ /** * @author Joas Schilling <nickvergessen@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Tom Needham <tom@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. diff --git a/apps/provisioning_api/tests/testcase.php b/apps/provisioning_api/tests/testcase.php index cc8f5c1d16d..113bc512243 100644 --- a/apps/provisioning_api/tests/testcase.php +++ b/apps/provisioning_api/tests/testcase.php @@ -2,7 +2,7 @@ /** * @author Joas Schilling <nickvergessen@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 @@ -46,7 +46,7 @@ abstract class TestCase extends \Test\TestCase { /** * Generates a temp user * @param int $num number of users to generate - * @return array + * @return IUser[]|Iuser */ protected function generateUsers($num = 1) { $users = array(); diff --git a/apps/provisioning_api/tests/userstest.php b/apps/provisioning_api/tests/userstest.php index 350586f8335..607e6f118ae 100644 --- a/apps/provisioning_api/tests/userstest.php +++ b/apps/provisioning_api/tests/userstest.php @@ -2,7 +2,7 @@ /** * @author Joas Schilling <nickvergessen@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Roeland Jago Douma <rullzer@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Tom Needham <tom@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> @@ -63,10 +63,16 @@ class UsersTest extends TestCase { $this->groupManager, $this->userSession ); + + $this->userSession->setUser(null); } // Test getting the list of users - public function testGetUsers() { + public function testGetUsersAsAdmin() { + $user = $this->generateUsers(); + $this->groupManager->get('admin')->addUser($user); + $this->userSession->setUser($user); + $result = $this->api->getUsers(); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); @@ -103,6 +109,83 @@ class UsersTest extends TestCase { $this->assertEquals(array_keys($this->userManager->search('', 1, 1)), $data['users']); } + public function testGetUsersAsSubAdmin() { + $user = $this->generateUsers(10); + $this->userSession->setUser($user[0]); + $group = $this->groupManager->createGroup($this->getUniqueID()); + \OC_SubAdmin::createSubAdmin($user[0]->getUID(), $group->getGID()); + + //Empty list + $result = $this->api->getUsers([]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertTrue($result->succeeded()); + $this->assertEquals(['users' => []], $result->getData()); + + //Some users in group + $group->addUser($user[1]); + $group->addUser($user[2]); + $group->addUser($user[3]); + $group->addUser($user[4]); + + $result = $this->api->getUsers([]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertTrue($result->succeeded()); + $this->assertArrayHasKey('users', $result->getData()); + + $this->assertContains($user[1]->getUID(), $result->getData()['users']); + $this->assertContains($user[2]->getUID(), $result->getData()['users']); + $this->assertContains($user[3]->getUID(), $result->getData()['users']); + $this->assertContains($user[4]->getUID(), $result->getData()['users']); + + $uids = [ + $user[1]->getUID(), + $user[2]->getUID(), + $user[3]->getUID(), + $user[4]->getUID() + ]; + sort($uids); + + $_GET['limit'] = 2; + $_GET['offset'] = 1; + $result = $this->api->getUsers([]); + + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertTrue($result->succeeded()); + + // Disable this test for now since sorting is not done the same on all backends + //$this->assertEquals(['users' => array_slice($uids, 1, 2)], $result->getData()); + + $this->assertCount(2, $result->getData()['users']); + + $counter = 0; + foreach ($uids as $uid) { + if (in_array($uid, $result->getData()['users'], true)) { + $counter += 1; + } + } + + $this->assertEquals(2, $counter); + } + + public function testGetUsersNoUser() { + $result = $this->api->getUsers([]); + + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(\OCP\API::RESPOND_UNAUTHORISED, $result->getStatusCode()); + } + + public function testGetUsersAsUser() { + $user = $this->generateUsers(); + $this->userSession->setUser($user); + + $result = $this->api->getUsers(); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(\OCP\API::RESPOND_UNAUTHORISED, $result->getStatusCode()); + + } + public function testAddUser() { $this->resetParams(); $_POST['userid'] = $this->getUniqueID(); @@ -164,7 +247,7 @@ class UsersTest extends TestCase { $user = $this->generateUsers(); $user->setDisplayName('foobar'); $this->userSession->setUser($user); - $params['userid'] = $user->getUID(); + $params = ['userid' => $user->getUID()]; $result = $this->api->getUser($params); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); @@ -191,7 +274,7 @@ class UsersTest extends TestCase { public function testGetUserOnOtherUser() { $users = $this->generateUsers(2); - $params['userid'] = $users[0]; + $params = ['userid' => $users[0]->getUID()]; $this->userSession->setUser($users[1]); $result = $this->api->getUser($params); $this->assertInstanceOf('OC_OCS_Result', $result); @@ -794,6 +877,9 @@ class UsersTest extends TestCase { } public function testAddToGroupNoGroupId() { + $user = $this->generateUsers(); + $this->userSession->setUser($user); + $_POST['groupid'] = ''; $result = $this->api->addToGroup([ 'userid' => $this->getUniqueID(), @@ -935,6 +1021,9 @@ class UsersTest extends TestCase { } public function testRemoveFromGroupNoGroupId() { + $user = $this->generateUsers(); + $this->userSession->setUser($user); + $result = $this->api->removeFromGroup([ '_delete' => [ 'groupid' => '' diff --git a/apps/user_ldap/appinfo/info.xml b/apps/user_ldap/appinfo/info.xml index 89c1a4ea3ff..7dbce1da5fa 100644 --- a/apps/user_ldap/appinfo/info.xml +++ b/apps/user_ldap/appinfo/info.xml @@ -9,8 +9,8 @@ A user logs into ownCloud with their LDAP or AD credentials, and is granted acce </description> <licence>AGPL</licence> <author>Dominik Schmidt and Arthur Schiwon</author> - <requiremin>4.93</requiremin> <shipped>true</shipped> + <version>0.8.0</version> <types> <authentication/> </types> @@ -19,5 +19,6 @@ A user logs into ownCloud with their LDAP or AD credentials, and is granted acce </documentation> <dependencies> <lib>ldap</lib> + <owncloud min-version="9.0" /> </dependencies> </info> diff --git a/apps/user_ldap/appinfo/version b/apps/user_ldap/appinfo/version deleted file mode 100644 index 844f6a91acb..00000000000 --- a/apps/user_ldap/appinfo/version +++ /dev/null @@ -1 +0,0 @@ -0.6.3 diff --git a/apps/user_ldap/group_ldap.php b/apps/user_ldap/group_ldap.php index 97ed8fa91ac..d7ca786a439 100644 --- a/apps/user_ldap/group_ldap.php +++ b/apps/user_ldap/group_ldap.php @@ -6,6 +6,7 @@ * @author Bart Visscher <bartv@thisnet.nl> * @author Christopher Schäpers <kondou@ts.unde.re> * @author Frédéric Fortier <frederic.fortier@oronospolytechnique.com> + * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Nicolas Grekas <nicolas.grekas@gmail.com> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> @@ -33,7 +34,6 @@ namespace OCA\user_ldap; use OCA\user_ldap\lib\Access; use OCA\user_ldap\lib\BackendUtility; -use OCA\user_ldap\lib\user\User; class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { protected $enabled = false; @@ -198,11 +198,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { return array(); } $seen[$DN] = 1; - $user = $this->access->userManager->get($DN); - if(!$user instanceof User) { - return array(); - } - $groups = $user->getMemberOfGroups(); + $groups = $this->access->readAttribute($DN, 'memberOf'); if (!is_array($groups)) { return array(); } @@ -214,7 +210,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { $subGroups = $this->_getGroupDNsFromMemberOf($group, $seen); $allGroups = array_merge($allGroups, $subGroups); } - } + } return $allGroups; } @@ -247,7 +243,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { if(empty($result)) { return false; } - $dn = $result[0]; + $dn = $result[0]['dn'][0]; //and now the group name //NOTE once we have separate ownCloud group IDs and group names we can @@ -491,7 +487,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { array($this->access->connection->ldapGroupDisplayName, 'dn')); if (is_array($groups)) { foreach ($groups as $groupobj) { - $groupDN = $groupobj['dn']; + $groupDN = $groupobj['dn'][0]; $allGroups[$groupDN] = $groupobj; $nestedGroups = $this->access->connection->ldapNestedGroups; if (!empty($nestedGroups)) { @@ -653,7 +649,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { str_replace('%uid', $member, $this->access->connection->ldapLoginFilter), $this->access->getFilterPartForUserSearch($search) )); - $ldap_users = $this->access->fetchListOfUsers($filter, 'dn'); + $ldap_users = $this->access->fetchListOfUsers($filter, 'dn', 1); if(count($ldap_users) < 1) { continue; } diff --git a/apps/user_ldap/js/wizard/wizardTabElementary.js b/apps/user_ldap/js/wizard/wizardTabElementary.js index 7c1a550c097..f5232e91010 100644 --- a/apps/user_ldap/js/wizard/wizardTabElementary.js +++ b/apps/user_ldap/js/wizard/wizardTabElementary.js @@ -137,6 +137,10 @@ OCA = OCA || {}; this.setElementValue( this.managedItems.ldap_agent_password.$element, agentPwd ); + if (agentPwd && $('html').hasClass('lte9')) { + // make it a password field again (IE fix, placeholders bug) + this.managedItems.ldap_agent_password.$element.attr('type', 'password'); + } }, /** * updates the base DN text area diff --git a/apps/user_ldap/l10n/de.js b/apps/user_ldap/l10n/de.js index 9a25c8e5b9a..375b78b4526 100644 --- a/apps/user_ldap/l10n/de.js +++ b/apps/user_ldap/l10n/de.js @@ -67,7 +67,7 @@ OC.L10N.register( "LDAP Filter:" : "LDAP-Filter:", "The filter specifies which LDAP groups shall have access to the %s instance." : "Der Filter bestimmt, welche LDAP-Gruppen Zugriff auf die %s-Instanz haben sollen.", "Verify settings and count groups" : "Einstellungen überprüfen und Gruppen zählen", - "When logging in, %s will find the user based on the following attributes:" : "Beim Anmelden wird %s die Nutzerbasis mit folgenden Attributen vorfinden:", + "When logging in, %s will find the user based on the following attributes:" : "Beim Anmelden wird %s den Nutzer basierend auf folgenden Attributen finden:", "LDAP / AD Username:" : "LDAP-/AD-Benutzername:", "Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected." : "Erlaubt das Anmelden gegen den LDAP / AD Nutzernamen, welcher entweder eine UID oder samaccountname ist und automatisch erkannt wird.", "LDAP / AD Email Address:" : "LDAP-/AD-E-Mail-Adresse:", diff --git a/apps/user_ldap/l10n/de.json b/apps/user_ldap/l10n/de.json index dcea495adf6..17c0bf02c2d 100644 --- a/apps/user_ldap/l10n/de.json +++ b/apps/user_ldap/l10n/de.json @@ -65,7 +65,7 @@ "LDAP Filter:" : "LDAP-Filter:", "The filter specifies which LDAP groups shall have access to the %s instance." : "Der Filter bestimmt, welche LDAP-Gruppen Zugriff auf die %s-Instanz haben sollen.", "Verify settings and count groups" : "Einstellungen überprüfen und Gruppen zählen", - "When logging in, %s will find the user based on the following attributes:" : "Beim Anmelden wird %s die Nutzerbasis mit folgenden Attributen vorfinden:", + "When logging in, %s will find the user based on the following attributes:" : "Beim Anmelden wird %s den Nutzer basierend auf folgenden Attributen finden:", "LDAP / AD Username:" : "LDAP-/AD-Benutzername:", "Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected." : "Erlaubt das Anmelden gegen den LDAP / AD Nutzernamen, welcher entweder eine UID oder samaccountname ist und automatisch erkannt wird.", "LDAP / AD Email Address:" : "LDAP-/AD-E-Mail-Adresse:", diff --git a/apps/user_ldap/l10n/de_DE.js b/apps/user_ldap/l10n/de_DE.js index f7558f5ef04..49fcd8c8c69 100644 --- a/apps/user_ldap/l10n/de_DE.js +++ b/apps/user_ldap/l10n/de_DE.js @@ -67,7 +67,7 @@ OC.L10N.register( "LDAP Filter:" : "LDAP-Filter:", "The filter specifies which LDAP groups shall have access to the %s instance." : "Der Filter bestimmt, welche LDAP-Gruppen Zugriff auf die %s-Instanz haben sollen.", "Verify settings and count groups" : "Einstellungen überprüfen und Gruppen zählen", - "When logging in, %s will find the user based on the following attributes:" : "Beim Anmelden wird %s die Nutzerbasis mit folgenden Attributen vorfinden:", + "When logging in, %s will find the user based on the following attributes:" : "Beim Anmelden wird %s den Nutzer basierend auf folgenden Attributen finden:", "LDAP / AD Username:" : "LDAP-/AD-Benutzername:", "Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected." : "Erlaubt das Anmelden gegen den LDAP / AD Nutzernamen, welcher entweder eine UID oder samaccountname ist und automatisch erkannt wird.", "LDAP / AD Email Address:" : "LDAP-/AD-E-Mail-Adresse:", diff --git a/apps/user_ldap/l10n/de_DE.json b/apps/user_ldap/l10n/de_DE.json index fb397693095..61792d3704e 100644 --- a/apps/user_ldap/l10n/de_DE.json +++ b/apps/user_ldap/l10n/de_DE.json @@ -65,7 +65,7 @@ "LDAP Filter:" : "LDAP-Filter:", "The filter specifies which LDAP groups shall have access to the %s instance." : "Der Filter bestimmt, welche LDAP-Gruppen Zugriff auf die %s-Instanz haben sollen.", "Verify settings and count groups" : "Einstellungen überprüfen und Gruppen zählen", - "When logging in, %s will find the user based on the following attributes:" : "Beim Anmelden wird %s die Nutzerbasis mit folgenden Attributen vorfinden:", + "When logging in, %s will find the user based on the following attributes:" : "Beim Anmelden wird %s den Nutzer basierend auf folgenden Attributen finden:", "LDAP / AD Username:" : "LDAP-/AD-Benutzername:", "Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected." : "Erlaubt das Anmelden gegen den LDAP / AD Nutzernamen, welcher entweder eine UID oder samaccountname ist und automatisch erkannt wird.", "LDAP / AD Email Address:" : "LDAP-/AD-E-Mail-Adresse:", diff --git a/apps/user_ldap/l10n/fr.js b/apps/user_ldap/l10n/fr.js index 3bf26f0e3ea..9e863ada3b5 100644 --- a/apps/user_ldap/l10n/fr.js +++ b/apps/user_ldap/l10n/fr.js @@ -39,7 +39,7 @@ OC.L10N.register( "Select attributes" : "Sélectionner les attributs", "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "Utilisateur introuvable. Veuillez vérifier les attributs de login et le nom d'utilisateur. Filtre effectif (à copier-coller pour valider en ligne de commande):<br/>", "User found and settings verified." : "Utilisateur trouvé et paramètres vérifiés.", - "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "Paramètres vérifiés, mais seul le premier utilisateur pourra se connecter. Utilisez plutôt un filtre moins restrictif.", + "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter.", "An unspecified error occurred. Please check the settings and the log." : "Une erreur inconnue s'est produite. Veuillez vérifier les paramètres et le log.", "The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "Le filtre de recherche n'est pas valide, probablement à cause de problèmes de syntaxe tels que des parenthèses manquantes. Veuillez le corriger.", "A connection error to LDAP / AD occurred, please check host, port and credentials." : "Une erreur s'est produite lors de la connexion au LDAP / AD. Veuillez vérifier l'hôte, le port et les informations d'identification.", diff --git a/apps/user_ldap/l10n/fr.json b/apps/user_ldap/l10n/fr.json index 87cf780e825..a26710c340b 100644 --- a/apps/user_ldap/l10n/fr.json +++ b/apps/user_ldap/l10n/fr.json @@ -37,7 +37,7 @@ "Select attributes" : "Sélectionner les attributs", "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "Utilisateur introuvable. Veuillez vérifier les attributs de login et le nom d'utilisateur. Filtre effectif (à copier-coller pour valider en ligne de commande):<br/>", "User found and settings verified." : "Utilisateur trouvé et paramètres vérifiés.", - "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "Paramètres vérifiés, mais seul le premier utilisateur pourra se connecter. Utilisez plutôt un filtre moins restrictif.", + "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter.", "An unspecified error occurred. Please check the settings and the log." : "Une erreur inconnue s'est produite. Veuillez vérifier les paramètres et le log.", "The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "Le filtre de recherche n'est pas valide, probablement à cause de problèmes de syntaxe tels que des parenthèses manquantes. Veuillez le corriger.", "A connection error to LDAP / AD occurred, please check host, port and credentials." : "Une erreur s'est produite lors de la connexion au LDAP / AD. Veuillez vérifier l'hôte, le port et les informations d'identification.", diff --git a/apps/user_ldap/l10n/ja.js b/apps/user_ldap/l10n/ja.js index 43402d94ace..2db99acf7af 100644 --- a/apps/user_ldap/l10n/ja.js +++ b/apps/user_ldap/l10n/ja.js @@ -3,6 +3,7 @@ OC.L10N.register( { "Failed to clear the mappings." : "マッピングのクリアに失敗しました。", "Failed to delete the server configuration" : "サーバー設定の削除に失敗しました", + "The configuration is invalid: anonymous bind is not allowed." : "設定は無効です: 匿名接続は、許可されていません。", "The configuration is valid and the connection could be established!" : "設定は有効です。接続できました。", "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "設定は有効ですが、接続に失敗しました。サーバー設定と資格情報を確認してください。", "The configuration is invalid. Please have a look at the logs for further details." : "設定が無効です。詳細はログを確認してください。", @@ -24,10 +25,27 @@ OC.L10N.register( "{nthServer}. Server" : "{nthServer}. サーバー", "No object found in the given Base DN. Please revise." : "指定されたベース DN でオブジェクトを見つけることができませんでした。修正をお願いします。", "More than 1.000 directory entries available." : "1000 以上のディレクトリエントリが利用可能です。", + " entries available within the provided Base DN" : "入力されたベースDNでエントリーが利用可能", + "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "エラーが発生しました。ベースDNをチェックし、接続設定と権限についても同様に確認してください。", "Do you really want to delete the current Server Configuration?" : "現在のサーバー設定を本当に削除してもよろしいですか?", "Confirm Deletion" : "削除の確認", "Mappings cleared successfully!" : "マッピングのクリアに成功しました!", + "Error while clearing the mappings." : "マッピングのクリアー中にエラー発生", + "Anonymous bind is not allowed. Please provide a User DN and Password." : "匿名接続が許可されていません。ユーザーDNとパスワードを入力してください。", + "LDAP Operations error. Anonymous bind might not be allowed." : "LDAP操作エラー。匿名接続が許可されていないのかもしれません。", + "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "保存に失敗。データベースが稼働中か確認してください。続ける前にリロードしてください。", + "Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?" : "モード切替により自動LDAP問合せが有効になります。LDAPのデータ量により時間がかかる可能性があります。モードを切り替えますか?", + "Mode switch" : "モード変更", "Select attributes" : "属性を選択", + "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "ユーザーは見つかりませんでした。ログインの属性とユーザー名をチェックしてください。適用されているフィルター(コピーペーストしてコマンドラインでの確認できます): <br/>", + "User found and settings verified." : "ユーザーが見つかり、設定が検証できました。", + "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "設定は検証できましたが、ユーザーが1名見つかりました。最初の1名だけログインできます。より厳しいフィルターを検討してください。", + "An unspecified error occurred. Please check the settings and the log." : "不明なエラーが発生しました。設定とログを確認してください。", + "The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "検索フィルターが不正です。恐らく文法の問題で、開き括弧と閉じ括弧がマッチしていません。修正をお願いします。", + "A connection error to LDAP / AD occurred, please check host, port and credentials." : "LDAP / AD の接続エラーが発生しました。ホスト名、ポート、権限をチェックしてください。", + "The %uid placeholder is missing. It will be replaced with the login name when querying LDAP / AD." : "%uid のプレースフォルダがありません。プレースフォルダでは、LDAP /ADで問合せするときにログイン名で置き換えられます。", + "Please provide a login name to test against" : "テストの為にログイン名を入力してください。", + "The group box was disabled, because the LDAP / AD server does not support memberOf." : "グループボックスは無効にされました。LDAP/AD サーバーが MemberOf オプションをサポートしていないからです。", "_%s group found_::_%s groups found_" : ["%s グループが見つかりました"], "_%s user found_::_%s users found_" : ["%s ユーザーが見つかりました"], "Could not detect user display name attribute. Please specify it yourself in advanced ldap settings." : "ユーザー表示名の属性を検出できませんでした。詳細設定で対応する属性を指定してください。", @@ -40,6 +58,8 @@ OC.L10N.register( "Test Configuration" : "設定をテスト", "Help" : "ヘルプ", "Groups meeting these criteria are available in %s:" : "これらの基準を満たすグループが %s で利用可能:", + "Only these object classes:" : "このオブジェクトクラスからのみ:", + "Only from these groups:" : "これらのグループからのみ:", "Search groups" : "グループを検索", "Available groups" : "利用可能なグループ", "Selected groups" : "選択されたグループ", @@ -47,13 +67,20 @@ OC.L10N.register( "LDAP Filter:" : "LDAP フィルタ:", "The filter specifies which LDAP groups shall have access to the %s instance." : "フィルターは、どの LDAP グループが %s にアクセスするかを指定します。", "Verify settings and count groups" : "設定を検証し、グループを数える", + "When logging in, %s will find the user based on the following attributes:" : "ログイン時に、%s により次の属性からユーザーを見つけます:", "LDAP / AD Username:" : "LDAP/ADユーザー名:", + "Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected." : "LDAP / AD ユーザー名に対してログインが許可されています。uid か、samaccountname のどちらかが検出されました。", "LDAP / AD Email Address:" : "LDAP / AD メールアドレス:", + "Allows login against an email attribute. Mail and mailPrimaryAddress will be allowed." : "メール属性に対してログインが許可されています。メールや mailPrimaryAddress が許可されています。", "Other Attributes:" : "その他の属性:", "Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: \"uid=%%uid\"" : "ログイン実行時に適用するフィルターを定義します。%%uid にはログイン操作におけるユーザー名が入ります。例: \"uid=%%uid\"", + "Test Loginname" : "テスト用ログイン名", "Verify settings" : "設定のチェック", "1. Server" : "1. Server", "%s. Server:" : "%s. サーバー:", + "Add a new and blank configuration" : "空欄の新しい設定を追加", + "Copy current configuration into new directory binding" : "現在の設定を新しいディレクトリ設定にコピー", + "Delete the current configuration" : "現在の設定を削除", "Host" : "ホスト", "You can omit the protocol, except you require SSL. Then start with ldaps://" : "SSL通信しない場合には、プロトコル名を省略することができます。そうでない場合には、ldaps:// から始めてください。", "Port" : "ポート", @@ -69,8 +96,9 @@ OC.L10N.register( "Avoids automatic LDAP requests. Better for bigger setups, but requires some LDAP knowledge." : "自動的なLDAP問合せを停止します。大規模な設定には適していますが、LDAPの知識が必要になります。", "Manually enter LDAP filters (recommended for large directories)" : "手動でLDAPフィルターを入力(大規模ディレクトリ時のみ推奨)", "Limit %s access to users meeting these criteria:" : "以下のフィルターに適合するユーザーのみ %s へアクセスを許可:", + "The most common object classes for users are organizationalPerson, person, user, and inetOrgPerson. If you are not sure which object class to select, please consult your directory admin." : "ユーザーの最も一般的なオブジェクトクラスは、organizationalPerson, person, user と inetOrgPerson です。もし、どのオブジェクトを選択すれば良いか分からない場合は、ディレクトリ管理者に相談してください。", "The filter specifies which LDAP users shall have access to the %s instance." : "フィルターは、どのLDAPユーザーが %s にアクセスするかを指定します。", - "Verify settings and count users" : "設定を検証し、ユーザを数える", + "Verify settings and count users" : "設定を検証し、ユーザーを数える", "Saving" : "保存中", "Back" : "戻る", "Continue" : "続ける", @@ -123,6 +151,7 @@ OC.L10N.register( "UUID Attribute for Users:" : "ユーザーのUUID属性:", "UUID Attribute for Groups:" : "グループの UUID 属性:", "Username-LDAP User Mapping" : "ユーザー名とLDAPユーザのマッピング", + "Usernames are used to store and assign (meta) data. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage." : "ユーザー名は(メタ)データの保存と割り当てに使用されます。ユーザーを正確に識別して認識するために、個々のLDAPユーザは内部ユーザ名を持っています。これは、ユーザー名からLDAPユーザーへのマッピングが必要であることを意味しています。この生成されたユーザ名は、LDAPユーザのUUIDにマッピングされます。加えて、DNがLDAPとのインタラクションを削減するためにキャッシュされますが、識別には利用されません。DNが変わった場合は、変更が検出されます。内部ユーザ名は全体に亘って利用されます。マッピングをクリアすると、いたるところに使われないままの物が残るでしょう。マッピングのクリアは設定に敏感ではありませんが、すべてのLDAPの設定に影響を与えます!本番の環境では決してマッピングをクリアしないでください。テストもしくは実験の段階でのみマッピングのクリアを行なってください。", "Clear Username-LDAP User Mapping" : "ユーザー名とLDAPユーザーのマッピングをクリアする", "Clear Groupname-LDAP Group Mapping" : "グループ名とLDAPグループのマッピングをクリアする" }, diff --git a/apps/user_ldap/l10n/ja.json b/apps/user_ldap/l10n/ja.json index 81a918eae02..3a9475699ae 100644 --- a/apps/user_ldap/l10n/ja.json +++ b/apps/user_ldap/l10n/ja.json @@ -1,6 +1,7 @@ { "translations": { "Failed to clear the mappings." : "マッピングのクリアに失敗しました。", "Failed to delete the server configuration" : "サーバー設定の削除に失敗しました", + "The configuration is invalid: anonymous bind is not allowed." : "設定は無効です: 匿名接続は、許可されていません。", "The configuration is valid and the connection could be established!" : "設定は有効です。接続できました。", "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "設定は有効ですが、接続に失敗しました。サーバー設定と資格情報を確認してください。", "The configuration is invalid. Please have a look at the logs for further details." : "設定が無効です。詳細はログを確認してください。", @@ -22,10 +23,27 @@ "{nthServer}. Server" : "{nthServer}. サーバー", "No object found in the given Base DN. Please revise." : "指定されたベース DN でオブジェクトを見つけることができませんでした。修正をお願いします。", "More than 1.000 directory entries available." : "1000 以上のディレクトリエントリが利用可能です。", + " entries available within the provided Base DN" : "入力されたベースDNでエントリーが利用可能", + "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "エラーが発生しました。ベースDNをチェックし、接続設定と権限についても同様に確認してください。", "Do you really want to delete the current Server Configuration?" : "現在のサーバー設定を本当に削除してもよろしいですか?", "Confirm Deletion" : "削除の確認", "Mappings cleared successfully!" : "マッピングのクリアに成功しました!", + "Error while clearing the mappings." : "マッピングのクリアー中にエラー発生", + "Anonymous bind is not allowed. Please provide a User DN and Password." : "匿名接続が許可されていません。ユーザーDNとパスワードを入力してください。", + "LDAP Operations error. Anonymous bind might not be allowed." : "LDAP操作エラー。匿名接続が許可されていないのかもしれません。", + "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "保存に失敗。データベースが稼働中か確認してください。続ける前にリロードしてください。", + "Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?" : "モード切替により自動LDAP問合せが有効になります。LDAPのデータ量により時間がかかる可能性があります。モードを切り替えますか?", + "Mode switch" : "モード変更", "Select attributes" : "属性を選択", + "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "ユーザーは見つかりませんでした。ログインの属性とユーザー名をチェックしてください。適用されているフィルター(コピーペーストしてコマンドラインでの確認できます): <br/>", + "User found and settings verified." : "ユーザーが見つかり、設定が検証できました。", + "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "設定は検証できましたが、ユーザーが1名見つかりました。最初の1名だけログインできます。より厳しいフィルターを検討してください。", + "An unspecified error occurred. Please check the settings and the log." : "不明なエラーが発生しました。設定とログを確認してください。", + "The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "検索フィルターが不正です。恐らく文法の問題で、開き括弧と閉じ括弧がマッチしていません。修正をお願いします。", + "A connection error to LDAP / AD occurred, please check host, port and credentials." : "LDAP / AD の接続エラーが発生しました。ホスト名、ポート、権限をチェックしてください。", + "The %uid placeholder is missing. It will be replaced with the login name when querying LDAP / AD." : "%uid のプレースフォルダがありません。プレースフォルダでは、LDAP /ADで問合せするときにログイン名で置き換えられます。", + "Please provide a login name to test against" : "テストの為にログイン名を入力してください。", + "The group box was disabled, because the LDAP / AD server does not support memberOf." : "グループボックスは無効にされました。LDAP/AD サーバーが MemberOf オプションをサポートしていないからです。", "_%s group found_::_%s groups found_" : ["%s グループが見つかりました"], "_%s user found_::_%s users found_" : ["%s ユーザーが見つかりました"], "Could not detect user display name attribute. Please specify it yourself in advanced ldap settings." : "ユーザー表示名の属性を検出できませんでした。詳細設定で対応する属性を指定してください。", @@ -38,6 +56,8 @@ "Test Configuration" : "設定をテスト", "Help" : "ヘルプ", "Groups meeting these criteria are available in %s:" : "これらの基準を満たすグループが %s で利用可能:", + "Only these object classes:" : "このオブジェクトクラスからのみ:", + "Only from these groups:" : "これらのグループからのみ:", "Search groups" : "グループを検索", "Available groups" : "利用可能なグループ", "Selected groups" : "選択されたグループ", @@ -45,13 +65,20 @@ "LDAP Filter:" : "LDAP フィルタ:", "The filter specifies which LDAP groups shall have access to the %s instance." : "フィルターは、どの LDAP グループが %s にアクセスするかを指定します。", "Verify settings and count groups" : "設定を検証し、グループを数える", + "When logging in, %s will find the user based on the following attributes:" : "ログイン時に、%s により次の属性からユーザーを見つけます:", "LDAP / AD Username:" : "LDAP/ADユーザー名:", + "Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected." : "LDAP / AD ユーザー名に対してログインが許可されています。uid か、samaccountname のどちらかが検出されました。", "LDAP / AD Email Address:" : "LDAP / AD メールアドレス:", + "Allows login against an email attribute. Mail and mailPrimaryAddress will be allowed." : "メール属性に対してログインが許可されています。メールや mailPrimaryAddress が許可されています。", "Other Attributes:" : "その他の属性:", "Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: \"uid=%%uid\"" : "ログイン実行時に適用するフィルターを定義します。%%uid にはログイン操作におけるユーザー名が入ります。例: \"uid=%%uid\"", + "Test Loginname" : "テスト用ログイン名", "Verify settings" : "設定のチェック", "1. Server" : "1. Server", "%s. Server:" : "%s. サーバー:", + "Add a new and blank configuration" : "空欄の新しい設定を追加", + "Copy current configuration into new directory binding" : "現在の設定を新しいディレクトリ設定にコピー", + "Delete the current configuration" : "現在の設定を削除", "Host" : "ホスト", "You can omit the protocol, except you require SSL. Then start with ldaps://" : "SSL通信しない場合には、プロトコル名を省略することができます。そうでない場合には、ldaps:// から始めてください。", "Port" : "ポート", @@ -67,8 +94,9 @@ "Avoids automatic LDAP requests. Better for bigger setups, but requires some LDAP knowledge." : "自動的なLDAP問合せを停止します。大規模な設定には適していますが、LDAPの知識が必要になります。", "Manually enter LDAP filters (recommended for large directories)" : "手動でLDAPフィルターを入力(大規模ディレクトリ時のみ推奨)", "Limit %s access to users meeting these criteria:" : "以下のフィルターに適合するユーザーのみ %s へアクセスを許可:", + "The most common object classes for users are organizationalPerson, person, user, and inetOrgPerson. If you are not sure which object class to select, please consult your directory admin." : "ユーザーの最も一般的なオブジェクトクラスは、organizationalPerson, person, user と inetOrgPerson です。もし、どのオブジェクトを選択すれば良いか分からない場合は、ディレクトリ管理者に相談してください。", "The filter specifies which LDAP users shall have access to the %s instance." : "フィルターは、どのLDAPユーザーが %s にアクセスするかを指定します。", - "Verify settings and count users" : "設定を検証し、ユーザを数える", + "Verify settings and count users" : "設定を検証し、ユーザーを数える", "Saving" : "保存中", "Back" : "戻る", "Continue" : "続ける", @@ -121,6 +149,7 @@ "UUID Attribute for Users:" : "ユーザーのUUID属性:", "UUID Attribute for Groups:" : "グループの UUID 属性:", "Username-LDAP User Mapping" : "ユーザー名とLDAPユーザのマッピング", + "Usernames are used to store and assign (meta) data. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage." : "ユーザー名は(メタ)データの保存と割り当てに使用されます。ユーザーを正確に識別して認識するために、個々のLDAPユーザは内部ユーザ名を持っています。これは、ユーザー名からLDAPユーザーへのマッピングが必要であることを意味しています。この生成されたユーザ名は、LDAPユーザのUUIDにマッピングされます。加えて、DNがLDAPとのインタラクションを削減するためにキャッシュされますが、識別には利用されません。DNが変わった場合は、変更が検出されます。内部ユーザ名は全体に亘って利用されます。マッピングをクリアすると、いたるところに使われないままの物が残るでしょう。マッピングのクリアは設定に敏感ではありませんが、すべてのLDAPの設定に影響を与えます!本番の環境では決してマッピングをクリアしないでください。テストもしくは実験の段階でのみマッピングのクリアを行なってください。", "Clear Username-LDAP User Mapping" : "ユーザー名とLDAPユーザーのマッピングをクリアする", "Clear Groupname-LDAP Group Mapping" : "グループ名とLDAPグループのマッピングをクリアする" },"pluralForm" :"nplurals=1; plural=0;" diff --git a/apps/user_ldap/l10n/ko.js b/apps/user_ldap/l10n/ko.js index 566b67bf98e..6a14df58855 100644 --- a/apps/user_ldap/l10n/ko.js +++ b/apps/user_ldap/l10n/ko.js @@ -5,7 +5,7 @@ OC.L10N.register( "Failed to delete the server configuration" : "서버 설정을 삭제할 수 없습니다.", "The configuration is invalid: anonymous bind is not allowed." : "설정이 잘못되었습니다: 익명 연결은 허용되지 않습니다.", "The configuration is valid and the connection could be established!" : "설정 정보가 올바르고 연결할 수 있습니다!", - "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "설정 정보가 올바르지만 바인딩이 실패하였습니다. 서버 설정과 인증 정보를 확인하십시오.", + "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "설정 정보가 올바르지만 바인딩이 실패했습니다. 서버 설정과 인증 정보를 확인하십시오.", "The configuration is invalid. Please have a look at the logs for further details." : "설정이 올바르지 않습니다. 자세한 사항은 로그를 참고하십시오.", "No action specified" : "동작이 지정되지 않음", "No configuration specified" : "설정이 지정되지 않음", @@ -26,11 +26,11 @@ OC.L10N.register( "No object found in the given Base DN. Please revise." : "입력한 기본 DN에서 객체를 찾을 수 없습니다. 다시 입력하십시오.", "More than 1.000 directory entries available." : "디렉터리 항목이 1,000개 이상 존재합니다.", " entries available within the provided Base DN" : "개(지정한 DN의 기본 항목 수)", - "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "오류가 발생하였습니다. 기본 DN, 연결 설정, 인증 정보를 확인하십시오.", + "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "오류가 발생했습니다. 기본 DN, 연결 설정, 인증 정보를 확인하십시오.", "Do you really want to delete the current Server Configuration?" : "현재 서버 설정을 지우시겠습니까?", "Confirm Deletion" : "삭제 확인", - "Mappings cleared successfully!" : "매핑을 삭제하였습니다!", - "Error while clearing the mappings." : "매핑을 삭제하는 중 오류가 발생하였습니다.", + "Mappings cleared successfully!" : "매핑을 삭제했습니다!", + "Error while clearing the mappings." : "매핑을 삭제하는 중 오류가 발생했습니다.", "Anonymous bind is not allowed. Please provide a User DN and Password." : "익명 연결은 허용되지 않습니다. 사용자 DN과 암호를 입력하십시오.", "LDAP Operations error. Anonymous bind might not be allowed." : "LDAP 작업 오류입니다. 익명 연결이 비활성화 되었을 수 있습니다.", "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "저장할 수 없습니다. 데이터베이스 상태를 확인하십시오. 계속하기 전에 새로 고치십시오.", @@ -38,11 +38,11 @@ OC.L10N.register( "Mode switch" : "모드 전환", "Select attributes" : "속성 선택", "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "사용자를 찾을 수 없습니다. 로그인 속성과 사용자 이름을 확인하십시오. 적용되는 필터(명령행에 붙여넣어 검증 가능):", - "User found and settings verified." : "사용자를 찾았고 설정을 확인하였습니다.", + "User found and settings verified." : "사용자를 찾았고 설정을 확인했습니다.", "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "설정을 확인하였으나 한 명의 사용자만 찾았습니다. 첫 사용자만 로그인할 수 있습니다. 더 좁은 필터를 지정하십시오.", - "An unspecified error occurred. Please check the settings and the log." : "알 수 없는 오류가 발생하였습니다. 설정과 로그를 확인하십시오.", + "An unspecified error occurred. Please check the settings and the log." : "알 수 없는 오류가 발생했습니다. 설정과 로그를 확인하십시오.", "The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "검색 필터가 잘못되었습니다. 열고 닫는 괄호 쌍이 맞지 않을 수도 있습니다. 확인 후 수정하십시오.", - "A connection error to LDAP / AD occurred, please check host, port and credentials." : "LDAP/AD 연결 오류가 발생하였습니다. 호스트, 포트, 인증 정보를 확인하십시오.", + "A connection error to LDAP / AD occurred, please check host, port and credentials." : "LDAP/AD 연결 오류가 발생했습니다. 호스트, 포트, 인증 정보를 확인하십시오.", "The %uid placeholder is missing. It will be replaced with the login name when querying LDAP / AD." : "%uid 자리 비움자가 존재하지 않습니다. LDAP/AD에 조회할 때 로그인 사용자 이름으로 대체됩니다.", "Please provide a login name to test against" : "테스트할 로그인 사용자 이름을 입력하십시오", "The group box was disabled, because the LDAP / AD server does not support memberOf." : "LDAP/AD 서버에서 memberOf를 지원하지 않아서 그룹 상자를 비활성화합니다.", diff --git a/apps/user_ldap/l10n/ko.json b/apps/user_ldap/l10n/ko.json index b8f649bc24e..40fd85ca809 100644 --- a/apps/user_ldap/l10n/ko.json +++ b/apps/user_ldap/l10n/ko.json @@ -3,7 +3,7 @@ "Failed to delete the server configuration" : "서버 설정을 삭제할 수 없습니다.", "The configuration is invalid: anonymous bind is not allowed." : "설정이 잘못되었습니다: 익명 연결은 허용되지 않습니다.", "The configuration is valid and the connection could be established!" : "설정 정보가 올바르고 연결할 수 있습니다!", - "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "설정 정보가 올바르지만 바인딩이 실패하였습니다. 서버 설정과 인증 정보를 확인하십시오.", + "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "설정 정보가 올바르지만 바인딩이 실패했습니다. 서버 설정과 인증 정보를 확인하십시오.", "The configuration is invalid. Please have a look at the logs for further details." : "설정이 올바르지 않습니다. 자세한 사항은 로그를 참고하십시오.", "No action specified" : "동작이 지정되지 않음", "No configuration specified" : "설정이 지정되지 않음", @@ -24,11 +24,11 @@ "No object found in the given Base DN. Please revise." : "입력한 기본 DN에서 객체를 찾을 수 없습니다. 다시 입력하십시오.", "More than 1.000 directory entries available." : "디렉터리 항목이 1,000개 이상 존재합니다.", " entries available within the provided Base DN" : "개(지정한 DN의 기본 항목 수)", - "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "오류가 발생하였습니다. 기본 DN, 연결 설정, 인증 정보를 확인하십시오.", + "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "오류가 발생했습니다. 기본 DN, 연결 설정, 인증 정보를 확인하십시오.", "Do you really want to delete the current Server Configuration?" : "현재 서버 설정을 지우시겠습니까?", "Confirm Deletion" : "삭제 확인", - "Mappings cleared successfully!" : "매핑을 삭제하였습니다!", - "Error while clearing the mappings." : "매핑을 삭제하는 중 오류가 발생하였습니다.", + "Mappings cleared successfully!" : "매핑을 삭제했습니다!", + "Error while clearing the mappings." : "매핑을 삭제하는 중 오류가 발생했습니다.", "Anonymous bind is not allowed. Please provide a User DN and Password." : "익명 연결은 허용되지 않습니다. 사용자 DN과 암호를 입력하십시오.", "LDAP Operations error. Anonymous bind might not be allowed." : "LDAP 작업 오류입니다. 익명 연결이 비활성화 되었을 수 있습니다.", "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "저장할 수 없습니다. 데이터베이스 상태를 확인하십시오. 계속하기 전에 새로 고치십시오.", @@ -36,11 +36,11 @@ "Mode switch" : "모드 전환", "Select attributes" : "속성 선택", "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "사용자를 찾을 수 없습니다. 로그인 속성과 사용자 이름을 확인하십시오. 적용되는 필터(명령행에 붙여넣어 검증 가능):", - "User found and settings verified." : "사용자를 찾았고 설정을 확인하였습니다.", + "User found and settings verified." : "사용자를 찾았고 설정을 확인했습니다.", "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "설정을 확인하였으나 한 명의 사용자만 찾았습니다. 첫 사용자만 로그인할 수 있습니다. 더 좁은 필터를 지정하십시오.", - "An unspecified error occurred. Please check the settings and the log." : "알 수 없는 오류가 발생하였습니다. 설정과 로그를 확인하십시오.", + "An unspecified error occurred. Please check the settings and the log." : "알 수 없는 오류가 발생했습니다. 설정과 로그를 확인하십시오.", "The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "검색 필터가 잘못되었습니다. 열고 닫는 괄호 쌍이 맞지 않을 수도 있습니다. 확인 후 수정하십시오.", - "A connection error to LDAP / AD occurred, please check host, port and credentials." : "LDAP/AD 연결 오류가 발생하였습니다. 호스트, 포트, 인증 정보를 확인하십시오.", + "A connection error to LDAP / AD occurred, please check host, port and credentials." : "LDAP/AD 연결 오류가 발생했습니다. 호스트, 포트, 인증 정보를 확인하십시오.", "The %uid placeholder is missing. It will be replaced with the login name when querying LDAP / AD." : "%uid 자리 비움자가 존재하지 않습니다. LDAP/AD에 조회할 때 로그인 사용자 이름으로 대체됩니다.", "Please provide a login name to test against" : "테스트할 로그인 사용자 이름을 입력하십시오", "The group box was disabled, because the LDAP / AD server does not support memberOf." : "LDAP/AD 서버에서 memberOf를 지원하지 않아서 그룹 상자를 비활성화합니다.", diff --git a/apps/user_ldap/l10n/sq.js b/apps/user_ldap/l10n/sq.js index 48ec0616b0e..0c46ff72a98 100644 --- a/apps/user_ldap/l10n/sq.js +++ b/apps/user_ldap/l10n/sq.js @@ -1,61 +1,156 @@ OC.L10N.register( "user_ldap", { - "Failed to clear the mappings." : "dështoi së pastruari planifikimet", - "Failed to delete the server configuration" : "dështoi fshirjen e konfigurimit të serverit", - "The configuration is valid and the connection could be established!" : "Konfigurimi është i vlefshem dhe lidhja mund të kryhet", - "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "Konfigurimi është i saktë por lidhja dështoi. Kontrolloni konfigurimete serverit dhe kredencialet.", - "Do you really want to delete the current Server Configuration?" : "Jeni vërtetë të sigurt të fshini konfigurimet aktuale të serverit?", - "Confirm Deletion" : "Konfirmoni Fshirjen", - "Users" : "Përdoruesit", - "Groups" : "Grupet", + "Failed to clear the mappings." : "Dështoi në heqjen e përshoqërimeve.", + "Failed to delete the server configuration" : "Dështoi në fshirjen e formësimit të shërbyesit", + "The configuration is invalid: anonymous bind is not allowed." : "Formësimi është i pavlefshëm: nuk lejohen bind-e anonimë.", + "The configuration is valid and the connection could be established!" : "Formësimi është i vlefshëm dhe lidhja mund të vendoset!", + "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "Formësimi është i vlefshëm, por Bind-i dështoi. Ju lutemi, kontrolloni rregullimet dhe kredencialet e shërbyesit.", + "The configuration is invalid. Please have a look at the logs for further details." : "Formësimi është i pavlefshëm. Ju lutemi, për hollësi të mëtejshme, hidhuni një sy regjistrave.", + "No action specified" : "S’është treguar veprim", + "No configuration specified" : "S’u dha formësim", + "No data specified" : "S’u treguan të dhëna", + " Could not set configuration %s" : "S’vuri dot në punë formësimin %s", + "Action does not exist" : "Veprimi s’ekziston", + "The Base DN appears to be wrong" : "DN-ja Bazë duket se është e gabuar", + "Configuration incorrect" : "Formësim i pasaktë", + "Configuration incomplete" : "Formësim jo i plotë", + "Configuration OK" : "Formësimi OK", + "Select groups" : "Përzgjidhni grupe", + "Select object classes" : "Përzgjidhni klasa objektesh", + "Please check the credentials, they seem to be wrong." : "Ju lutemi, kontrolloni kredencialet, duket se janë gabim.", + "Please specify the port, it could not be auto-detected." : "Ju lutemi, përcaktoni portën, s’u arrit të vetëzbulohet.", + "Base DN could not be auto-detected, please revise credentials, host and port." : "DN-ja Bazë s’u vetëzbulua dot, ju lutemi, rishikoni kredencialet, strehën dhe portën.", + "Could not detect Base DN, please enter it manually." : "S’u zbulua dot DN Bazë, ju lutemi, jepeni dorazi.", + "{nthServer}. Server" : "{nthServer}. Shërbyes", + "No object found in the given Base DN. Please revise." : "Në DN Bazë të dhën s’u gjet objekt. Ju lutemi, rishikojeni.", + "More than 1.000 directory entries available." : "Më tepër se 1.000 zëra drejtorie gati.", + " entries available within the provided Base DN" : " zëra të gatshëm brenda DN-së Bazë të dhënë", + "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "Ndodhi një gabim. Ju lutemi, kontrolloni DN-në Bazë, sie dhe rregullimet për lidhjen dhe kredencialet.", + "Do you really want to delete the current Server Configuration?" : "Doni vërtet të fshihet Formësimi i tanishëm i Shërbyesit?", + "Confirm Deletion" : "Ripohoni Fshirjen", + "Mappings cleared successfully!" : "Përshoqërimet u hoqën me sukses!", + "Error while clearing the mappings." : "Gabim gjatë heqjes së përshoqërimeve.", + "Anonymous bind is not allowed. Please provide a User DN and Password." : "S’lejohet bind anonim. Ju lutemi, jepni një DN Përodruesi dhe Fjalëkalim.", + "LDAP Operations error. Anonymous bind might not be allowed." : "Gabim Veprimesh LDAP. Mund të mos lejohen bind-e anonimë.", + "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "Ruajta dështoi. Ju lutemi, sigurohuni që baza e të dhënave është në punë. Ringarkojemi përpara se të vazhdohet.", + "Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?" : "Këmbimi i mënyrë do të lejojë kërkesa LDAP automatike. Në varësi të madhësisë për LDAP-in tuaj, kjo mund të hajë ca kohë. Doni prapë të këmbehet mënyra?", + "Mode switch" : "Këmbim mënyre", + "Select attributes" : "Përzgjidhni atribute", + "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "S’u gjet përdorues. Ju lutemi, kontrolloni atributet tuaja për hyrje dhe emrin e përdoruesit. Filtër efektiv (për kopjim dhe hedhje në rresht urdhrash për vleftësim): <br/>", + "User found and settings verified." : "Përdoruesi u gjet dhe rregullimet u verifikuan.", + "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "Rregullimet u verifikuan, por u gjet një përdorues. Vetëm i pari do të jetë në gjendje të bëjë hyrje. Shihni mundësinë e një filtri më të ngushtë.", + "An unspecified error occurred. Please check the settings and the log." : "Ndodhi një gabim i papërcaktuar. Ju lutemi, kontrolloni rregullimet dhe regjistrin.", + "The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "Filtri i kërkimit është i pavlefshëm, ndoshta për shkak problemesh sintakse, të tillë si një numër jo i njëjtë kllpash të hapura dhe mbyllura. Ju lutemi, rishikojeni.", + "A connection error to LDAP / AD occurred, please check host, port and credentials." : "Ndodhi një gabim lidhje te LDAP / AD, ju lutemi, kontrolloni strehën, portën dhe kredencialet.", + "The %uid placeholder is missing. It will be replaced with the login name when querying LDAP / AD." : "Vendmbajtësja %uid mungon. Do të zëvendësohet me emrin e hyrjes, kur të kërkohet te LDAP / AD.", + "Please provide a login name to test against" : "Ju lutemi, jepni një emër hyrjesh që të ritestohet", + "The group box was disabled, because the LDAP / AD server does not support memberOf." : "Kutia e grupeve u çaktivizua, ngaqë shërbyesi LDAP / AD nuk mbulon memberOf.", + "_%s group found_::_%s groups found_" : ["U gjet %s grup","U gjetën %s grupe"], + "_%s user found_::_%s users found_" : ["U gjet %s përdorues","U gjetën %s përdorues"], + "Could not detect user display name attribute. Please specify it yourself in advanced ldap settings." : "S’u zbulua dot atribut emri përdoruesi në ekran. Ju lutemi, caktojeni ju vetë te rregullime e mëtejshme për LDAP.", + "Could not find the desired feature" : "S’u gjet dot veçoria e dëshiruar", + "Invalid Host" : "Strehë e Pavlefshme", + "Server" : "Shërbyes", + "Users" : "Përdorues", + "Login Attributes" : "Atribute Hyrjesh", + "Groups" : "Grupe", "Test Configuration" : "Provoni konfigurimet", "Help" : "Ndihmë", + "Groups meeting these criteria are available in %s:" : "Grupet që i plotësojnë këto kushte gjenden te %s:", + "Only these object classes:" : "Vetëm këto klasa objektesh:", + "Only from these groups:" : "Vetëm prej këtyre grupesh:", + "Search groups" : "Kërko në grupe", + "Available groups" : "Grupe të mundshëm", + "Selected groups" : "Grupe të përzgjedhur", + "Edit LDAP Query" : "Përpunoni Kërkesë LDAP", + "LDAP Filter:" : "Filtër LDAP:", + "The filter specifies which LDAP groups shall have access to the %s instance." : "Filtri përcakton se cilët grupe LDAP do të kenë hyrje te instanca %s.", + "Verify settings and count groups" : "Verifiko rregullimet dhe numëro grupet", + "When logging in, %s will find the user based on the following attributes:" : "Kur hyhet, %s do ta gjejë përdoruesin duke u bazuar në atributet vijues:", + "LDAP / AD Username:" : "Emër përdoruesi LDAP / AD:", + "Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected." : "Lejon hyrje kundrejt emrin të përdoruesit LDAP / AD, që është ose uid, ose samaccountname, dhe do të zbulohet.", + "LDAP / AD Email Address:" : "Adresë Email LDAP / AD:", + "Allows login against an email attribute. Mail and mailPrimaryAddress will be allowed." : "Lejon hyrje kundrejt një atributi email. Do të lejohen Mail dhe mailPrimaryAddress.", + "Other Attributes:" : "Atribute të Tjerë:", + "Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: \"uid=%%uid\"" : "Përcakton filtrin që duhet zbatuar kur përpiqet të bëhet hyrje. %%uid zëvendëson emrin e përdoruesi te veprimi i hyrjes. Shembull: \"uid=%%uid\"", + "Test Loginname" : "Testo Emër hyrjesh", + "Verify settings" : "Verifikoni rregullimet", + "1. Server" : "1. Shërbyes", + "%s. Server:" : "%s. Shërbyes:", + "Add a new and blank configuration" : "Shtoni një formësim të ri të zbrazët", + "Copy current configuration into new directory binding" : "Kopjojeni formësimin e tanishëm te një lidhmë e re drejtorie", + "Delete the current configuration" : "Fshije formësimin e tanishëm", "Host" : "Pritësi", - "You can omit the protocol, except you require SSL. Then start with ldaps://" : "Ju mund të mos vendosni protokollin ,vetëm nëse ju nevojitet SSL. atherë filloni me ldaps://", - "Port" : "Porta", - "User DN" : "Përdoruesi DN", - "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "DN -ja e klientit për përdoruesin që kërkon të lidhet duhet të jetë si psh,uid=agent,dc=example,dc=com. Për lidhjet anonime lini boshe hapsirat e DN dhe fjalëkalim ", - "Password" : "fjalëkalim", - "For anonymous access, leave DN and Password empty." : "Për tu lidhur në mënyre anonime, lini bosh hapsirat e DN dhe fjalëkalim", - "One Base DN per line" : "Një baze DN për rrjesht", - "You can specify Base DN for users and groups in the Advanced tab" : "Ju mund të specifikoni Bazen DN për përdorues dhe grupe në butonin 'Të Përparuara'", + "You can omit the protocol, except you require SSL. Then start with ldaps://" : "Protokollin mund ta lini pa vënë, hiq rastin kur ju duhet SSL. Në atë rast filloni me ldaps://", + "Port" : "Portë", + "Detect Port" : "Zbulo Portë", + "User DN" : "DN Përdoruesi", + "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "DN -ja e klientit të përdoruesit për të cilin duhet kryer bind, p.sh,uid=agent,dc=example,dc=com. Për hyrje anonime, DN-në dhe Fjalëkalimin lëreni të zbrazët.", + "Password" : "Fjalëkalim", + "For anonymous access, leave DN and Password empty." : "Për hyrje anonime, lini të zbrazët DN-në dhe Fjalëkalimim.", + "One Base DN per line" : "Një DN Bazë për rresht", + "You can specify Base DN for users and groups in the Advanced tab" : "DN Bazë për përdorues dhe grupe mund të përcaktoni që nga skeda Të mëtejshme", + "Detect Base DN" : "Zbulo DN Bazë", + "Test Base DN" : "Testo DN Bazë", + "Avoids automatic LDAP requests. Better for bigger setups, but requires some LDAP knowledge." : "Shmang kërkesa LDAP automatike. Më e përshtatshme për instalime më të mëdha, por lyp ca dije rreth LDAP-it.", + "Manually enter LDAP filters (recommended for large directories)" : "Jepni filtra LDAP dorazi (e këshilluar për drejtori të mëdha)", + "Limit %s access to users meeting these criteria:" : "Kufizo hyrje %s vetëm për përdorues që plotësojnë këtë kusht:", + "The most common object classes for users are organizationalPerson, person, user, and inetOrgPerson. If you are not sure which object class to select, please consult your directory admin." : "Klasat më të rëndomta objektesht për përdoruesit janë organizationalPerson, person, user, dhe inetOrgPerson. Nëse s’jeni i sigurt cilën klasë objekti të përzgjidhniI, ju lutemi, lidhuni me përgjegjësin e drejtorisë suaj.", + "The filter specifies which LDAP users shall have access to the %s instance." : "Filtri përcakton se cilët përdorues LDAP do të kenë hyrje te instanca %s.", + "Verify settings and count users" : "Verifiko rregullimet dhe numëro përdoruesit", + "Saving" : "Po ruhet", + "Back" : "Mbrapsht", "Continue" : "Vazhdo", - "Advanced" : "E përparuar", - "<b>Warning:</b> The PHP LDAP module is not installed, the backend will not work. Please ask your system administrator to install it." : "<b>Njoftim:</b> moduli PHP LDAP nuk është instaluar, motori nuk do të funksionojë.Kontaktoni me administratorin e sistemit.", - "Connection Settings" : "Të dhënat e lidhjes", - "Configuration Active" : "Konfigurimi Aktiv", - "When unchecked, this configuration will be skipped." : "Nëse nuk është i zgjedhur, ky konfigurim do të anashkalohet.", - "Backup (Replica) Host" : "Pritësi rezervë (Replika)", - "Give an optional backup host. It must be a replica of the main LDAP/AD server." : "Jepni një pritës rezervë. Duhet të jetë replikimi i serverit AD/LDAP kryesor.", - "Backup (Replica) Port" : "Porta rezervë (Replika)", - "Disable Main Server" : "Ç'aktivizoni serverin kryesor", - "Turn off SSL certificate validation." : "Ç'aktivizoni kontrollin e certifikatës SSL.", + "LDAP" : "LDAP", + "Expert" : "Ekspert", + "Advanced" : "Të mëtejshme", + "<b>Warning:</b> Apps user_ldap and user_webdavauth are incompatible. You may experience unexpected behavior. Please ask your system administrator to disable one of them." : "<b>Kujdes:</b> user_ldap dhe user_webdavauth të aplikacionit janë të papërputhshëm. Mund t’ju ndodhin sjellje të papritura. Ju lutemi, kërkojini përgjegjësit të sistemit tuaj të çaktivizojë një prej tyre.", + "<b>Warning:</b> The PHP LDAP module is not installed, the backend will not work. Please ask your system administrator to install it." : "<b>Kujdes:</b> Moduli PHP LDAP s’është i instaluar, pjesa përkatëse në shërbyes nuk do të funksionojë. Ju lutemi, kërkojini përgjegjësit të sistemit tuaj ta instalojë.", + "Connection Settings" : "Rregullime Lidhjeje", + "Configuration Active" : "Formësimi Aktiv", + "When unchecked, this configuration will be skipped." : "Po u la pa shenjë, ky formësim do të anashkalohet.", + "Backup (Replica) Host" : "Strehë Kopjeruajtjeje (Replica)", + "Give an optional backup host. It must be a replica of the main LDAP/AD server." : "Jepni një strehë opsionale kopjeruajtjesh. Duhet të jetë një kopje identike e shërbyesit kryesor LDAP/AD.", + "Backup (Replica) Port" : "Portë Kopjeruajtjeje (Replica)", + "Disable Main Server" : "Çaktivizoni Shërbyesin Kryesor", + "Only connect to the replica server." : "Lidhu vetëm te shërbyesi kopje.", + "Turn off SSL certificate validation." : "Çaktivizoni vleftësim dëshmish SSL.", + "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server." : "E pakëshillueshme, përdoreni vetëm për testim! Nëse lidhja funksionon vetëm me këtë mundësi, importoni te shërbyesi juaj %s dëshminë SSL të shërbyesit LDAP.", "Cache Time-To-Live" : "Cache Time-To-Live", - "in seconds. A change empties the cache." : "në sekonda Ndryshimi boshatis 'cache'-n.", - "Directory Settings" : "Konfigurimet e Dosjeve", - "User Display Name Field" : "Hapsira e Emrit të Përdoruesit", + "in seconds. A change empties the cache." : "në sekonda. Ndryshimi e zbraz fshehtinën.", + "Directory Settings" : "Rregullime Drejtorie", + "User Display Name Field" : "Fushë Emri Përdoruesi Në Ekran", + "The LDAP attribute to use to generate the user's display name." : "Atribut LDAP që përdoret për të prodhuar emër ekrani për përdoruesin.", "Base User Tree" : "Struktura bazë e përdoruesit", - "One User Base DN per line" : "Një përdorues baze DN për rrjesht", + "One User Base DN per line" : "Një DN Bazë Përdoruesi për rresht", "User Search Attributes" : "Atributet e kërkimit të përdoruesëve", - "Optional; one attribute per line" : "Opsionale; një atribut për rrjesht", - "Group Display Name Field" : "Hapsira e Emrit të Grupit", + "Optional; one attribute per line" : "Opsionale; një atribut për rresht", + "Group Display Name Field" : "Fushë Emri Grupi Në Ekran", + "The LDAP attribute to use to generate the groups's display name." : "Atribut LDAP që përdoret për të prodhuar emër ekrani për grupin.", "Base Group Tree" : "Struktura bazë e grupit", - "One Group Base DN per line" : "Një grup baze DN për rrjesht", - "Group Search Attributes" : "Atributet e kërkimit të grupit", + "One Group Base DN per line" : "Një DN Bazë Grupi për rresht", + "Group Search Attributes" : "Atribute Kërkimi Grupi", "Group-Member association" : "Pjestar Grup-Përdorues ", - "Special Attributes" : "Atribute të veçanta", - "Quota Field" : "Hapsira e Kuotës", - "Quota Default" : "Kuota e paracaktuar", - "in bytes" : "në byte", - "Email Field" : "Hapsira e Postës Elektronike", - "User Home Folder Naming Rule" : "Rregulli i emërimit të dosjes së përdoruesit", + "Nested Groups" : "Grupe Brenda Njëri-Tjetrit", + "When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" : "Kur aktivizohet, grupet që përmbajnë grupe mbulohen. (Funksionon vetëm nëse atributi për anëtar grupi përmban DN-ra.)", + "Special Attributes" : "Atribute Speciale", + "Quota Field" : "Fushë Kuotash", + "Quota Default" : "Parazgjedhje Kuotash", + "in bytes" : "në bajte", + "Email Field" : "Fushë Email-i", + "User Home Folder Naming Rule" : "Rregull Emërtimi Dosjeje Kreu të Përdoruesit", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." : "Lëreni bosh për emrin e përdoruesit (I Paracaktuar). Ose, përcaktoni një atribut LDAP/AD.", - "Internal Username" : "Emër i brëndshëm i përdoruesit", - "Internal Username Attribute:" : "Atributet e emrit të përdoruesit të brëndshëm", - "Override UUID detection" : "Mbivendosni gjetjen e UUID", - "Username-LDAP User Mapping" : "Emri përdoruesit-LAPD përcaktues përdoruesi", - "Clear Username-LDAP User Mapping" : "Fshini Emër përdoruesi-LAPD Përcaktues përdoruesi", - "Clear Groupname-LDAP Group Mapping" : "Fshini Emër Grupi-LADP Përcaktues grupi" + "Internal Username" : "Emër i Brendshëm Përdoruesi", + "By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [ a-zA-Z0-9_.@- ]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all *DAV services. With this setting, the default behavior can be overridden. To achieve a similar behavior as before ownCloud 5 enter the user display name attribute in the following field. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users." : "Si parazgjedhje, emri i brendshëm i përdoruesit do të krijohet prej atributit UUID. Kjo siguron që emri i përdoruesit të jetë unik dhe shenjat që e përbëjnë s’do të jetë e nevojshme të shndërrohen. Emri i brendshëm i përdoruesi ka kufizimin që për të lejohen vetëm këto shenja: [ a-zA-Z0-9_.@- ]. Shenjat e tjera zëvendësohen me shenjën përgjegjëse në ASCII ose thjesht lihen pa u vënë. Kur ka përplasje, do të shtohet/rritet një numër. Emri i brendshëm i përdoruesit përdoret për ta identifikuar një përdorues së brendshmi. Është gjithashtu emri parazgjedhje për dosjen Home të përdoruesit. Është gjithashtu pjesë e URL-ve të largëta, për shembull, për krejt shërbimet *DAV. Me këtë rregullim mund të anashkalohet sjellja parazgjedhje. Që të arrini një sjellje të ngjashme si para ownCloud 5, jepni atributin emër përdoruesi në ekran te fusha vijuese. Lëreni të zbrazët, që të ruhet sjellja parazgjedhje. Ndryshimet do të kenë efekt vetëm mbi përdorues LDAP të përshoqëruar (shtuar) rishtas.", + "Internal Username Attribute:" : "Atribut Emër i Brendshëm Përdoruesi:", + "Override UUID detection" : "Anashkalo zbullim UUID-je", + "By default, the UUID attribute is automatically detected. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users and groups." : "Si parazgjedhje, atributi UUID zbulohet automatikisht. Atributi UUID përdoret për të identifikuar pa dyshime përdorues dhe grupe LDAP. Gjithashtu, emri i brendshëm i përdoruesi do të krijohet mbi bazën e UUID-së, në mos u përcaktoftë ndryshe më sipër. Mund ta anashkaloni rregullimin dhe të jepni një atribut tuajin sipas dëshirës. Duhet të siguroni që atributi sipas dëshirës të mund të jepet si për përdorues, ashtu edhe për grupe, dhe se është unik. Lëreni të zbrazët që të ruhet sjellja parazgjedhje. Ndryshimet do të kenë efekt vetëm etëm mbi përdorues LDAP të përshoqëruar (shtuar) rishtas.", + "UUID Attribute for Users:" : "Atribut UUID për Përdorues:", + "UUID Attribute for Groups:" : "Atribut UUID për Grupe:", + "Username-LDAP User Mapping" : "Përshoqërim Emër përdoruesi-Përdorues LDAP", + "Usernames are used to store and assign (meta) data. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage." : "Emrat e përdoruesve përdoren për të depozituar dhe shpërndarë (tej) të dhëna. Që të mund të identifikohen dhe pranohen saktësisht përdoruesit, çdo përdorues LDAP do të ketë një emër të brendshëm përdoruesi. Kjo kërkon përshoqërim nga emër përdoruesi te përdorues LDAP. Emri i përdoruesit i krijuar i përshoqërohet UUID-së së përdoruesit LDAP. Tej kësaj, edhe DN-ja ruhet në fshehtinë, për të zvogëluar ndërveprim LDAP, por s’përdoret për identifikim. Nëse ndryshon DN-ja, ndryshimet do të gjenden. Emri i brendshëm i përdoruesi përdoret gjithandej. Heqja e përshoqërimeve do të lërë thërrime ngado. Heqja e përshoqërimeve nuk preket nga formësimi, prek krejt formësimet për LDAP-në! Mos i hiqni kurrë përshoqërimet në një mjedis prodhimi, vetëm në një faqë testimi ose eksperimetale.", + "Clear Username-LDAP User Mapping" : "Pastro Përshoqërimin Emër përdoruesi-Përdorues LDAP", + "Clear Groupname-LDAP Group Mapping" : "Pastro Përshoqërimin Emër grupi-Grup LDAP" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/user_ldap/l10n/sq.json b/apps/user_ldap/l10n/sq.json index 595aee41fad..246adb316c2 100644 --- a/apps/user_ldap/l10n/sq.json +++ b/apps/user_ldap/l10n/sq.json @@ -1,59 +1,154 @@ { "translations": { - "Failed to clear the mappings." : "dështoi së pastruari planifikimet", - "Failed to delete the server configuration" : "dështoi fshirjen e konfigurimit të serverit", - "The configuration is valid and the connection could be established!" : "Konfigurimi është i vlefshem dhe lidhja mund të kryhet", - "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "Konfigurimi është i saktë por lidhja dështoi. Kontrolloni konfigurimete serverit dhe kredencialet.", - "Do you really want to delete the current Server Configuration?" : "Jeni vërtetë të sigurt të fshini konfigurimet aktuale të serverit?", - "Confirm Deletion" : "Konfirmoni Fshirjen", - "Users" : "Përdoruesit", - "Groups" : "Grupet", + "Failed to clear the mappings." : "Dështoi në heqjen e përshoqërimeve.", + "Failed to delete the server configuration" : "Dështoi në fshirjen e formësimit të shërbyesit", + "The configuration is invalid: anonymous bind is not allowed." : "Formësimi është i pavlefshëm: nuk lejohen bind-e anonimë.", + "The configuration is valid and the connection could be established!" : "Formësimi është i vlefshëm dhe lidhja mund të vendoset!", + "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "Formësimi është i vlefshëm, por Bind-i dështoi. Ju lutemi, kontrolloni rregullimet dhe kredencialet e shërbyesit.", + "The configuration is invalid. Please have a look at the logs for further details." : "Formësimi është i pavlefshëm. Ju lutemi, për hollësi të mëtejshme, hidhuni një sy regjistrave.", + "No action specified" : "S’është treguar veprim", + "No configuration specified" : "S’u dha formësim", + "No data specified" : "S’u treguan të dhëna", + " Could not set configuration %s" : "S’vuri dot në punë formësimin %s", + "Action does not exist" : "Veprimi s’ekziston", + "The Base DN appears to be wrong" : "DN-ja Bazë duket se është e gabuar", + "Configuration incorrect" : "Formësim i pasaktë", + "Configuration incomplete" : "Formësim jo i plotë", + "Configuration OK" : "Formësimi OK", + "Select groups" : "Përzgjidhni grupe", + "Select object classes" : "Përzgjidhni klasa objektesh", + "Please check the credentials, they seem to be wrong." : "Ju lutemi, kontrolloni kredencialet, duket se janë gabim.", + "Please specify the port, it could not be auto-detected." : "Ju lutemi, përcaktoni portën, s’u arrit të vetëzbulohet.", + "Base DN could not be auto-detected, please revise credentials, host and port." : "DN-ja Bazë s’u vetëzbulua dot, ju lutemi, rishikoni kredencialet, strehën dhe portën.", + "Could not detect Base DN, please enter it manually." : "S’u zbulua dot DN Bazë, ju lutemi, jepeni dorazi.", + "{nthServer}. Server" : "{nthServer}. Shërbyes", + "No object found in the given Base DN. Please revise." : "Në DN Bazë të dhën s’u gjet objekt. Ju lutemi, rishikojeni.", + "More than 1.000 directory entries available." : "Më tepër se 1.000 zëra drejtorie gati.", + " entries available within the provided Base DN" : " zëra të gatshëm brenda DN-së Bazë të dhënë", + "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "Ndodhi një gabim. Ju lutemi, kontrolloni DN-në Bazë, sie dhe rregullimet për lidhjen dhe kredencialet.", + "Do you really want to delete the current Server Configuration?" : "Doni vërtet të fshihet Formësimi i tanishëm i Shërbyesit?", + "Confirm Deletion" : "Ripohoni Fshirjen", + "Mappings cleared successfully!" : "Përshoqërimet u hoqën me sukses!", + "Error while clearing the mappings." : "Gabim gjatë heqjes së përshoqërimeve.", + "Anonymous bind is not allowed. Please provide a User DN and Password." : "S’lejohet bind anonim. Ju lutemi, jepni një DN Përodruesi dhe Fjalëkalim.", + "LDAP Operations error. Anonymous bind might not be allowed." : "Gabim Veprimesh LDAP. Mund të mos lejohen bind-e anonimë.", + "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "Ruajta dështoi. Ju lutemi, sigurohuni që baza e të dhënave është në punë. Ringarkojemi përpara se të vazhdohet.", + "Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?" : "Këmbimi i mënyrë do të lejojë kërkesa LDAP automatike. Në varësi të madhësisë për LDAP-in tuaj, kjo mund të hajë ca kohë. Doni prapë të këmbehet mënyra?", + "Mode switch" : "Këmbim mënyre", + "Select attributes" : "Përzgjidhni atribute", + "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "S’u gjet përdorues. Ju lutemi, kontrolloni atributet tuaja për hyrje dhe emrin e përdoruesit. Filtër efektiv (për kopjim dhe hedhje në rresht urdhrash për vleftësim): <br/>", + "User found and settings verified." : "Përdoruesi u gjet dhe rregullimet u verifikuan.", + "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "Rregullimet u verifikuan, por u gjet një përdorues. Vetëm i pari do të jetë në gjendje të bëjë hyrje. Shihni mundësinë e një filtri më të ngushtë.", + "An unspecified error occurred. Please check the settings and the log." : "Ndodhi një gabim i papërcaktuar. Ju lutemi, kontrolloni rregullimet dhe regjistrin.", + "The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "Filtri i kërkimit është i pavlefshëm, ndoshta për shkak problemesh sintakse, të tillë si një numër jo i njëjtë kllpash të hapura dhe mbyllura. Ju lutemi, rishikojeni.", + "A connection error to LDAP / AD occurred, please check host, port and credentials." : "Ndodhi një gabim lidhje te LDAP / AD, ju lutemi, kontrolloni strehën, portën dhe kredencialet.", + "The %uid placeholder is missing. It will be replaced with the login name when querying LDAP / AD." : "Vendmbajtësja %uid mungon. Do të zëvendësohet me emrin e hyrjes, kur të kërkohet te LDAP / AD.", + "Please provide a login name to test against" : "Ju lutemi, jepni një emër hyrjesh që të ritestohet", + "The group box was disabled, because the LDAP / AD server does not support memberOf." : "Kutia e grupeve u çaktivizua, ngaqë shërbyesi LDAP / AD nuk mbulon memberOf.", + "_%s group found_::_%s groups found_" : ["U gjet %s grup","U gjetën %s grupe"], + "_%s user found_::_%s users found_" : ["U gjet %s përdorues","U gjetën %s përdorues"], + "Could not detect user display name attribute. Please specify it yourself in advanced ldap settings." : "S’u zbulua dot atribut emri përdoruesi në ekran. Ju lutemi, caktojeni ju vetë te rregullime e mëtejshme për LDAP.", + "Could not find the desired feature" : "S’u gjet dot veçoria e dëshiruar", + "Invalid Host" : "Strehë e Pavlefshme", + "Server" : "Shërbyes", + "Users" : "Përdorues", + "Login Attributes" : "Atribute Hyrjesh", + "Groups" : "Grupe", "Test Configuration" : "Provoni konfigurimet", "Help" : "Ndihmë", + "Groups meeting these criteria are available in %s:" : "Grupet që i plotësojnë këto kushte gjenden te %s:", + "Only these object classes:" : "Vetëm këto klasa objektesh:", + "Only from these groups:" : "Vetëm prej këtyre grupesh:", + "Search groups" : "Kërko në grupe", + "Available groups" : "Grupe të mundshëm", + "Selected groups" : "Grupe të përzgjedhur", + "Edit LDAP Query" : "Përpunoni Kërkesë LDAP", + "LDAP Filter:" : "Filtër LDAP:", + "The filter specifies which LDAP groups shall have access to the %s instance." : "Filtri përcakton se cilët grupe LDAP do të kenë hyrje te instanca %s.", + "Verify settings and count groups" : "Verifiko rregullimet dhe numëro grupet", + "When logging in, %s will find the user based on the following attributes:" : "Kur hyhet, %s do ta gjejë përdoruesin duke u bazuar në atributet vijues:", + "LDAP / AD Username:" : "Emër përdoruesi LDAP / AD:", + "Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected." : "Lejon hyrje kundrejt emrin të përdoruesit LDAP / AD, që është ose uid, ose samaccountname, dhe do të zbulohet.", + "LDAP / AD Email Address:" : "Adresë Email LDAP / AD:", + "Allows login against an email attribute. Mail and mailPrimaryAddress will be allowed." : "Lejon hyrje kundrejt një atributi email. Do të lejohen Mail dhe mailPrimaryAddress.", + "Other Attributes:" : "Atribute të Tjerë:", + "Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: \"uid=%%uid\"" : "Përcakton filtrin që duhet zbatuar kur përpiqet të bëhet hyrje. %%uid zëvendëson emrin e përdoruesi te veprimi i hyrjes. Shembull: \"uid=%%uid\"", + "Test Loginname" : "Testo Emër hyrjesh", + "Verify settings" : "Verifikoni rregullimet", + "1. Server" : "1. Shërbyes", + "%s. Server:" : "%s. Shërbyes:", + "Add a new and blank configuration" : "Shtoni një formësim të ri të zbrazët", + "Copy current configuration into new directory binding" : "Kopjojeni formësimin e tanishëm te një lidhmë e re drejtorie", + "Delete the current configuration" : "Fshije formësimin e tanishëm", "Host" : "Pritësi", - "You can omit the protocol, except you require SSL. Then start with ldaps://" : "Ju mund të mos vendosni protokollin ,vetëm nëse ju nevojitet SSL. atherë filloni me ldaps://", - "Port" : "Porta", - "User DN" : "Përdoruesi DN", - "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "DN -ja e klientit për përdoruesin që kërkon të lidhet duhet të jetë si psh,uid=agent,dc=example,dc=com. Për lidhjet anonime lini boshe hapsirat e DN dhe fjalëkalim ", - "Password" : "fjalëkalim", - "For anonymous access, leave DN and Password empty." : "Për tu lidhur në mënyre anonime, lini bosh hapsirat e DN dhe fjalëkalim", - "One Base DN per line" : "Një baze DN për rrjesht", - "You can specify Base DN for users and groups in the Advanced tab" : "Ju mund të specifikoni Bazen DN për përdorues dhe grupe në butonin 'Të Përparuara'", + "You can omit the protocol, except you require SSL. Then start with ldaps://" : "Protokollin mund ta lini pa vënë, hiq rastin kur ju duhet SSL. Në atë rast filloni me ldaps://", + "Port" : "Portë", + "Detect Port" : "Zbulo Portë", + "User DN" : "DN Përdoruesi", + "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "DN -ja e klientit të përdoruesit për të cilin duhet kryer bind, p.sh,uid=agent,dc=example,dc=com. Për hyrje anonime, DN-në dhe Fjalëkalimin lëreni të zbrazët.", + "Password" : "Fjalëkalim", + "For anonymous access, leave DN and Password empty." : "Për hyrje anonime, lini të zbrazët DN-në dhe Fjalëkalimim.", + "One Base DN per line" : "Një DN Bazë për rresht", + "You can specify Base DN for users and groups in the Advanced tab" : "DN Bazë për përdorues dhe grupe mund të përcaktoni që nga skeda Të mëtejshme", + "Detect Base DN" : "Zbulo DN Bazë", + "Test Base DN" : "Testo DN Bazë", + "Avoids automatic LDAP requests. Better for bigger setups, but requires some LDAP knowledge." : "Shmang kërkesa LDAP automatike. Më e përshtatshme për instalime më të mëdha, por lyp ca dije rreth LDAP-it.", + "Manually enter LDAP filters (recommended for large directories)" : "Jepni filtra LDAP dorazi (e këshilluar për drejtori të mëdha)", + "Limit %s access to users meeting these criteria:" : "Kufizo hyrje %s vetëm për përdorues që plotësojnë këtë kusht:", + "The most common object classes for users are organizationalPerson, person, user, and inetOrgPerson. If you are not sure which object class to select, please consult your directory admin." : "Klasat më të rëndomta objektesht për përdoruesit janë organizationalPerson, person, user, dhe inetOrgPerson. Nëse s’jeni i sigurt cilën klasë objekti të përzgjidhniI, ju lutemi, lidhuni me përgjegjësin e drejtorisë suaj.", + "The filter specifies which LDAP users shall have access to the %s instance." : "Filtri përcakton se cilët përdorues LDAP do të kenë hyrje te instanca %s.", + "Verify settings and count users" : "Verifiko rregullimet dhe numëro përdoruesit", + "Saving" : "Po ruhet", + "Back" : "Mbrapsht", "Continue" : "Vazhdo", - "Advanced" : "E përparuar", - "<b>Warning:</b> The PHP LDAP module is not installed, the backend will not work. Please ask your system administrator to install it." : "<b>Njoftim:</b> moduli PHP LDAP nuk është instaluar, motori nuk do të funksionojë.Kontaktoni me administratorin e sistemit.", - "Connection Settings" : "Të dhënat e lidhjes", - "Configuration Active" : "Konfigurimi Aktiv", - "When unchecked, this configuration will be skipped." : "Nëse nuk është i zgjedhur, ky konfigurim do të anashkalohet.", - "Backup (Replica) Host" : "Pritësi rezervë (Replika)", - "Give an optional backup host. It must be a replica of the main LDAP/AD server." : "Jepni një pritës rezervë. Duhet të jetë replikimi i serverit AD/LDAP kryesor.", - "Backup (Replica) Port" : "Porta rezervë (Replika)", - "Disable Main Server" : "Ç'aktivizoni serverin kryesor", - "Turn off SSL certificate validation." : "Ç'aktivizoni kontrollin e certifikatës SSL.", + "LDAP" : "LDAP", + "Expert" : "Ekspert", + "Advanced" : "Të mëtejshme", + "<b>Warning:</b> Apps user_ldap and user_webdavauth are incompatible. You may experience unexpected behavior. Please ask your system administrator to disable one of them." : "<b>Kujdes:</b> user_ldap dhe user_webdavauth të aplikacionit janë të papërputhshëm. Mund t’ju ndodhin sjellje të papritura. Ju lutemi, kërkojini përgjegjësit të sistemit tuaj të çaktivizojë një prej tyre.", + "<b>Warning:</b> The PHP LDAP module is not installed, the backend will not work. Please ask your system administrator to install it." : "<b>Kujdes:</b> Moduli PHP LDAP s’është i instaluar, pjesa përkatëse në shërbyes nuk do të funksionojë. Ju lutemi, kërkojini përgjegjësit të sistemit tuaj ta instalojë.", + "Connection Settings" : "Rregullime Lidhjeje", + "Configuration Active" : "Formësimi Aktiv", + "When unchecked, this configuration will be skipped." : "Po u la pa shenjë, ky formësim do të anashkalohet.", + "Backup (Replica) Host" : "Strehë Kopjeruajtjeje (Replica)", + "Give an optional backup host. It must be a replica of the main LDAP/AD server." : "Jepni një strehë opsionale kopjeruajtjesh. Duhet të jetë një kopje identike e shërbyesit kryesor LDAP/AD.", + "Backup (Replica) Port" : "Portë Kopjeruajtjeje (Replica)", + "Disable Main Server" : "Çaktivizoni Shërbyesin Kryesor", + "Only connect to the replica server." : "Lidhu vetëm te shërbyesi kopje.", + "Turn off SSL certificate validation." : "Çaktivizoni vleftësim dëshmish SSL.", + "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server." : "E pakëshillueshme, përdoreni vetëm për testim! Nëse lidhja funksionon vetëm me këtë mundësi, importoni te shërbyesi juaj %s dëshminë SSL të shërbyesit LDAP.", "Cache Time-To-Live" : "Cache Time-To-Live", - "in seconds. A change empties the cache." : "në sekonda Ndryshimi boshatis 'cache'-n.", - "Directory Settings" : "Konfigurimet e Dosjeve", - "User Display Name Field" : "Hapsira e Emrit të Përdoruesit", + "in seconds. A change empties the cache." : "në sekonda. Ndryshimi e zbraz fshehtinën.", + "Directory Settings" : "Rregullime Drejtorie", + "User Display Name Field" : "Fushë Emri Përdoruesi Në Ekran", + "The LDAP attribute to use to generate the user's display name." : "Atribut LDAP që përdoret për të prodhuar emër ekrani për përdoruesin.", "Base User Tree" : "Struktura bazë e përdoruesit", - "One User Base DN per line" : "Një përdorues baze DN për rrjesht", + "One User Base DN per line" : "Një DN Bazë Përdoruesi për rresht", "User Search Attributes" : "Atributet e kërkimit të përdoruesëve", - "Optional; one attribute per line" : "Opsionale; një atribut për rrjesht", - "Group Display Name Field" : "Hapsira e Emrit të Grupit", + "Optional; one attribute per line" : "Opsionale; një atribut për rresht", + "Group Display Name Field" : "Fushë Emri Grupi Në Ekran", + "The LDAP attribute to use to generate the groups's display name." : "Atribut LDAP që përdoret për të prodhuar emër ekrani për grupin.", "Base Group Tree" : "Struktura bazë e grupit", - "One Group Base DN per line" : "Një grup baze DN për rrjesht", - "Group Search Attributes" : "Atributet e kërkimit të grupit", + "One Group Base DN per line" : "Një DN Bazë Grupi për rresht", + "Group Search Attributes" : "Atribute Kërkimi Grupi", "Group-Member association" : "Pjestar Grup-Përdorues ", - "Special Attributes" : "Atribute të veçanta", - "Quota Field" : "Hapsira e Kuotës", - "Quota Default" : "Kuota e paracaktuar", - "in bytes" : "në byte", - "Email Field" : "Hapsira e Postës Elektronike", - "User Home Folder Naming Rule" : "Rregulli i emërimit të dosjes së përdoruesit", + "Nested Groups" : "Grupe Brenda Njëri-Tjetrit", + "When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" : "Kur aktivizohet, grupet që përmbajnë grupe mbulohen. (Funksionon vetëm nëse atributi për anëtar grupi përmban DN-ra.)", + "Special Attributes" : "Atribute Speciale", + "Quota Field" : "Fushë Kuotash", + "Quota Default" : "Parazgjedhje Kuotash", + "in bytes" : "në bajte", + "Email Field" : "Fushë Email-i", + "User Home Folder Naming Rule" : "Rregull Emërtimi Dosjeje Kreu të Përdoruesit", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." : "Lëreni bosh për emrin e përdoruesit (I Paracaktuar). Ose, përcaktoni një atribut LDAP/AD.", - "Internal Username" : "Emër i brëndshëm i përdoruesit", - "Internal Username Attribute:" : "Atributet e emrit të përdoruesit të brëndshëm", - "Override UUID detection" : "Mbivendosni gjetjen e UUID", - "Username-LDAP User Mapping" : "Emri përdoruesit-LAPD përcaktues përdoruesi", - "Clear Username-LDAP User Mapping" : "Fshini Emër përdoruesi-LAPD Përcaktues përdoruesi", - "Clear Groupname-LDAP Group Mapping" : "Fshini Emër Grupi-LADP Përcaktues grupi" + "Internal Username" : "Emër i Brendshëm Përdoruesi", + "By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [ a-zA-Z0-9_.@- ]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all *DAV services. With this setting, the default behavior can be overridden. To achieve a similar behavior as before ownCloud 5 enter the user display name attribute in the following field. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users." : "Si parazgjedhje, emri i brendshëm i përdoruesit do të krijohet prej atributit UUID. Kjo siguron që emri i përdoruesit të jetë unik dhe shenjat që e përbëjnë s’do të jetë e nevojshme të shndërrohen. Emri i brendshëm i përdoruesi ka kufizimin që për të lejohen vetëm këto shenja: [ a-zA-Z0-9_.@- ]. Shenjat e tjera zëvendësohen me shenjën përgjegjëse në ASCII ose thjesht lihen pa u vënë. Kur ka përplasje, do të shtohet/rritet një numër. Emri i brendshëm i përdoruesit përdoret për ta identifikuar një përdorues së brendshmi. Është gjithashtu emri parazgjedhje për dosjen Home të përdoruesit. Është gjithashtu pjesë e URL-ve të largëta, për shembull, për krejt shërbimet *DAV. Me këtë rregullim mund të anashkalohet sjellja parazgjedhje. Që të arrini një sjellje të ngjashme si para ownCloud 5, jepni atributin emër përdoruesi në ekran te fusha vijuese. Lëreni të zbrazët, që të ruhet sjellja parazgjedhje. Ndryshimet do të kenë efekt vetëm mbi përdorues LDAP të përshoqëruar (shtuar) rishtas.", + "Internal Username Attribute:" : "Atribut Emër i Brendshëm Përdoruesi:", + "Override UUID detection" : "Anashkalo zbullim UUID-je", + "By default, the UUID attribute is automatically detected. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users and groups." : "Si parazgjedhje, atributi UUID zbulohet automatikisht. Atributi UUID përdoret për të identifikuar pa dyshime përdorues dhe grupe LDAP. Gjithashtu, emri i brendshëm i përdoruesi do të krijohet mbi bazën e UUID-së, në mos u përcaktoftë ndryshe më sipër. Mund ta anashkaloni rregullimin dhe të jepni një atribut tuajin sipas dëshirës. Duhet të siguroni që atributi sipas dëshirës të mund të jepet si për përdorues, ashtu edhe për grupe, dhe se është unik. Lëreni të zbrazët që të ruhet sjellja parazgjedhje. Ndryshimet do të kenë efekt vetëm etëm mbi përdorues LDAP të përshoqëruar (shtuar) rishtas.", + "UUID Attribute for Users:" : "Atribut UUID për Përdorues:", + "UUID Attribute for Groups:" : "Atribut UUID për Grupe:", + "Username-LDAP User Mapping" : "Përshoqërim Emër përdoruesi-Përdorues LDAP", + "Usernames are used to store and assign (meta) data. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage." : "Emrat e përdoruesve përdoren për të depozituar dhe shpërndarë (tej) të dhëna. Që të mund të identifikohen dhe pranohen saktësisht përdoruesit, çdo përdorues LDAP do të ketë një emër të brendshëm përdoruesi. Kjo kërkon përshoqërim nga emër përdoruesi te përdorues LDAP. Emri i përdoruesit i krijuar i përshoqërohet UUID-së së përdoruesit LDAP. Tej kësaj, edhe DN-ja ruhet në fshehtinë, për të zvogëluar ndërveprim LDAP, por s’përdoret për identifikim. Nëse ndryshon DN-ja, ndryshimet do të gjenden. Emri i brendshëm i përdoruesi përdoret gjithandej. Heqja e përshoqërimeve do të lërë thërrime ngado. Heqja e përshoqërimeve nuk preket nga formësimi, prek krejt formësimet për LDAP-në! Mos i hiqni kurrë përshoqërimet në një mjedis prodhimi, vetëm në një faqë testimi ose eksperimetale.", + "Clear Username-LDAP User Mapping" : "Pastro Përshoqërimin Emër përdoruesi-Përdorues LDAP", + "Clear Groupname-LDAP Group Mapping" : "Pastro Përshoqërimin Emër grupi-Grup LDAP" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index 0707b95013c..dd8ffe14bca 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -489,7 +489,7 @@ class Access extends LDAPUtility implements user\IUserTools { /** * gives back the user names as they are used ownClod internally - * @param array $ldapUsers an array with the ldap Users result in style of array ( array ('dn' => foo, 'uid' => bar), ... ) + * @param array $ldapUsers as returned by fetchList() * @return array an array with the user names to use in ownCloud * * gives back the user names as they are used ownClod internally @@ -500,7 +500,7 @@ class Access extends LDAPUtility implements user\IUserTools { /** * gives back the group names as they are used ownClod internally - * @param array $ldapGroups an array with the ldap Groups result in style of array ( array ('dn' => foo, 'cn' => bar), ... ) + * @param array $ldapGroups as returned by fetchList() * @return array an array with the group names to use in ownCloud * * gives back the group names as they are used ownClod internally @@ -510,7 +510,7 @@ class Access extends LDAPUtility implements user\IUserTools { } /** - * @param array $ldapObjects + * @param array $ldapObjects as returned by fetchList() * @param bool $isUsers * @return array */ @@ -523,15 +523,25 @@ class Access extends LDAPUtility implements user\IUserTools { $ownCloudNames = array(); foreach($ldapObjects as $ldapObject) { - $nameByLDAP = isset($ldapObject[$nameAttribute]) ? $ldapObject[$nameAttribute] : null; - $ocName = $this->dn2ocname($ldapObject['dn'], $nameByLDAP, $isUsers); + $nameByLDAP = null; + if( isset($ldapObject[$nameAttribute]) + && is_array($ldapObject[$nameAttribute]) + && isset($ldapObject[$nameAttribute][0]) + ) { + // might be set, but not necessarily. if so, we use it. + $nameByLDAP = $ldapObject[$nameAttribute][0]; + } + + $ocName = $this->dn2ocname($ldapObject['dn'][0], $nameByLDAP, $isUsers); if($ocName) { $ownCloudNames[] = $ocName; if($isUsers) { //cache the user names so it does not need to be retrieved //again later (e.g. sharing dialogue). $this->cacheUserExists($ocName); - $this->cacheUserDisplayName($ocName, $nameByLDAP); + if(!is_null($nameByLDAP)) { + $this->cacheUserDisplayName($ocName, $nameByLDAP); + } } } continue; @@ -681,8 +691,9 @@ class Access extends LDAPUtility implements user\IUserTools { * @param array $ldapRecords */ public function batchApplyUserAttributes(array $ldapRecords){ + $displayNameAttribute = strtolower($this->connection->ldapUserDisplayName); foreach($ldapRecords as $userRecord) { - $ocName = $this->dn2ocname($userRecord['dn'], $userRecord[$this->connection->ldapUserDisplayName]); + $ocName = $this->dn2ocname($userRecord['dn'][0], $userRecord[$displayNameAttribute]); $this->cacheUserExists($ocName); $user = $this->userManager->get($ocName); $user->processAttributes($userRecord); @@ -710,6 +721,11 @@ class Access extends LDAPUtility implements user\IUserTools { if($manyAttributes) { return $list; } else { + $list = array_reduce($list, function($carry, $item) { + $attribute = array_keys($item)[0]; + $carry[] = $item[$attribute][0]; + return $carry; + }, array()); return array_unique($list, SORT_LOCALE_STRING); } } @@ -982,44 +998,29 @@ class Access extends LDAPUtility implements user\IUserTools { if(!is_null($attr)) { $selection = array(); - $multiArray = false; - if(count($attr) > 1) { - $multiArray = true; - $i = 0; - } + $i = 0; foreach($findings as $item) { if(!is_array($item)) { continue; } $item = \OCP\Util::mb_array_change_key_case($item, MB_CASE_LOWER, 'UTF-8'); - - if($multiArray) { - foreach($attr as $key) { - $key = mb_strtolower($key, 'UTF-8'); - if(isset($item[$key])) { - if($key !== 'dn') { - $selection[$i][$key] = $this->resemblesDN($key) ? - $this->sanitizeDN($item[$key][0]) - : $item[$key][0]; - } else { - $selection[$i][$key] = $this->sanitizeDN($item[$key]); - } - } - - } - $i++; - } else { - //tribute to case insensitivity - $key = mb_strtolower($attr[0], 'UTF-8'); - + foreach($attr as $key) { + $key = mb_strtolower($key, 'UTF-8'); if(isset($item[$key])) { - if($this->resemblesDN($key)) { - $selection[] = $this->sanitizeDN($item[$key]); + if(is_array($item[$key]) && isset($item[$key]['count'])) { + unset($item[$key]['count']); + } + if($key !== 'dn') { + $selection[$i][$key] = $this->resemblesDN($key) ? + $this->sanitizeDN($item[$key]) + : $item[$key]; } else { - $selection[] = $item[$key]; + $selection[$i][$key] = [$this->sanitizeDN($item[$key])]; } } + } + $i++; } $findings = $selection; } diff --git a/apps/user_ldap/lib/backendutility.php b/apps/user_ldap/lib/backendutility.php index 2b500595569..b0fee7cd25e 100644 --- a/apps/user_ldap/lib/backendutility.php +++ b/apps/user_ldap/lib/backendutility.php @@ -23,7 +23,6 @@ namespace OCA\user_ldap\lib; -use OCA\user_ldap\lib\Access; abstract class BackendUtility { protected $access; diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php index 6e62f5730b9..e1ca624af95 100644 --- a/apps/user_ldap/lib/configuration.php +++ b/apps/user_ldap/lib/configuration.php @@ -3,6 +3,7 @@ * @author Alexander Bergolth <leo@strike.wu.ac.at> * @author Arthur Schiwon <blizzz@owncloud.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lennart Rosam <hello@takuto.de> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> @@ -150,8 +151,9 @@ class Configuration { $setMethod = 'setRawValue'; break; case 'homeFolderNamingRule': - if(!empty(trim($val)) && strpos($val, 'attr:') === false) { - $val = 'attr:'.trim($val); + $trimmedVal = trim($val); + if(!empty($trimmedVal) && strpos($val, 'attr:') === false) { + $val = 'attr:'.$trimmedVal; } break; case 'ldapBase': diff --git a/apps/user_ldap/lib/filesystemhelper.php b/apps/user_ldap/lib/filesystemhelper.php index 7c45d25ed31..ee8c26d2f59 100644 --- a/apps/user_ldap/lib/filesystemhelper.php +++ b/apps/user_ldap/lib/filesystemhelper.php @@ -1,6 +1,7 @@ <?php /** * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * * @copyright Copyright (c) 2015, ownCloud, Inc. @@ -37,7 +38,7 @@ class FilesystemHelper { /** * @brief initializes the filesystem for the given user - * @param string the ownCloud username of the user + * @param string $uid the ownCloud username of the user */ public function setup($uid) { \OC_Util::setupFS($uid); diff --git a/apps/user_ldap/lib/helper.php b/apps/user_ldap/lib/helper.php index 57b75823a1d..f05a4afad2c 100644 --- a/apps/user_ldap/lib/helper.php +++ b/apps/user_ldap/lib/helper.php @@ -27,7 +27,6 @@ namespace OCA\user_ldap\lib; -use OCA\user_ldap\lib\LDAP; use OCA\user_ldap\User_Proxy; class Helper { diff --git a/apps/user_ldap/lib/user/deletedusersindex.php b/apps/user_ldap/lib/user/deletedusersindex.php index acea80bb9d0..6b58595cce6 100644 --- a/apps/user_ldap/lib/user/deletedusersindex.php +++ b/apps/user_ldap/lib/user/deletedusersindex.php @@ -1,6 +1,7 @@ <?php /** * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * * @copyright Copyright (c) 2015, ownCloud, Inc. @@ -22,7 +23,6 @@ namespace OCA\user_ldap\lib\user; -use OCA\user_ldap\lib\user\OfflineUser; use OCA\User_LDAP\Mapping\UserMapping; /** @@ -51,9 +51,9 @@ class DeletedUsersIndex { protected $deletedUsers; /** - * @param OCP\IConfig $config - * @param OCP\IDBConnection $db - * @param OCA\User_LDAP\Mapping\UserMapping $mapping + * @param \OCP\IConfig $config + * @param \OCP\IDBConnection $db + * @param \OCA\User_LDAP\Mapping\UserMapping $mapping */ public function __construct(\OCP\IConfig $config, \OCP\IDBConnection $db, UserMapping $mapping) { $this->config = $config; @@ -63,7 +63,7 @@ class DeletedUsersIndex { /** * reads LDAP users marked as deleted from the database - * @return OCA\user_ldap\lib\user\OfflineUser[] + * @return \OCA\user_ldap\lib\user\OfflineUser[] */ private function fetchDeletedUsers() { $deletedUsers = $this->config->getUsersForUserValue( @@ -80,7 +80,7 @@ class DeletedUsersIndex { /** * returns all LDAP users that are marked as deleted - * @return OCA\user_ldap\lib\user\OfflineUser[] + * @return \OCA\user_ldap\lib\user\OfflineUser[] */ public function getUsers() { if(is_array($this->deletedUsers)) { @@ -105,7 +105,7 @@ class DeletedUsersIndex { /** * marks a user as deleted - * @param string ocName + * @param string $ocName */ public function markUser($ocName) { $this->config->setUserValue($ocName, 'user_ldap', 'isDeleted', '1'); diff --git a/apps/user_ldap/lib/user/manager.php b/apps/user_ldap/lib/user/manager.php index 86f0b8991d7..4a687c0832a 100644 --- a/apps/user_ldap/lib/user/manager.php +++ b/apps/user_ldap/lib/user/manager.php @@ -1,6 +1,7 @@ <?php /** * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Morris Jobke <hey@morrisjobke.de> * @@ -75,7 +76,7 @@ class Manager { * @param \OCP\IAvatarManager $avatarManager * @param \OCP\Image $image an empty image instance * @param \OCP\IDBConnection $db - * @throws Exception when the methods mentioned above do not exist + * @throws \Exception when the methods mentioned above do not exist */ public function __construct(\OCP\IConfig $ocConfig, FilesystemHelper $ocFilesystem, LogWrapper $ocLog, @@ -101,9 +102,9 @@ class Manager { /** * @brief creates an instance of User and caches (just runtime) it in the * property array - * @param string the DN of the user - * @param string the internal (owncloud) username - * @return \OCA\user_ldap\lib\User + * @param string $dn the DN of the user + * @param string $uid the internal (owncloud) username + * @return \OCA\user_ldap\lib\User\User */ private function createAndCache($dn, $uid) { $this->checkAccess(); @@ -117,7 +118,7 @@ class Manager { /** * @brief checks whether the Access instance has been set - * @throws Exception if Access has not been set + * @throws \Exception if Access has not been set * @return null */ private function checkAccess() { @@ -189,7 +190,7 @@ class Manager { /** * @brief returns a User object by it's ownCloud username - * @param string the DN or username of the user + * @param string $id the DN or username of the user * @return \OCA\user_ldap\lib\user\User|\OCA\user_ldap\lib\user\OfflineUser|null */ protected function createInstancyByUserName($id) { @@ -206,7 +207,7 @@ class Manager { /** * @brief returns a User object by it's DN or ownCloud username - * @param string the DN or username of the user + * @param string $id the DN or username of the user * @return \OCA\user_ldap\lib\user\User|\OCA\user_ldap\lib\user\OfflineUser|null * @throws \Exception when connection could not be established */ diff --git a/apps/user_ldap/lib/user/offlineuser.php b/apps/user_ldap/lib/user/offlineuser.php index 0727eb5b584..72c02427928 100644 --- a/apps/user_ldap/lib/user/offlineuser.php +++ b/apps/user_ldap/lib/user/offlineuser.php @@ -1,6 +1,7 @@ <?php /** * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * * @copyright Copyright (c) 2015, ownCloud, Inc. @@ -72,9 +73,9 @@ class OfflineUser { /** * @param string $ocName - * @param OCP\IConfig $config - * @param OCP\IDBConnection $db - * @param OCA\User_LDAP\Mapping\UserMapping $mapping + * @param \OCP\IConfig $config + * @param \OCP\IDBConnection $db + * @param \OCA\User_LDAP\Mapping\UserMapping $mapping */ public function __construct($ocName, \OCP\IConfig $config, \OCP\IDBConnection $db, UserMapping $mapping) { $this->ocName = $ocName; diff --git a/apps/user_ldap/lib/user/user.php b/apps/user_ldap/lib/user/user.php index 6498cdf913f..0dc3c8c0c26 100644 --- a/apps/user_ldap/lib/user/user.php +++ b/apps/user_ldap/lib/user/user.php @@ -1,6 +1,7 @@ <?php /** * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * * @copyright Copyright (c) 2015, ownCloud, Inc. @@ -87,15 +88,15 @@ class User { /** * @brief constructor, make sure the subclasses call this one! - * @param string the internal username - * @param string the LDAP DN + * @param string $username the internal username + * @param string $dn the LDAP DN * @param IUserTools $access an instance that implements IUserTools for * LDAP interaction - * @param \OCP\IConfig - * @param FilesystemHelper - * @param \OCP\Image any empty instance - * @param LogWrapper - * @param \OCP\IAvatarManager + * @param \OCP\IConfig $config + * @param FilesystemHelper $fs + * @param \OCP\Image $image any empty instance + * @param LogWrapper $log + * @param \OCP\IAvatarManager $avatarManager */ public function __construct($username, $dn, IUserTools $access, \OCP\IConfig $config, FilesystemHelper $fs, \OCP\Image $image, @@ -147,21 +148,21 @@ class User { //Quota $attr = strtolower($this->connection->ldapQuotaAttribute); if(isset($ldapEntry[$attr])) { - $this->updateQuota($ldapEntry[$attr]); + $this->updateQuota($ldapEntry[$attr][0]); } unset($attr); //Email $attr = strtolower($this->connection->ldapEmailAttribute); if(isset($ldapEntry[$attr])) { - $this->updateEmail($ldapEntry[$attr]); + $this->updateEmail($ldapEntry[$attr][0]); } unset($attr); //displayName $attr = strtolower($this->connection->ldapUserDisplayName); if(isset($ldapEntry[$attr])) { - $displayName = $ldapEntry[$attr]; + $displayName = $ldapEntry[$attr][0]; if(!empty($displayName)) { $this->storeDisplayName($displayName); $this->access->cacheUserDisplayName($this->getUsername(), $displayName); @@ -171,18 +172,20 @@ class User { // LDAP Username, needed for s2s sharing if(isset($ldapEntry['uid'])) { - $this->storeLDAPUserName($ldapEntry['uid']); + $this->storeLDAPUserName($ldapEntry['uid'][0]); } else if(isset($ldapEntry['samaccountname'])) { - $this->storeLDAPUserName($ldapEntry['samaccountname']); + $this->storeLDAPUserName($ldapEntry['samaccountname'][0]); } + //homePath if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) { $attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:'))); if(isset($ldapEntry[$attr])) { $this->access->cacheUserHome( - $this->getUsername(), $this->getHomePath($ldapEntry[$attr])); + $this->getUsername(), $this->getHomePath($ldapEntry[$attr][0])); } } + //memberOf groups $cacheKey = 'getMemberOf'.$this->getUsername(); $groups = false; @@ -190,11 +193,12 @@ class User { $groups = $ldapEntry['memberof']; } $this->connection->writeToCache($cacheKey, $groups); + //Avatar $attrs = array('jpegphoto', 'thumbnailphoto'); foreach ($attrs as $attr) { if(isset($ldapEntry[$attr])) { - $this->avatarImage = $ldapEntry[$attr]; + $this->avatarImage = $ldapEntry[$attr][0]; $this->updateAvatar(); break; } @@ -365,7 +369,7 @@ class User { * @brief checks whether an update method specified by feature was run * already. If not, it will marked like this, because it is expected that * the method will be run, when false is returned. - * @param string email | quota | avatar (can be extended) + * @param string $feature email | quota | avatar (can be extended) * @return bool */ private function wasRefreshed($feature) { @@ -413,9 +417,9 @@ class User { } //can be null $quotaDefault = $this->connection->ldapQuotaDefault; - $quota = !is_null($valueFromLDAP) - ? $valueFromLDAP - : $quotaDefault !== '' ? $quotaDefault : null; + $quota = $quotaDefault !== '' ? $quotaDefault : null; + $quota = !is_null($valueFromLDAP) ? $valueFromLDAP : $quota; + if(is_null($valueFromLDAP)) { $quotaAttribute = $this->connection->ldapQuotaAttribute; if(!empty($quotaAttribute)) { diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php index 84a1225d6ec..a819b2e0e46 100644 --- a/apps/user_ldap/lib/wizard.php +++ b/apps/user_ldap/lib/wizard.php @@ -435,7 +435,11 @@ class Wizard extends LDAPUtility { // detection will fail later $result = $this->access->searchGroups($filter, array('cn', 'dn'), $limit, $offset); foreach($result as $item) { - $groupNames[] = $item['cn']; + if(!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) { + // just in case - no issue known + continue; + } + $groupNames[] = $item['cn'][0]; $groupEntries[] = $item; } $offset += $limit; diff --git a/apps/user_ldap/tests/access.php b/apps/user_ldap/tests/access.php index cb6dbf0cd5d..25e871d9b3d 100644 --- a/apps/user_ldap/tests/access.php +++ b/apps/user_ldap/tests/access.php @@ -230,24 +230,34 @@ class Test_Access extends \Test\TestCase { $mapperMock = $this->getMockBuilder('\OCA\User_LDAP\Mapping\UserMapping') ->disableOriginalConstructor() ->getMock(); + + $mapperMock->expects($this->any()) + ->method('getNameByDN') + ->will($this->returnValue('a_username')); + $userMock = $this->getMockBuilder('\OCA\user_ldap\lib\user\User') ->disableOriginalConstructor() ->getMock(); + $access->connection->expects($this->any()) + ->method('__get') + ->will($this->returnValue('displayName')); + $access->setUserMapper($mapperMock); + $displayNameAttribute = strtolower($access->connection->ldapUserDisplayName); $data = array( array( 'dn' => 'foobar', - $con->ldapUserDisplayName => 'barfoo' + $displayNameAttribute => 'barfoo' ), array( 'dn' => 'foo', - $con->ldapUserDisplayName => 'bar' + $displayNameAttribute => 'bar' ), array( 'dn' => 'raboof', - $con->ldapUserDisplayName => 'oofrab' + $displayNameAttribute => 'oofrab' ) ); diff --git a/apps/user_ldap/tests/group_ldap.php b/apps/user_ldap/tests/group_ldap.php index 132fbfdf687..6a6d5bc7ca1 100644 --- a/apps/user_ldap/tests/group_ldap.php +++ b/apps/user_ldap/tests/group_ldap.php @@ -3,6 +3,7 @@ * @author Arthur Schiwon <blizzz@owncloud.com> * @author Frédéric Fortier <frederic.fortier@oronospolytechnique.com> * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * * @copyright Copyright (c) 2015, ownCloud, Inc. @@ -42,14 +43,9 @@ class Test_Group_Ldap extends \Test\TestCase { $connector = $this->getMock('\OCA\user_ldap\lib\Connection', $conMethods, array($lw, null, null)); - $um = new \OCA\user_ldap\lib\user\Manager( - $this->getMock('\OCP\IConfig'), - $this->getMock('\OCA\user_ldap\lib\FilesystemHelper'), - $this->getMock('\OCA\user_ldap\lib\LogWrapper'), - $this->getMock('\OCP\IAvatarManager'), - $this->getMock('\OCP\Image'), - $this->getMock('\OCP\IDBConnection') - ); + $um = $this->getMockBuilder('\OCA\user_ldap\lib\user\Manager') + ->disableOriginalConstructor() + ->getMock(); $access = $this->getMock('\OCA\user_ldap\lib\Access', $accMethods, array($connector, $lw, $um)); @@ -145,7 +141,7 @@ class Test_Group_Ldap extends \Test\TestCase { $access->expects($this->once()) ->method('searchGroups') - ->will($this->returnValue(array('cn=foo,dc=barfoo,dc=bar'))); + ->will($this->returnValue([['dn' => ['cn=foo,dc=barfoo,dc=bar']]])); $access->expects($this->once()) ->method('dn2groupname') @@ -221,7 +217,7 @@ class Test_Group_Ldap extends \Test\TestCase { $access->expects($this->once()) ->method('searchGroups') - ->will($this->returnValue(array('cn=foo,dc=barfoo,dc=bar'))); + ->will($this->returnValue([['dn' => ['cn=foo,dc=barfoo,dc=bar']]])); $access->expects($this->once()) ->method('dn2groupname') diff --git a/apps/user_ldap/tests/user/user.php b/apps/user_ldap/tests/user/user.php index 1c41eb71ec2..19581d835d1 100644 --- a/apps/user_ldap/tests/user/user.php +++ b/apps/user_ldap/tests/user/user.php @@ -370,6 +370,45 @@ class Test_User_User extends \Test\TestCase { $user->updateQuota(); } + public function testUpdateQuotaFromValue() { + list($access, $config, $filesys, $image, $log, $avaMgr, $dbc) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr, $dbc); + + $readQuota = '19 GB'; + + $connection->expects($this->at(0)) + ->method('__get') + ->with($this->equalTo('ldapQuotaDefault')) + ->will($this->returnValue('')); + + $connection->expects($this->once(1)) + ->method('__get') + ->with($this->equalTo('ldapQuotaDefault')) + ->will($this->returnValue(null)); + + $access->expects($this->never()) + ->method('readAttribute'); + + $config->expects($this->once()) + ->method('setUserValue') + ->with($this->equalTo('alice'), + $this->equalTo('files'), + $this->equalTo('quota'), + $this->equalTo($readQuota)) + ->will($this->returnValue(true)); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->updateQuota($readQuota); + } + //the testUpdateAvatar series also implicitely tests getAvatarImage public function testUpdateAvatarJpegPhotoProvided() { list($access, $config, $filesys, $image, $log, $avaMgr, $dbc) = diff --git a/apps/user_ldap/tests/user_ldap.php b/apps/user_ldap/tests/user_ldap.php index 69a76c0b7ac..0f70c43fc11 100644 --- a/apps/user_ldap/tests/user_ldap.php +++ b/apps/user_ldap/tests/user_ldap.php @@ -124,7 +124,7 @@ class Test_User_Ldap_Direct extends \Test\TestCase { ->method('fetchListOfUsers') ->will($this->returnCallback(function($filter) { if($filter === 'roland') { - return array(array('dn' => 'dnOfRoland,dc=test')); + return array(array('dn' => ['dnOfRoland,dc=test'])); } return array(); })); @@ -133,7 +133,7 @@ class Test_User_Ldap_Direct extends \Test\TestCase { ->method('fetchUsersByLoginName') ->will($this->returnCallback(function($uid) { if($uid === 'roland') { - return array(array('dn' => 'dnOfRoland,dc=test')); + return array(array('dn' => ['dnOfRoland,dc=test'])); } return array(); })); diff --git a/apps/user_ldap/user_ldap.php b/apps/user_ldap/user_ldap.php index 59c61524c9b..fc8ce361637 100644 --- a/apps/user_ldap/user_ldap.php +++ b/apps/user_ldap/user_ldap.php @@ -78,7 +78,7 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn public function loginName2UserName($loginName) { try { $ldapRecord = $this->getLDAPUserByLoginName($loginName); - $user = $this->access->userManager->get($ldapRecord['dn']); + $user = $this->access->userManager->get($ldapRecord['dn'][0]); if($user instanceof OfflineUser) { return false; } @@ -119,7 +119,7 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn } catch(\Exception $e) { return false; } - $dn = $ldapRecord['dn']; + $dn = $ldapRecord['dn'][0]; $user = $this->access->userManager->get($dn); if(!$user instanceof User) { |