diff --git a/.htaccess b/.htaccess index cb2cc9cf7e..8a24076dcb 100644 --- a/.htaccess +++ b/.htaccess @@ -14,6 +14,10 @@ Header set X-Robots-Tag "none" Header set X-Frame-Options "SAMEORIGIN" SetEnv modHeadersAvailable true + + # Add CSP header if not set, used for static resources + Header append Content-Security-Policy "" + Header edit Content-Security-Policy "^$" "default-src 'none'; style-src 'self' 'unsafe-inline'; script-src 'self'" # Add cache control for CSS and JS files @@ -53,6 +57,7 @@ RewriteRule ^core/js/oc.js$ index.php/core/js/oc.js [PT,E=PATH_INFO:$1] RewriteRule ^core/preview.png$ index.php/core/preview.png [PT,E=PATH_INFO:$1] RewriteCond %{REQUEST_FILENAME} !\.(css|js|svg|gif|png|html|ttf|woff)$ + RewriteCond %{REQUEST_FILENAME} !core/img/favicon.ico$ RewriteCond %{REQUEST_FILENAME} !/remote.php RewriteCond %{REQUEST_FILENAME} !/public.php RewriteCond %{REQUEST_FILENAME} !/cron.php diff --git a/.mention-bot b/.mention-bot new file mode 100644 index 0000000000..b342edbd21 --- /dev/null +++ b/.mention-bot @@ -0,0 +1,11 @@ +{ + "maxReviewers": 3, + "numFilesToCheck": 5, + "alwaysNotifyForPaths": [ + { + "name": "DeepDiver1975", + "files": ["apps/dav/**"] + } + ], + "userBlacklist": ["owncloud-bot"] +} diff --git a/apps/dav/lib/rootcollection.php b/apps/dav/lib/rootcollection.php index c1635c9cde..9ee32822bb 100644 --- a/apps/dav/lib/rootcollection.php +++ b/apps/dav/lib/rootcollection.php @@ -33,6 +33,13 @@ class RootCollection extends SimpleCollection { $caldavBackend = new CalDavBackend($db); $calendarRoot = new CalendarRoot($principalBackend, $caldavBackend, 'principals/users'); $calendarRoot->disableListing = $disableListing; + $systemTagCollection = new SystemTag\SystemTagsByIdCollection( + \OC::$server->getSystemTagManager() + ); + $systemTagRelationsCollection = new SystemTag\SystemTagsRelationsCollection( + \OC::$server->getSystemTagManager(), + \OC::$server->getSystemTagObjectMapper() + ); $usersCardDavBackend = new CardDavBackend($db, $principalBackend); $usersAddressBookRoot = new AddressBookRoot($principalBackend, $usersCardDavBackend, 'principals/users'); @@ -51,6 +58,8 @@ class RootCollection extends SimpleCollection { new SimpleCollection('addressbooks', [ $usersAddressBookRoot, $systemAddressBookRoot]), + $systemTagCollection, + $systemTagRelationsCollection, ]; parent::__construct('root', $children); diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php index a031f2c442..ffcbb02db7 100644 --- a/apps/dav/lib/server.php +++ b/apps/dav/lib/server.php @@ -60,6 +60,9 @@ class Server { // addressbook plugins $this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin()); + // system tags plugins + $this->server->addPlugin(new \OCA\DAV\SystemTag\SystemTagPlugin(\OC::$server->getSystemTagManager())); + // Finder on OS X requires Class 2 WebDAV support (locking), since we do // not provide locking we emulate it using a fake locking plugin. if($request->isUserAgent(['/WebDAVFS/'])) { diff --git a/apps/dav/lib/systemtag/systemtagmappingnode.php b/apps/dav/lib/systemtag/systemtagmappingnode.php new file mode 100644 index 0000000000..cbf8542a4f --- /dev/null +++ b/apps/dav/lib/systemtag/systemtagmappingnode.php @@ -0,0 +1,102 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\SystemTag; + +use Sabre\DAV\Exception\NotFound; + +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagObjectMapper; +use OCP\SystemTag\TagNotFoundException; + +/** + * Mapping node for system tag to object id + */ +class SystemTagMappingNode extends SystemTagNode { + + /** + * @var ISystemTagObjectMapper + */ + private $tagMapper; + + /** + * @var string + */ + private $objectId; + + /** + * @var string + */ + private $objectType; + + /** + * Sets up the node, expects a full path name + * + * @param ISystemTag $tag system tag + * @param string $objectId + * @param string $objectType + * @param ISystemTagManager $tagManager + * @param ISystemTagObjectMapper $tagMapper + */ + public function __construct( + ISystemTag $tag, + $objectId, + $objectType, + ISystemTagManager $tagManager, + ISystemTagObjectMapper $tagMapper + ) { + $this->objectId = $objectId; + $this->objectType = $objectType; + $this->tagMapper = $tagMapper; + parent::__construct($tag, $tagManager); + } + + /** + * Returns the object id of the relationship + * + * @return string object id + */ + public function getObjectId() { + return $this->objectId; + } + + /** + * Returns the object type of the relationship + * + * @return string object type + */ + public function getObjectType() { + return $this->objectType; + } + + /** + * Delete tag to object association + */ + public function delete() { + try { + $this->tagMapper->unassignTags($this->objectId, $this->objectType, $this->tag->getId()); + } catch (TagNotFoundException $e) { + // can happen if concurrent deletion occurred + throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found', 0, $e); + } + } +} diff --git a/apps/dav/lib/systemtag/systemtagnode.php b/apps/dav/lib/systemtag/systemtagnode.php new file mode 100644 index 0000000000..7ab4a8a14f --- /dev/null +++ b/apps/dav/lib/systemtag/systemtagnode.php @@ -0,0 +1,126 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\SystemTag; + +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Exception\MethodNotAllowed; +use Sabre\DAV\Exception\Conflict; + +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\TagNotFoundException; +use OCP\SystemTag\TagAlreadyExistsException; + +/** + * DAV node representing a system tag, with the name being the tag id. + */ +class SystemTagNode implements \Sabre\DAV\INode { + + /** + * @var ISystemTag + */ + protected $tag; + + /** + * @var ISystemTagManager + */ + protected $tagManager; + + /** + * Sets up the node, expects a full path name + * + * @param ISystemTag $tag system tag + * @param ISystemTagManager $tagManager + */ + public function __construct(ISystemTag $tag, ISystemTagManager $tagManager) { + $this->tag = $tag; + $this->tagManager = $tagManager; + } + + /** + * Returns the id of the tag + * + * @return string + */ + public function getName() { + return $this->tag->getId(); + } + + /** + * Returns the system tag represented by this node + * + * @return ISystemTag system tag + */ + public function getSystemTag() { + return $this->tag; + } + + /** + * Renames the node + * + * @param string $name The new name + * + * @throws MethodNotAllowed not allowed to rename node + */ + public function setName($name) { + throw new MethodNotAllowed(); + } + + /** + * Update tag + * + * @param string $name new tag name + * @param bool $userVisible user visible + * @param bool $userAssignable user assignable + * @throws NotFound whenever the given tag id does not exist + * @throws Conflict whenever a tag already exists with the given attributes + */ + public function update($name, $userVisible, $userAssignable) { + try { + $this->tagManager->updateTag($this->tag->getId(), $name, $userVisible, $userAssignable); + } catch (TagNotFoundException $e) { + throw new NotFound('Tag with id ' . $this->tag->getId() . ' does not exist'); + } catch (TagAlreadyExistsException $e) { + throw new Conflict( + 'Tag with the properties "' . $name . '", ' . + $userVisible . ', ' . $userAssignable . ' already exists' + ); + } + } + + /** + * Returns null, not supported + * + */ + public function getLastModified() { + return null; + } + + public function delete() { + try { + $this->tagManager->deleteTags($this->tag->getId()); + } catch (TagNotFoundException $e) { + // can happen if concurrent deletion occurred + throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found', 0, $e); + } + } +} diff --git a/apps/dav/lib/systemtag/systemtagplugin.php b/apps/dav/lib/systemtag/systemtagplugin.php new file mode 100644 index 0000000000..51db063254 --- /dev/null +++ b/apps/dav/lib/systemtag/systemtagplugin.php @@ -0,0 +1,249 @@ + + * + * @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 + * + */ +namespace OCA\DAV\SystemTag; + +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Exception\UnsupportedMediaType; +use Sabre\DAV\Exception\Conflict; + +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\TagAlreadyExistsException; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * Sabre plugin to handle system tags: + * + * - makes it possible to create new tags with POST operation + * - get/set Webdav properties for tags + * + */ +class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { + + // namespace + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + const ID_PROPERTYNAME = '{http://owncloud.org/ns}id'; + const DISPLAYNAME_PROPERTYNAME = '{http://owncloud.org/ns}display-name'; + const USERVISIBLE_PROPERTYNAME = '{http://owncloud.org/ns}user-visible'; + const USERASSIGNABLE_PROPERTYNAME = '{http://owncloud.org/ns}user-assignable'; + + /** + * @var \Sabre\DAV\Server $server + */ + private $server; + + /** + * @var ISystemTagManager + */ + protected $tagManager; + + /** + * System tags plugin + * + * @param ISystemTagManager $tagManager tag manager + */ + public function __construct(ISystemTagManager $tagManager) { + $this->tagManager = $tagManager; + } + + /** + * 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->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc'; + + $server->protectedProperties[] = self::ID_PROPERTYNAME; + + $server->on('propFind', array($this, 'handleGetProperties')); + $server->on('propPatch', array($this, 'handleUpdateProperties')); + $server->on('method:POST', [$this, 'httpPost']); + + $this->server = $server; + } + + /** + * POST operation on system tag collections + * + * @param RequestInterface $request request object + * @param ResponseInterface $response response object + * @return null|false + */ + public function httpPost(RequestInterface $request, ResponseInterface $response) { + $path = $request->getPath(); + + // Making sure the node exists + try { + $node = $this->server->tree->getNodeForPath($path); + } catch (NotFound $e) { + return null; + } + + if ($node instanceof SystemTagsByIdCollection || $node instanceof SystemTagsObjectMappingCollection) { + $data = $request->getBodyAsString(); + + $tag = $this->createTag($data, $request->getHeader('Content-Type')); + + if ($node instanceof SystemTagsObjectMappingCollection) { + // also add to collection + $node->createFile($tag->getId()); + $url = $request->getBaseUrl() . 'systemtags/'; + } else { + $url = $request->getUrl(); + } + + if ($url[strlen($url) - 1] !== '/') { + $url .= '/'; + } + + $response->setHeader('Location', $url . $tag->getId()); + + // created + $response->setStatus(201); + return false; + } + } + + /** + * Creates a new tag + * + * @param string $data JSON encoded string containing the properties of the tag to create + * @param string $contentType content type of the data + * @return ISystemTag newly created system tag + * + * @throws BadRequest if a field was missing + * @throws Conflict if a tag with the same properties already exists + * @throws UnsupportedMediaType if the content type is not supported + */ + private function createTag($data, $contentType = 'application/json') { + if ($contentType === 'application/json') { + $data = json_decode($data, true); + } else { + throw new UnsupportedMediaType(); + } + + if (!isset($data['name'])) { + throw new BadRequest('Missing "name" attribute'); + } + + $tagName = $data['name']; + $userVisible = true; + $userAssignable = true; + + if (isset($data['userVisible'])) { + $userVisible = (bool)$data['userVisible']; + } + + if (isset($data['userAssignable'])) { + $userAssignable = (bool)$data['userAssignable']; + } + try { + return $this->tagManager->createTag($tagName, $userVisible, $userAssignable); + } catch (TagAlreadyExistsException $e) { + throw new Conflict('Tag already exists', 0, $e); + } + } + + + /** + * Retrieves system tag properties + * + * @param PropFind $propFind + * @param \Sabre\DAV\INode $node + */ + public function handleGetProperties( + PropFind $propFind, + \Sabre\DAV\INode $node + ) { + if (!($node instanceof SystemTagNode)) { + return; + } + + $propFind->handle(self::ID_PROPERTYNAME, function() use ($node) { + return $node->getSystemTag()->getId(); + }); + + $propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function() use ($node) { + return $node->getSystemTag()->getName(); + }); + + $propFind->handle(self::USERVISIBLE_PROPERTYNAME, function() use ($node) { + return (int)$node->getSystemTag()->isUserVisible(); + }); + + $propFind->handle(self::USERASSIGNABLE_PROPERTYNAME, function() use ($node) { + return (int)$node->getSystemTag()->isUserAssignable(); + }); + } + + /** + * Updates tag attributes + * + * @param string $path + * @param PropPatch $propPatch + * + * @return void + */ + public function handleUpdateProperties($path, PropPatch $propPatch) { + $propPatch->handle([ + self::DISPLAYNAME_PROPERTYNAME, + self::USERVISIBLE_PROPERTYNAME, + self::USERASSIGNABLE_PROPERTYNAME, + ], function($props) use ($path) { + $node = $this->server->tree->getNodeForPath($path); + if (!($node instanceof SystemTagNode)) { + return; + } + + $tag = $node->getSystemTag(); + $name = $tag->getName(); + $userVisible = $tag->isUserVisible(); + $userAssignable = $tag->isUserAssignable(); + + if (isset($props[self::DISPLAYNAME_PROPERTYNAME])) { + $name = $props[self::DISPLAYNAME_PROPERTYNAME]; + } + + if (isset($props[self::USERVISIBLE_PROPERTYNAME])) { + $userVisible = (bool)$props[self::USERVISIBLE_PROPERTYNAME]; + } + + if (isset($props[self::USERASSIGNABLE_PROPERTYNAME])) { + $userAssignable = (bool)$props[self::USERASSIGNABLE_PROPERTYNAME]; + } + + $node->update($name, $userVisible, $userAssignable); + return true; + }); + } +} diff --git a/apps/dav/lib/systemtag/systemtagsbyidcollection.php b/apps/dav/lib/systemtag/systemtagsbyidcollection.php new file mode 100644 index 0000000000..e7b7b6d0ac --- /dev/null +++ b/apps/dav/lib/systemtag/systemtagsbyidcollection.php @@ -0,0 +1,117 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\SystemTag; + +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\ICollection; + +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\TagNotFoundException; + +class SystemTagsByIdCollection implements ICollection { + + /** + * @var ISystemTagManager + */ + private $tagManager; + + /** + * SystemTagsByIdCollection constructor. + * + * @param ISystemTagManager $tagManager + */ + public function __construct($tagManager) { + $this->tagManager = $tagManager; + } + + function createFile($name, $data = null) { + throw new Forbidden('Cannot create tags by id'); + } + + function createDirectory($name) { + throw new Forbidden('Permission denied to create collections'); + } + + function getChild($name) { + try { + $tags = $this->tagManager->getTagsByIds([$name]); + return $this->makeNode(current($tags)); + } catch (\InvalidArgumentException $e) { + throw new BadRequest('Invalid tag id', 0, $e); + } catch (TagNotFoundException $e) { + throw new NotFound('Tag with id ' . $name . ' not found', 0, $e); + } + } + + function getChildren() { + $tags = $this->tagManager->getAllTags(true); + return array_map(function($tag) { + return $this->makeNode($tag); + }, $tags); + } + + function childExists($name) { + try { + $this->tagManager->getTagsByIds([$name]); + return true; + } catch (\InvalidArgumentException $e) { + throw new BadRequest('Invalid tag id', 0, $e); + } catch (TagNotFoundException $e) { + return false; + } + } + + function delete() { + throw new Forbidden('Permission denied to delete this collection'); + } + + function getName() { + return 'systemtags'; + } + + function setName($name) { + throw new Forbidden('Permission denied to rename this collection'); + } + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int + */ + function getLastModified() { + return null; + } + + /** + * Create a sabre node for the given system tag + * + * @param ISystemTag $tag + * + * @return SystemTagNode + */ + private function makeNode(ISystemTag $tag) { + return new SystemTagNode($tag, $this->tagManager); + } +} diff --git a/apps/dav/lib/systemtag/systemtagsobjectmappingcollection.php b/apps/dav/lib/systemtag/systemtagsobjectmappingcollection.php new file mode 100644 index 0000000000..89e8620614 --- /dev/null +++ b/apps/dav/lib/systemtag/systemtagsobjectmappingcollection.php @@ -0,0 +1,160 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\SystemTag; + +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Exception\PreconditionFailed; +use Sabre\DAV\ICollection; + +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagObjectMapper; +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\TagNotFoundException; + +/** + * Collection containing tags by object id + */ +class SystemTagsObjectMappingCollection implements ICollection { + + /** + * @var string + */ + private $objectId; + + /** + * @var string + */ + private $objectType; + + /** + * @var ISystemTagManager + */ + private $tagManager; + + /** + * @var ISystemTagObjectMapper + */ + private $tagMapper; + + /** + * Constructor + * + * @param string $objectId object id + * @param string $objectType object type + * @param ISystemTagManager $tagManager + * @param ISystemTagObjectMapper $tagMapper + */ + public function __construct($objectId, $objectType, $tagManager, $tagMapper) { + $this->tagManager = $tagManager; + $this->tagMapper = $tagMapper; + $this->objectId = $objectId; + $this->objectType = $objectType; + } + + function createFile($tagId, $data = null) { + try { + $this->tagMapper->assignTags($this->objectId, $this->objectType, $tagId); + } catch (TagNotFoundException $e) { + throw new PreconditionFailed('Tag with id ' . $tagId . ' does not exist, cannot assign'); + } + } + + function createDirectory($name) { + throw new Forbidden('Permission denied to create collections'); + } + + function getChild($tagId) { + try { + if ($this->tagMapper->haveTag([$this->objectId], $this->objectType, $tagId, true)) { + $tag = $this->tagManager->getTagsByIds([$tagId]); + return $this->makeNode(current($tag)); + } + throw new NotFound('Tag with id ' . $tagId . ' not present for object ' . $this->objectId); + } catch (\InvalidArgumentException $e) { + throw new BadRequest('Invalid tag id', 0, $e); + } catch (TagNotFoundException $e) { + throw new NotFound('Tag with id ' . $tagId . ' not found', 0, $e); + } + } + + function getChildren() { + $tagIds = current($this->tagMapper->getTagIdsForObjects([$this->objectId], $this->objectType)); + if (empty($tagIds)) { + return []; + } + $tags = $this->tagManager->getTagsByIds($tagIds); + return array_values(array_map(function($tag) { + return $this->makeNode($tag); + }, $tags)); + } + + function childExists($tagId) { + try { + return ($this->tagMapper->haveTag([$this->objectId], $this->objectType, $tagId, true)); + } catch (\InvalidArgumentException $e) { + throw new BadRequest('Invalid tag id', 0, $e); + } catch (TagNotFoundException $e) { + return false; + } + } + + function delete() { + throw new Forbidden('Permission denied to delete this collection'); + } + + function getName() { + return $this->objectId; + } + + function setName($name) { + throw new Forbidden('Permission denied to rename this collection'); + } + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int + */ + function getLastModified() { + return null; + } + + /** + * Create a sabre node for the mapping of the + * given system tag to the collection's object + * + * @param ISystemTag $tag + * + * @return SystemTagNode + */ + private function makeNode(ISystemTag $tag) { + return new SystemTagMappingNode( + $tag, + $this->objectId, + $this->objectType, + $this->tagManager, + $this->tagMapper + ); + } +} diff --git a/apps/dav/lib/systemtag/systemtagsobjecttypecollection.php b/apps/dav/lib/systemtag/systemtagsobjecttypecollection.php new file mode 100644 index 0000000000..e544073613 --- /dev/null +++ b/apps/dav/lib/systemtag/systemtagsobjecttypecollection.php @@ -0,0 +1,110 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\SystemTag; + +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\MethodNotAllowed; +use Sabre\DAV\ICollection; + +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagObjectMapper; + +/** + * Collection containing object ids by object type + */ +class SystemTagsObjectTypeCollection implements ICollection { + + /** + * @var string + */ + private $objectType; + + /** + * @var ISystemTagManager + */ + private $tagManager; + + /** + * @var ISystemTagObjectMapper + */ + private $tagMapper; + + /** + * Constructor + * + * @param string $objectType object type + * @param ISystemTagManager $tagManager + * @param ISystemTagObjectMapper $tagMapper + */ + public function __construct($objectType, $tagManager, $tagMapper) { + $this->tagManager = $tagManager; + $this->tagMapper = $tagMapper; + $this->objectType = $objectType; + } + + function createFile($name, $data = null) { + throw new Forbidden('Permission denied to create nodes'); + } + + function createDirectory($name) { + throw new Forbidden('Permission denied to create collections'); + } + + function getChild($objectId) { + return new SystemTagsObjectMappingCollection( + $objectId, + $this->objectType, + $this->tagManager, + $this->tagMapper + ); + } + + function getChildren() { + // do not list object ids + throw new MethodNotAllowed(); + } + + function childExists($name) { + return true; + } + + function delete() { + throw new Forbidden('Permission denied to delete this collection'); + } + + function getName() { + return $this->objectType; + } + + function setName($name) { + throw new Forbidden('Permission denied to rename this collection'); + } + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int + */ + function getLastModified() { + return null; + } +} diff --git a/apps/dav/lib/systemtag/systemtagsrelationscollection.php b/apps/dav/lib/systemtag/systemtagsrelationscollection.php new file mode 100644 index 0000000000..44069bca02 --- /dev/null +++ b/apps/dav/lib/systemtag/systemtagsrelationscollection.php @@ -0,0 +1,53 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\SystemTag; + +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagObjectMapper; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\SimpleCollection; + +class SystemTagsRelationsCollection extends SimpleCollection { + + /** + * SystemTagsRelationsCollection constructor. + * + * @param ISystemTagManager $tagManager + * @param ISystemTagObjectMapper $tagMapper + */ + public function __construct($tagManager, $tagMapper) { + $children = [ + new SystemTagsObjectTypeCollection('files', $tagManager, $tagMapper), + ]; + + parent::__construct('root', $children); + } + + function getName() { + return 'systemtags-relations'; + } + + function setName($name) { + throw new Forbidden('Permission denied to rename this collection'); + } + +} diff --git a/apps/dav/tests/unit/systemtag/systemtagmappingnode.php b/apps/dav/tests/unit/systemtag/systemtagmappingnode.php new file mode 100644 index 0000000000..849f7c2fa5 --- /dev/null +++ b/apps/dav/tests/unit/systemtag/systemtagmappingnode.php @@ -0,0 +1,84 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCA\DAV\Tests\Unit\SystemTag; + +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Exception\MethodNotAllowed; +use Sabre\DAV\Exception\Conflict; + +use OC\SystemTag\SystemTag; +use OCP\SystemTag\TagNotFoundException; +use OCP\SystemTag\TagAlreadyExistsException; + +class SystemTagMappingNode extends SystemTagNode { + + /** + * @var \OCA\DAV\SystemTag\SystemTagMappingNode + */ + private $node; + + /** + * @var \OCP\SystemTag\ISystemTagManager + */ + private $tagManager; + + /** + * @var \OCP\SystemTag\ISystemTagObjectMapper + */ + private $tagMapper; + + /** + * @var \OCP\SystemTag\ISystemTag + */ + private $tag; + + protected function setUp() { + parent::setUp(); + + $this->tag = new SystemTag(1, 'Test', true, false); + $this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager'); + $this->tagMapper = $this->getMock('\OCP\SystemTag\ISystemTagObjectMapper'); + + $this->node = new \OCA\DAV\SystemTag\SystemTagMappingNode( + $this->tag, + 123, + 'files', + $this->tagManager, + $this->tagMapper + ); + } + + public function testGetters() { + parent::testGetters(); + $this->assertEquals(123, $this->node->getObjectId()); + $this->assertEquals('files', $this->node->getObjectType()); + } + + public function testDeleteTag() { + $this->tagManager->expects($this->never()) + ->method('deleteTags'); + $this->tagMapper->expects($this->once()) + ->method('unassignTags') + ->with(123, 'files', 1); + + $this->node->delete(); + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + public function testDeleteTagNotFound() { + $this->tagMapper->expects($this->once()) + ->method('unassignTags') + ->with(123, 'files', 1) + ->will($this->throwException(new TagNotFoundException())); + + $this->node->delete(); + } +} diff --git a/apps/dav/tests/unit/systemtag/systemtagnode.php b/apps/dav/tests/unit/systemtag/systemtagnode.php new file mode 100644 index 0000000000..a43dda3025 --- /dev/null +++ b/apps/dav/tests/unit/systemtag/systemtagnode.php @@ -0,0 +1,103 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCA\DAV\Tests\Unit\SystemTag; + +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Exception\MethodNotAllowed; +use Sabre\DAV\Exception\Conflict; + +use OC\SystemTag\SystemTag; +use OCP\SystemTag\TagNotFoundException; +use OCP\SystemTag\TagAlreadyExistsException; + +class SystemTagNode extends \Test\TestCase { + + /** + * @var \OCA\DAV\SystemTag\SystemTagNode + */ + private $node; + + /** + * @var \OCP\SystemTag\ISystemTagManager + */ + private $tagManager; + + /** + * @var \OCP\SystemTag\ISystemTag + */ + private $tag; + + protected function setUp() { + parent::setUp(); + + $this->tag = new SystemTag(1, 'Test', true, false); + $this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager'); + + $this->node = new \OCA\DAV\SystemTag\SystemTagNode($this->tag, $this->tagManager); + } + + public function testGetters() { + $this->assertEquals('1', $this->node->getName()); + $this->assertEquals($this->tag, $this->node->getSystemTag()); + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + public function testSetName() { + $this->node->setName('2'); + } + + public function testUpdateTag() { + $this->tagManager->expects($this->once()) + ->method('updateTag') + ->with(1, 'Renamed', false, true); + $this->node->update('Renamed', false, true); + } + + /** + * @expectedException Sabre\DAV\Exception\Conflict + */ + public function testUpdateTagAlreadyExists() { + $this->tagManager->expects($this->once()) + ->method('updateTag') + ->with(1, 'Renamed', false, true) + ->will($this->throwException(new TagAlreadyExistsException())); + $this->node->update('Renamed', false, true); + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + public function testUpdateTagNotFound() { + $this->tagManager->expects($this->once()) + ->method('updateTag') + ->with(1, 'Renamed', false, true) + ->will($this->throwException(new TagNotFoundException())); + $this->node->update('Renamed', false, true); + } + + public function testDeleteTag() { + $this->tagManager->expects($this->once()) + ->method('deleteTags') + ->with('1'); + $this->node->delete(); + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + public function testDeleteTagNotFound() { + $this->tagManager->expects($this->once()) + ->method('deleteTags') + ->with('1') + ->will($this->throwException(new TagNotFoundException())); + $this->node->delete(); + } +} diff --git a/apps/dav/tests/unit/systemtag/systemtagplugin.php b/apps/dav/tests/unit/systemtag/systemtagplugin.php new file mode 100644 index 0000000000..48c9aa69f7 --- /dev/null +++ b/apps/dav/tests/unit/systemtag/systemtagplugin.php @@ -0,0 +1,308 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCA\DAV\Tests\Unit\SystemTag; + +use OC\SystemTag\SystemTag; +use OCP\SystemTag\TagAlreadyExistsException; + +class SystemTagPlugin extends \Test\TestCase { + + const ID_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::ID_PROPERTYNAME; + const DISPLAYNAME_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::DISPLAYNAME_PROPERTYNAME; + const USERVISIBLE_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::USERVISIBLE_PROPERTYNAME; + const USERASSIGNABLE_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::USERASSIGNABLE_PROPERTYNAME; + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\Tree + */ + private $tree; + + /** + * @var \OCP\SystemTag\ISystemTagManager + */ + private $tagManager; + + /** + * @var \OCA\DAV\Connector\Sabre\TagsPlugin + */ + private $plugin; + + public function setUp() { + parent::setUp(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + + $this->server = new \Sabre\DAV\Server($this->tree); + + $this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager'); + + $this->plugin = new \OCA\DAV\SystemTag\SystemTagPlugin($this->tagManager); + $this->plugin->initialize($this->server); + } + + public function testGetProperties() { + $systemTag = new SystemTag(1, 'Test', true, true); + $requestedProperties = [ + self::ID_PROPERTYNAME, + self::DISPLAYNAME_PROPERTYNAME, + self::USERVISIBLE_PROPERTYNAME, + self::USERASSIGNABLE_PROPERTYNAME + ]; + $expectedProperties = [ + 200 => [ + self::ID_PROPERTYNAME => '1', + self::DISPLAYNAME_PROPERTYNAME => 'Test', + self::USERVISIBLE_PROPERTYNAME => 1, + self::USERASSIGNABLE_PROPERTYNAME => 1, + ] + ]; + + $node = $this->getMockBuilder('\OCA\DAV\SystemTag\SystemTagNode') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getSystemTag') + ->will($this->returnValue($systemTag)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/systemtag/1') + ->will($this->returnValue($node)); + + $propFind = new \Sabre\DAV\PropFind( + '/systemtag/1', + $requestedProperties, + 0 + ); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $result = $propFind->getResultForMultiStatus(); + + $this->assertEmpty($result[404]); + unset($result[404]); + $this->assertEquals($expectedProperties, $result); + } + + public function testUpdateProperties() { + $systemTag = new SystemTag(1, 'Test', true, false); + $node = $this->getMockBuilder('\OCA\DAV\SystemTag\SystemTagNode') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getSystemTag') + ->will($this->returnValue($systemTag)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/systemtag/1') + ->will($this->returnValue($node)); + + $node->expects($this->once()) + ->method('update') + ->with('Test changed', false, true); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::DISPLAYNAME_PROPERTYNAME => 'Test changed', + self::USERVISIBLE_PROPERTYNAME => 0, + self::USERASSIGNABLE_PROPERTYNAME => 1, + )); + + $this->plugin->handleUpdateProperties( + '/systemtag/1', + $propPatch + ); + + $propPatch->commit(); + + // all requested properties removed, as they were processed already + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(200, $result[self::DISPLAYNAME_PROPERTYNAME]); + $this->assertEquals(200, $result[self::USERASSIGNABLE_PROPERTYNAME]); + $this->assertEquals(200, $result[self::USERVISIBLE_PROPERTYNAME]); + } + + public function testCreateTagInByIdCollection() { + $systemTag = new SystemTag(1, 'Test', true, false); + + $requestData = json_encode([ + 'name' => 'Test', + 'userVisible' => true, + 'userAssignable' => false, + ]); + + $node = $this->getMockBuilder('\OCA\DAV\SystemTag\SystemTagsByIdCollection') + ->disableOriginalConstructor() + ->getMock(); + $this->tagManager->expects($this->once()) + ->method('createTag') + ->with('Test', true, false) + ->will($this->returnValue($systemTag)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/systemtags') + ->will($this->returnValue($node)); + + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $request->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('/systemtags')); + + $request->expects($this->once()) + ->method('getBodyAsString') + ->will($this->returnValue($requestData)); + + $request->expects($this->once()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue('application/json')); + + $request->expects($this->once()) + ->method('getUrl') + ->will($this->returnValue('http://example.com/dav/systemtags')); + + $response->expects($this->once()) + ->method('setHeader') + ->with('Location', 'http://example.com/dav/systemtags/1'); + + $this->plugin->httpPost($request, $response); + } + + public function nodeClassProvider() { + return [ + ['\OCA\DAV\SystemTag\SystemTagsByIdCollection'], + ['\OCA\DAV\SystemTag\SystemTagsObjectMappingCollection'], + ]; + } + + public function testCreateTagInMappingCollection() { + $systemTag = new SystemTag(1, 'Test', true, false); + + $requestData = json_encode([ + 'name' => 'Test', + 'userVisible' => true, + 'userAssignable' => false, + ]); + + $node = $this->getMockBuilder('\OCA\DAV\SystemTag\SystemTagsObjectMappingCollection') + ->disableOriginalConstructor() + ->getMock(); + + $this->tagManager->expects($this->once()) + ->method('createTag') + ->with('Test', true, false) + ->will($this->returnValue($systemTag)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/systemtags-relations/files/12') + ->will($this->returnValue($node)); + + $node->expects($this->once()) + ->method('createFile') + ->with(1); + + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $request->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('/systemtags-relations/files/12')); + + $request->expects($this->once()) + ->method('getBodyAsString') + ->will($this->returnValue($requestData)); + + $request->expects($this->once()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue('application/json')); + + $request->expects($this->once()) + ->method('getBaseUrl') + ->will($this->returnValue('http://example.com/dav/')); + + $response->expects($this->once()) + ->method('setHeader') + ->with('Location', 'http://example.com/dav/systemtags/1'); + + $this->plugin->httpPost($request, $response); + } + + /** + * @dataProvider nodeClassProvider + * @expectedException Sabre\DAV\Exception\Conflict + */ + public function testCreateTagConflict($nodeClass) { + $requestData = json_encode([ + 'name' => 'Test', + 'userVisible' => true, + 'userAssignable' => false, + ]); + + $node = $this->getMockBuilder($nodeClass) + ->disableOriginalConstructor() + ->getMock(); + $this->tagManager->expects($this->once()) + ->method('createTag') + ->with('Test', true, false) + ->will($this->throwException(new TagAlreadyExistsException('Tag already exists'))); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/systemtags') + ->will($this->returnValue($node)); + + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $request->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('/systemtags')); + + $request->expects($this->once()) + ->method('getBodyAsString') + ->will($this->returnValue($requestData)); + + $request->expects($this->once()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue('application/json')); + + $this->plugin->httpPost($request, $response); + } + +} diff --git a/apps/dav/tests/unit/systemtag/systemtagsbyidcollection.php b/apps/dav/tests/unit/systemtag/systemtagsbyidcollection.php new file mode 100644 index 0000000000..104ce36603 --- /dev/null +++ b/apps/dav/tests/unit/systemtag/systemtagsbyidcollection.php @@ -0,0 +1,147 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCA\DAV\Tests\Unit\SystemTag; + + +use OC\SystemTag\SystemTag; +use OCP\SystemTag\TagNotFoundException; +use OCP\SystemTag\TagAlreadyExistsException; + +class SystemTagsByIdCollection extends \Test\TestCase { + + /** + * @var \OCA\DAV\SystemTag\SystemTagsByIdCollection + */ + private $node; + + /** + * @var \OCP\SystemTag\ISystemTagManager + */ + private $tagManager; + + protected function setUp() { + parent::setUp(); + + $this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager'); + + $this->node = new \OCA\DAV\SystemTag\SystemTagsByIdCollection($this->tagManager); + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testForbiddenCreateFile() { + $this->node->createFile('555'); + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testForbiddenCreateDirectory() { + $this->node->createDirectory('789'); + } + + public function testGetChild() { + $tag = new SystemTag(123, 'Test', true, false); + + $this->tagManager->expects($this->once()) + ->method('getTagsByIds') + ->with(['123']) + ->will($this->returnValue([$tag])); + + $childNode = $this->node->getChild('123'); + + $this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagNode', $childNode); + $this->assertEquals('123', $childNode->getName()); + $this->assertEquals($tag, $childNode->getSystemTag()); + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + public function testGetChildInvalidName() { + $this->tagManager->expects($this->once()) + ->method('getTagsByIds') + ->with(['invalid']) + ->will($this->throwException(new \InvalidArgumentException())); + + $this->node->getChild('invalid'); + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + public function testGetChildNotFound() { + $this->tagManager->expects($this->once()) + ->method('getTagsByIds') + ->with(['444']) + ->will($this->throwException(new TagNotFoundException())); + + $this->node->getChild('444'); + } + + public function testGetChildren() { + $tag1 = new SystemTag(123, 'One', true, false); + $tag2 = new SystemTag(456, 'Two', true, true); + + $this->tagManager->expects($this->once()) + ->method('getAllTags') + ->with(true) + ->will($this->returnValue([$tag1, $tag2])); + + $children = $this->node->getChildren(); + + $this->assertCount(2, $children); + + $this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagNode', $children[0]); + $this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagNode', $children[1]); + $this->assertEquals($tag1, $children[0]->getSystemTag()); + $this->assertEquals($tag2, $children[1]->getSystemTag()); + } + + public function testGetChildrenEmpty() { + $this->tagManager->expects($this->once()) + ->method('getAllTags') + ->with(true) + ->will($this->returnValue([])); + $this->assertCount(0, $this->node->getChildren()); + } + + public function testChildExists() { + $tag = new SystemTag(123, 'One', true, false); + + $this->tagManager->expects($this->once()) + ->method('getTagsByIds') + ->with(['123']) + ->will($this->returnValue([$tag])); + + $this->assertTrue($this->node->childExists('123')); + } + + public function testChildExistsNotFound() { + $this->tagManager->expects($this->once()) + ->method('getTagsByIds') + ->with(['123']) + ->will($this->throwException(new TagNotFoundException())); + + $this->assertFalse($this->node->childExists('123')); + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + public function testChildExistsBadRequest() { + $this->tagManager->expects($this->once()) + ->method('getTagsByIds') + ->with(['invalid']) + ->will($this->throwException(new \InvalidArgumentException())); + + $this->node->childExists('invalid'); + } +} diff --git a/apps/dav/tests/unit/systemtag/systemtagsobjectmappingcollection.php b/apps/dav/tests/unit/systemtag/systemtagsobjectmappingcollection.php new file mode 100644 index 0000000000..6e15bb78e7 --- /dev/null +++ b/apps/dav/tests/unit/systemtag/systemtagsobjectmappingcollection.php @@ -0,0 +1,215 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCA\DAV\Tests\Unit\SystemTag; + + +use OC\SystemTag\SystemTag; +use OCP\SystemTag\TagNotFoundException; +use OCP\SystemTag\TagAlreadyExistsException; + +class SystemTagsObjectMappingCollection extends \Test\TestCase { + + /** + * @var \OCA\DAV\SystemTag\SystemTagsObjectTypeCollection + */ + private $node; + + /** + * @var \OCP\SystemTag\ISystemTagManager + */ + private $tagManager; + + /** + * @var \OCP\SystemTag\ISystemTagMapper + */ + private $tagMapper; + + protected function setUp() { + parent::setUp(); + + $this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager'); + $this->tagMapper = $this->getMock('\OCP\SystemTag\ISystemTagObjectMapper'); + + $this->node = new \OCA\DAV\SystemTag\SystemTagsObjectMappingCollection ( + 111, + 'files', + $this->tagManager, + $this->tagMapper + ); + } + + public function testAssignTag() { + $this->tagMapper->expects($this->once()) + ->method('assignTags') + ->with(111, 'files', '555'); + + $this->node->createFile('555'); + } + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + public function testAssignTagNotFound() { + $this->tagMapper->expects($this->once()) + ->method('assignTags') + ->with(111, 'files', '555') + ->will($this->throwException(new TagNotFoundException())); + + $this->node->createFile('555'); + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testForbiddenCreateDirectory() { + $this->node->createDirectory('789'); + } + + public function testGetChild() { + $tag = new SystemTag(555, 'TheTag', true, false); + + $this->tagMapper->expects($this->once()) + ->method('haveTag') + ->with([111], 'files', '555', true) + ->will($this->returnValue(true)); + + $this->tagManager->expects($this->once()) + ->method('getTagsByIds') + ->with(['555']) + ->will($this->returnValue(['555' => $tag])); + + $childNode = $this->node->getChild('555'); + + $this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagNode', $childNode); + $this->assertEquals('555', $childNode->getName()); + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + public function testGetChildRelationNotFound() { + $this->tagMapper->expects($this->once()) + ->method('haveTag') + ->with([111], 'files', '777') + ->will($this->returnValue(false)); + + $this->node->getChild('777'); + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + public function testGetChildInvalidId() { + $this->tagMapper->expects($this->once()) + ->method('haveTag') + ->with([111], 'files', 'badid') + ->will($this->throwException(new \InvalidArgumentException())); + + $this->node->getChild('badid'); + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + public function testGetChildTagDoesNotExist() { + $this->tagMapper->expects($this->once()) + ->method('haveTag') + ->with([111], 'files', '777') + ->will($this->throwException(new TagNotFoundException())); + + $this->node->getChild('777'); + } + + public function testGetChildren() { + $tag1 = new SystemTag(555, 'TagOne', true, false); + $tag2 = new SystemTag(556, 'TagTwo', true, true); + + $this->tagMapper->expects($this->once()) + ->method('getTagIdsForObjects') + ->with([111], 'files') + ->will($this->returnValue(['111' => ['555', '556']])); + + $this->tagManager->expects($this->once()) + ->method('getTagsByIds') + ->with(['555', '556']) + ->will($this->returnValue(['555' => $tag1, '666' => $tag2])); + + $children = $this->node->getChildren(); + + $this->assertCount(2, $children); + + $this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagMappingNode', $children[0]); + $this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagMappingNode', $children[1]); + + $this->assertEquals(111, $children[0]->getObjectId()); + $this->assertEquals('files', $children[0]->getObjectType()); + $this->assertEquals($tag1, $children[0]->getSystemTag()); + + $this->assertEquals(111, $children[1]->getObjectId()); + $this->assertEquals('files', $children[1]->getObjectType()); + $this->assertEquals($tag2, $children[1]->getSystemTag()); + } + + public function testChildExists() { + $this->tagMapper->expects($this->once()) + ->method('haveTag') + ->with([111], 'files', '555') + ->will($this->returnValue(true)); + + $this->assertTrue($this->node->childExists('555')); + } + + public function testChildExistsNotFound() { + $this->tagMapper->expects($this->once()) + ->method('haveTag') + ->with([111], 'files', '555') + ->will($this->returnValue(false)); + + $this->assertFalse($this->node->childExists('555')); + } + + public function testChildExistsTagNotFound() { + $this->tagMapper->expects($this->once()) + ->method('haveTag') + ->with([111], 'files', '555') + ->will($this->throwException(new TagNotFoundException())); + + $this->assertFalse($this->node->childExists('555')); + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + public function testChildExistsInvalidId() { + $this->tagMapper->expects($this->once()) + ->method('haveTag') + ->with([111], 'files', '555') + ->will($this->throwException(new \InvalidArgumentException())); + + $this->node->childExists('555'); + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testDelete() { + $this->node->delete(); + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testSetName() { + $this->node->setName('somethingelse'); + } + + public function testGetName() { + $this->assertEquals('111', $this->node->getName()); + } +} diff --git a/apps/dav/tests/unit/systemtag/systemtagsobjecttypecollection.php b/apps/dav/tests/unit/systemtag/systemtagsobjecttypecollection.php new file mode 100644 index 0000000000..39223ff912 --- /dev/null +++ b/apps/dav/tests/unit/systemtag/systemtagsobjecttypecollection.php @@ -0,0 +1,90 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCA\DAV\Tests\Unit\SystemTag; + +class SystemTagsObjectTypeCollection extends \Test\TestCase { + + /** + * @var \OCA\DAV\SystemTag\SystemTagsObjectTypeCollection + */ + private $node; + + /** + * @var \OCP\SystemTag\ISystemTagManager + */ + private $tagManager; + + /** + * @var \OCP\SystemTag\ISystemTagMapper + */ + private $tagMapper; + + protected function setUp() { + parent::setUp(); + + $this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager'); + $this->tagMapper = $this->getMock('\OCP\SystemTag\ISystemTagObjectMapper'); + + $this->node = new \OCA\DAV\SystemTag\SystemTagsObjectTypeCollection( + 'files', + $this->tagManager, + $this->tagMapper + ); + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testForbiddenCreateFile() { + $this->node->createFile('555'); + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testForbiddenCreateDirectory() { + $this->node->createDirectory('789'); + } + + public function testGetChild() { + $childNode = $this->node->getChild('files'); + + $this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagsObjectMappingCollection', $childNode); + $this->assertEquals('files', $childNode->getName()); + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + public function testGetChildren() { + $this->node->getChildren(); + } + + public function testChildExists() { + $this->assertTrue($this->node->childExists('123')); + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testDelete() { + $this->node->delete(); + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testSetName() { + $this->node->setName('somethingelse'); + } + + public function testGetName() { + $this->assertEquals('files', $this->node->getName()); + } +} diff --git a/apps/files/admin.php b/apps/files/admin.php index 786a8edf2a..f23f9b5269 100644 --- a/apps/files/admin.php +++ b/apps/files/admin.php @@ -30,9 +30,8 @@ OCP\User::checkAdminUser(); $htaccessWorking=(getenv('htaccessWorking')=='true'); - -$upload_max_filesize = OCP\Util::computerFileSize(ini_get('upload_max_filesize')); -$post_max_size = OCP\Util::computerFileSize(ini_get('post_max_size')); +$upload_max_filesize = OC::$server->getIniWrapper()->getBytes('upload_max_filesize'); +$post_max_size = OC::$server->getIniWrapper()->getBytes('post_max_size'); $maxUploadFilesize = OCP\Util::humanFileSize(min($upload_max_filesize, $post_max_size)); if($_POST && OC_Util::isCallRegistered()) { if(isset($_POST['maxUploadSize'])) { diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php index 18e9cfe611..14f56a24b4 100644 --- a/apps/files/ajax/upload.php +++ b/apps/files/ajax/upload.php @@ -114,7 +114,7 @@ foreach ($_FILES['files']['error'] as $error) { $errors = array( UPLOAD_ERR_OK => $l->t('There is no error, the file uploaded with success'), UPLOAD_ERR_INI_SIZE => $l->t('The uploaded file exceeds the upload_max_filesize directive in php.ini: ') - . ini_get('upload_max_filesize'), + . OC::$server->getIniWrapper()->getNumeric('upload_max_filesize'), UPLOAD_ERR_FORM_SIZE => $l->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'), UPLOAD_ERR_PARTIAL => $l->t('The uploaded file was only partially uploaded'), UPLOAD_ERR_NO_FILE => $l->t('No file was uploaded'), diff --git a/apps/files_external/command/listcommand.php b/apps/files_external/command/listcommand.php index 4c027ffcb8..baba9be59f 100644 --- a/apps/files_external/command/listcommand.php +++ b/apps/files_external/command/listcommand.php @@ -29,7 +29,6 @@ use OCP\IUserManager; use OCP\IUserSession; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\Table; -use Symfony\Component\Console\Helper\TableHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; diff --git a/apps/files_external/css/external.css b/apps/files_external/css/external.css new file mode 100644 index 0000000000..bf57ec8805 --- /dev/null +++ b/apps/files_external/css/external.css @@ -0,0 +1,8 @@ +#filestable tbody tr.externalDisabledRow { + background-color: #CCC; +} + + +#filestable tbody tr.externalErroredRow { + background-color: #F2DEDE; +} diff --git a/apps/files_external/js/app.js b/apps/files_external/js/app.js index 1bff3014bd..d3ce2010ec 100644 --- a/apps/files_external/js/app.js +++ b/apps/files_external/js/app.js @@ -73,5 +73,42 @@ $(document).ready(function() { $('#app-content-extstoragemounts').on('hide', function() { OCA.External.App.removeList(); }); + + /* Status Manager */ + if ($('#filesApp').val()) { + + $('#app-content-files') + .add('#app-content-extstoragemounts') + .on('changeDirectory', function(e){ + if (e.dir === '/') { + var mount_point = e.previousDir.split('/', 2)[1]; + // Every time that we return to / root folder from a mountpoint, mount_point status is rechecked + OCA.External.StatusManager.getMountPointList(function() { + OCA.External.StatusManager.recheckConnectivityForMount([mount_point], true); + }); + } + }) + .on('fileActionsReady', function(e){ + if ($.isArray(e.$files)) { + if (OCA.External.StatusManager.mountStatus === null || + OCA.External.StatusManager.mountPointList === null || + _.size(OCA.External.StatusManager.mountStatus) !== _.size(OCA.External.StatusManager.mountPointList)) { + // Will be the very first check when the files view will be loaded + OCA.External.StatusManager.launchFullConnectivityCheckOneByOne(); + } else { + // When we change between general files view and external files view + OCA.External.StatusManager.getMountPointList(function(){ + var fileNames = []; + $.each(e.$files, function(key, value){ + fileNames.push(value.attr('data-file')); + }); + // Recheck if launched but work from cache + OCA.External.StatusManager.recheckConnectivityForMount(fileNames, false); + }); + } + } + }); + } + /* End Status Manager */ }); diff --git a/apps/files_external/js/rollingqueue.js b/apps/files_external/js/rollingqueue.js new file mode 100644 index 0000000000..58cb0fb22f --- /dev/null +++ b/apps/files_external/js/rollingqueue.js @@ -0,0 +1,137 @@ +/** + * ownCloud + * + * @author Juan Pablo Villafañez Ramos + * @author Jesus Macias Portela + * @copyright (C) 2014 ownCloud, Inc. + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function(){ +/** + * Launch several functions at thee same time. The number of functions + * running at the same time is controlled by the queueWindow param + * + * The function list come in the following format: + * + * var flist = [ + * { + * funcName: function () { + * var d = $.Deferred(); + * setTimeout(function(){d.resolve();}, 1000); + * return d; + * } + * }, + * { + * funcName: $.get, + * funcArgs: [ + * OC.filePath('files_external', 'ajax', 'connectivityCheck.php'), + * {}, + * function () { + * console.log('titoooo'); + * } + * ] + * }, + * { + * funcName: $.get, + * funcArgs: [ + * OC.filePath('files_external', 'ajax', 'connectivityCheck.php') + * ], + * done: function () { + * console.log('yuupi'); + * }, + * always: function () { + * console.log('always done'); + * } + * } + *]; + * + * functions MUST implement the deferred interface + * + * @param functionList list of functions that the queue will run + * (check example above for the expected format) + * @param queueWindow specify the number of functions that will + * be executed at the same time + */ +var RollingQueue = function (functionList, queueWindow, callback) { + this.queueWindow = queueWindow || 1; + this.functionList = functionList; + this.callback = callback; + this.counter = 0; + this.runQueue = function() { + this.callbackCalled = false; + this.deferredsList = []; + if (!$.isArray(this.functionList)) { + throw "functionList must be an array"; + } + + for (i = 0; i < this.queueWindow; i++) { + this.launchNext(); + } + }; + + this.hasNext = function() { + return (this.counter in this.functionList); + }; + + this.launchNext = function() { + var currentCounter = this.counter++; + if (currentCounter in this.functionList) { + var funcData = this.functionList[currentCounter]; + if ($.isFunction(funcData.funcName)) { + var defObj = funcData.funcName.apply(funcData.funcName, funcData.funcArgs); + this.deferredsList.push(defObj); + if ($.isFunction(funcData.done)) { + defObj.done(funcData.done); + } + + if ($.isFunction(funcData.fail)) { + defObj.fail(funcData.fail); + } + + if ($.isFunction(funcData.always)) { + defObj.always(funcData.always); + } + + if (this.hasNext()) { + var self = this; + defObj.always(function(){ + _.defer($.proxy(function(){ + self.launchNext(); + }, self)); + }); + } else { + if (!this.callbackCalled) { + this.callbackCalled = true; + if ($.isFunction(this.callback)) { + $.when.apply($, this.deferredsList) + .always($.proxy(function(){ + this.callback(); + }, this) + ); + } + } + } + return defObj; + } + } + return false; + }; +}; + +if (!OCA.External) { + OCA.External = {}; +} + +if (!OCA.External.StatusManager) { + OCA.External.StatusManager = {}; +} + +OCA.External.StatusManager.RollingQueue = RollingQueue; + +})(); \ No newline at end of file diff --git a/apps/files_external/js/statusmanager.js b/apps/files_external/js/statusmanager.js new file mode 100644 index 0000000000..4048bfc31b --- /dev/null +++ b/apps/files_external/js/statusmanager.js @@ -0,0 +1,539 @@ +/** + * ownCloud + * + * @author Juan Pablo Villafañez Ramos + * @author Jesus Macias Portela + * @copyright (C) 2014 ownCloud, Inc. + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +if (!OCA.External) { + OCA.External = {}; +} + +if (!OCA.External.StatusManager) { + OCA.External.StatusManager = {}; +} + +OCA.External.StatusManager = { + + mountStatus : null, + mountPointList : null, + + /** + * Function + * @param {callback} afterCallback + */ + + getMountStatus : function(afterCallback) { + var self = this; + if (typeof afterCallback !== 'function' || self.isGetMountStatusRunning) { + return; + } + + if (self.mountStatus) { + afterCallback(self.mountStatus); + } + }, + + /** + * Function Check mount point status from cache + * @param {string} mount_point + */ + + getMountPointListElement : function(mount_point) { + var element; + $.each(this.mountPointList, function(key, value){ + if (value.mount_point === mount_point) { + element = value; + return false; + } + }); + return element; + }, + + /** + * Function Check mount point status from cache + * @param {string} mount_point + * @param {string} mount_point + */ + + getMountStatusForMount : function(mountData, afterCallback) { + var self = this; + if (typeof afterCallback !== 'function' || self.isGetMountStatusRunning) { + return $.Deferred().resolve(); + } + + var defObj; + if (self.mountStatus[mountData.mount_point]) { + defObj = $.Deferred(); + afterCallback(mountData, self.mountStatus[mountData.mount_point]); + defObj.resolve(); // not really useful, but it'll keep the same behaviour + } else { + defObj = $.ajax({ + type : 'GET', + url: OC.webroot + '/index.php/apps/files_external/' + ((mountData.type === 'personal') ? 'userstorages' : 'globalstorages') + '/' + mountData.id, + success : function(response) { + if (response && response.status === 0) { + self.mountStatus[mountData.mount_point] = response; + } else { + if (response && response.statusMessage) { + // failure response with error message + self.mountStatus[mountData.mount_point] = { type: mountData.type, + status: 1, + error: response.statusMessage}; + } else { + self.mountStatus[mountData.mount_point] = { type: mountData.type, + status: 1, + error: t('files_external', 'Empty response from the server')}; + } + } + afterCallback(mountData, self.mountStatus[mountData.mount_point]); + }, + error : function(jqxhr, state, error) { + var message; + if(mountData.location === 3){ + // In this case the error is because mount point use Login credentials and don't exist in the session + message = t('files_external', 'Couldn\'t access. Please logout and login to activate this mount point'); + } else { + message = t('files_external', 'Couldn\'t get the information from the ownCloud server: {code} {type}', {code: jqxhr.status, type: error}); + } + self.mountStatus[mountData.mount_point] = { type: mountData.type, + status: 1, + location: mountData.location, + error: message}; + afterCallback(mountData, self.mountStatus[mountData.mount_point]); + } + }); + } + return defObj; + }, + + /** + * Function to get external mount point list from the files_external API + * @param {function} afterCallback function to be executed + */ + + getMountPointList : function(afterCallback) { + var self = this; + if (typeof afterCallback !== 'function' || self.isGetMountPointListRunning) { + return; + } + + if (self.mountPointList) { + afterCallback(self.mountPointList); + } else { + self.isGetMountPointListRunning = true; + $.ajax({ + type : 'GET', + url : OC.linkToOCS('apps/files_external/api/v1') + 'mounts?format=json', + success : function(response) { + self.mountPointList = []; + _.each(response.ocs.data, function(mount){ + var element = {}; + element.mount_point = mount.name; + element.type = mount.scope; + element.location = ""; + element.id = mount.id; + element.backendText = mount.backend; + element.backend = mount.class; + + self.mountPointList.push(element); + }); + afterCallback(self.mountPointList); + }, + error : function(jqxhr, state, error) { + self.mountPointList = []; + OC.Notification.showTemporary(t('files_external', 'Couldn\'t get the list of external mount points: {type}', {type : error})); + }, + complete : function() { + self.isGetMountPointListRunning = false; + } + }); + } + }, + + /** + * Function to manage action when a mountpoint status = 1 (Errored). Show a dialog to be redirected to settings page. + * @param {string} name MountPoint Name + */ + + manageMountPointError : function(name) { + var self = this; + this.getMountStatus($.proxy(function(allMountStatus) { + if (typeof allMountStatus[name] !== 'undefined' || allMountStatus[name].status === 1) { + var mountData = allMountStatus[name]; + if (mountData.type === "system") { + OC.dialogs.confirm(t('files_external', 'There was an error with message: ') + mountData.error + '. Do you want to review mount point config in admin settings page?', t('files_external', 'External mount error'), function(e){ + if(e === true) { + window.location.href = OC.generateUrl('/settings/admin#files_external'); + } + }); + } else { + OC.dialogs.confirm(t('files_external', 'There was an error with message: ') + mountData.error + '. Do you want to review mount point config in personal settings page?', t('files_external', 'External mount error'), function(e){ + if(e === true) { + window.location.href = OC.generateUrl('/settings/personal#' + t('files_external', 'goto-external-storage')); + } + }); + } + } + }, this)); + }, + + /** + * Function to process a mount point in relation with their status, Called from Async Queue. + * @param {object} mountData + * @param {object} mountStatus + */ + + processMountStatusIndividual : function(mountData, mountStatus) { + + var mountPoint = mountData.mount_point; + if (mountStatus.status === 1) { + var trElement = FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(mountPoint)); + + route = OCA.External.StatusManager.Utils.getIconRoute(trElement) + '-error'; + + if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) { + OCA.External.StatusManager.Utils.showIconError(mountPoint, $.proxy(OCA.External.StatusManager.manageMountPointError, OCA.External.StatusManager), route); + } + return false; + } else { + if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) { + OCA.External.StatusManager.Utils.restoreFolder(mountPoint); + OCA.External.StatusManager.Utils.toggleLink(mountPoint, true, true); + } + return true; + } + }, + + /** + * Function to process a mount point in relation with their status + * @param {object} mountData + * @param {object} mountStatus + */ + + processMountList : function(mountList) { + var elementList = null; + $.each(mountList, function(name, value){ + var trElement = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(value.mount_point) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(value.mount_point)); + trElement.attr('data-external-backend', value.backend); + if (elementList) { + elementList = elementList.add(trElement); + } else { + elementList = trElement; + } + }); + + if (elementList instanceof $) { + if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) { + // Put their custom icon + OCA.External.StatusManager.Utils.changeFolderIcon(elementList); + // Save default view + OCA.External.StatusManager.Utils.storeDefaultFolderIconAndBgcolor(elementList); + // Disable row until check status + elementList.addClass('externalDisabledRow'); + OCA.External.StatusManager.Utils.toggleLink(elementList.find('a.name'), false, false); + } + } + }, + + /** + * Function to process the whole mount point list in relation with their status (Async queue) + */ + + launchFullConnectivityCheckOneByOne : function() { + var self = this; + this.getMountPointList(function(list){ + // check if we have a list first + if (list === undefined && !self.emptyWarningShown) { + self.emptyWarningShown = true; + OC.Notification.showTemporary(t('files_external', 'Couldn\'t get the list of Windows network drive mount points: empty response from the server')); + return; + } + if (list && list.length > 0) { + self.processMountList(list); + + if (!self.mountStatus) { + self.mountStatus = {}; + } + + var ajaxQueue = []; + $.each(list, function(key, value){ + var queueElement = {funcName: $.proxy(self.getMountStatusForMount, self), + funcArgs: [value, + $.proxy(self.processMountStatusIndividual, self)]}; + ajaxQueue.push(queueElement); + }); + + var rolQueue = new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4, function(){ + if (!self.notificationHasShown) { + var showNotification = false; + $.each(self.mountStatus, function(key, value){ + if (value.status === 1) { + self.notificationHasShown = true; + showNotification = true; + } + }); + if (showNotification) { + OC.Notification.showTemporary(t('files_external', 'Some of the configured external mount points are not connected. Please click on the red row(s) for more information')); + } + } + }); + rolQueue.runQueue(); + } + }); + }, + + + /** + * Function to process a mount point list in relation with their status (Async queue) + * @param {object} mountListData + * @param {boolean} recheck delete cached info and force api call to check mount point status + */ + + launchPartialConnectivityCheck : function(mountListData, recheck) { + if (mountListData.length === 0) { + return; + } + + var self = this; + var ajaxQueue = []; + $.each(mountListData, function(key, value){ + if (recheck && value.mount_point in self.mountStatus) { + delete self.mountStatus[value.mount_point]; + } + var queueElement = {funcName: $.proxy(self.getMountStatusForMount, self), + funcArgs: [value, + $.proxy(self.processMountStatusIndividual, self)]}; + ajaxQueue.push(queueElement); + }); + new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4).runQueue(); + }, + + + /** + * Function to relaunch some mount point status check + * @param {string} mountListNames + * @param {boolean} recheck delete cached info and force api call to check mount point status + */ + + recheckConnectivityForMount : function(mountListNames, recheck) { + if (mountListNames.length === 0) { + return; + } + + var self = this; + var mountListData = []; + var recheckPersonalGlobal = false; + var recheckAdminGlobal = false; + + if (!self.mountStatus) { + self.mountStatus = {}; + } + + $.each(mountListNames, function(key, value){ + var mountData = self.getMountPointListElement(value); + if (mountData) { + mountListData.push(mountData); + } + }); + + // for all mounts in the list, delete the cached status values + if (recheck) { + $.each(mountListData, function(key, value){ + if (value.mount_point in self.mountStatus) { + delete self.mountStatus[value.mount_point]; + } + }); + } + + self.processMountList(mountListData); + self.launchPartialConnectivityCheck(mountListData, recheck); + } +}; + +OCA.External.StatusManager.Utils = { + + showIconError: function(folder, clickAction, errorImageUrl) { + var imageUrl = "url(" + errorImageUrl + ")"; + var trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); + this.changeFolderIcon(folder, imageUrl); + this.toggleLink(folder, false, clickAction); + trFolder.addClass('externalErroredRow'); + }, + + /** + * @param folder string with the folder or jQuery element pointing to the tr element + */ + storeDefaultFolderIconAndBgcolor: function(folder) { + var trFolder; + if (folder instanceof $) { + trFolder = folder; + } else { + trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); //$('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); + } + trFolder.each(function(){ + var thisElement = $(this); + if (thisElement.data('oldbgcolor') === undefined) { + thisElement.data('oldbgcolor', thisElement.css('background-color')); + } + }); + + var icon = trFolder.find('td:first-child div.thumbnail'); + icon.each(function(){ + var thisElement = $(this); + if (thisElement.data('oldImage') === undefined) { + thisElement.data('oldImage', thisElement.css('background-image')); + } + }); + }, + + /** + * @param folder string with the folder or jQuery element pointing to the tr element + */ + restoreFolder: function(folder) { + var trFolder; + if (folder instanceof $) { + trFolder = folder; + } else { + // cant use here FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); return incorrect instance of filelist + trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); + } + trFolder.removeClass('externalErroredRow').removeClass('externalDisabledRow'); + tdChilds = trFolder.find("td:first-child div.thumbnail"); + tdChilds.each(function(){ + var thisElement = $(this); + thisElement.css('background-image', thisElement.data('oldImage')); + }); + }, + + /** + * @param folder string with the folder or jQuery element pointing to the first td element + * of the tr matching the folder name + */ + changeFolderIcon: function(filename) { + var file; + var route; + if (filename instanceof $) { + //trElementList + $.each(filename, function(index){ + route = OCA.External.StatusManager.Utils.getIconRoute($(this)); + $(this).attr("data-icon", route); + $(this).find('td:first-child div.thumbnail').css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline'); + }); + } else { + file = $("#fileList tr[data-file=\"" + this.jqSelEscape(filename) + "\"] > td:first-child div.thumbnail"); + parentTr = file.parents('tr:first'); + route = OCA.External.StatusManager.Utils.getIconRoute(parentTr); + parentTr.attr("data-icon", route); + file.css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline'); + } + }, + + /** + * @param backend string with the name of the external storage backend + * of the tr matching the folder name + */ + getIconRoute: function(tr) { + var icon = OC.imagePath('core', 'filetypes/folder-external'); + var backend = null; + + if (tr instanceof $) { + backend = tr.attr('data-external-backend'); + } + + switch (backend) { + case 'smb': + icon = OC.imagePath('windows_network_drive', 'folder-windows'); + break; + case 'sharepoint': + icon = OC.imagePath('sharepoint', 'folder-sharepoint'); + break; + case 'amazons3': + icon = OC.imagePath('core', 'filetypes/folder-external'); + break; + case 'dav': + icon = OC.imagePath('core', 'filetypes/folder-external'); + break; + case 'dropbox': + icon = OC.imagePath('core', 'filetypes/folder-external'); + break; + case 'ftp': + icon = OC.imagePath('core', 'filetypes/folder-external'); + break; + case 'google': + icon = OC.imagePath('core', 'filetypes/folder-external'); + break; + case 'owncloud': + icon = OC.imagePath('core', 'filetypes/folder-external'); + break; + case 'sftp': + icon = OC.imagePath('core', 'filetypes/folder-external'); + break; + case 'swift': + icon = OC.imagePath('core', 'filetypes/folder-external'); + break; + } + + return icon; + }, + + toggleLink: function(filename, active, action) { + var link; + if (filename instanceof $) { + link = filename; + } else { + link = $("#fileList tr[data-file=\"" + this.jqSelEscape(filename) + "\"] > td:first-child a.name"); + } + if (active) { + link.off('click.connectivity'); + OCA.Files.App.fileList.fileActions.display(link.parent(), true, OCA.Files.App.fileList); + } else { + link.find('.fileactions, .nametext .action').remove(); // from files/js/fileactions (display) + link.off('click.connectivity'); + link.on('click.connectivity', function(e){ + if (action && $.isFunction(action)) { + action(filename); + } + e.preventDefault(); + return false; + }); + } + }, + + isCorrectViewAndRootFolder: function() { + // correct views = files & extstoragemounts + if (OCA.Files.App.getActiveView() === 'files' || OCA.Files.App.getActiveView() === 'extstoragemounts') { + return OCA.Files.App.getCurrentAppContainer().find('#dir').val() === '/'; + } + return false; + }, + + /* escape a selector expression for jQuery */ + jqSelEscape: function(expression) { + if(expression){ + return expression.replace(/[!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&'); + } + return null; + }, + + /* Copied from http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key */ + checkNested: function(cobj /*, level1, level2, ... levelN*/) { + var args = Array.prototype.slice.call(arguments), + obj = args.shift(); + + for (var i = 0; i < args.length; i++) { + if (!obj || !obj.hasOwnProperty(args[i])) { + return false; + } + obj = obj[args[i]]; + } + return true; + } +}; diff --git a/apps/files_external/l10n/es.js b/apps/files_external/l10n/es.js index 1d49d7b13b..6d8bf0d313 100644 --- a/apps/files_external/l10n/es.js +++ b/apps/files_external/l10n/es.js @@ -1,13 +1,23 @@ OC.L10N.register( "files_external", { + "Fetching request tokens failed. Verify that your app key and secret are correct." : "Fallo al acceder a los tokens solicitados. Verfique que su clave de app y la clave secreta son correctas.", + "Fetching access tokens failed. Verify that your app key and secret are correct." : "Fallo al acceder a los tokens solicitados. Verfique que su clave de app y la clave secreta son correctas.", + "Please provide a valid app key and secret." : "Por favor facilite una clave de app y una clave secreta válidas.", "Step 1 failed. Exception: %s" : "El paso 1 falló. Excepción: %s", "Step 2 failed. Exception: %s" : "El paso 2 falló. Excepción: %s", "External storage" : "Almacenamiento externo", "Storage with id \"%i\" not found" : "No se ha encontrado almacenamiento con id \"%i\"", + "Invalid backend or authentication mechanism class" : "Sistema o mecanismo de autentificación inválido", "Invalid mount point" : "Punto de montaje no válido", + "Objectstore forbidden" : "Objeto de almacenaje prohibido", "Invalid storage backend \"%s\"" : "Motor de almacenamiento no válido «%s»", - "Not permitted to use authentication mechanism \"%s\"" : "No está permitido usar el mecanismo de autenticación \"%s\"", + "Not permitted to use backend \"%s\"" : "No se permite usar el mecanismo \"%s\"", + "Not permitted to use authentication mechanism \"%s\"" : "No está permitido usar el mecanismo de autentificación \"%s\"", + "Unsatisfied backend parameters" : "Los parámetros del sistema no son válidos", + "Unsatisfied authentication mechanism parameters" : "Los parámetros del mecanismo de autentificación no son válidos", + "Insufficient data: %s" : "Datos insuficientes: %s", + "%s" : "%s", "Personal" : "Personal", "System" : "Sistema", "Grant access" : "Conceder acceso", @@ -84,7 +94,7 @@ OC.L10N.register( "Scope" : "Ámbito", "External Storage" : "Almacenamiento externo", "Folder name" : "Nombre de la carpeta", - "Authentication" : "Autenticación", + "Authentication" : "Autentificación", "Configuration" : "Configuración", "Available for" : "Disponible para", "Add storage" : "Añadir almacenamiento", diff --git a/apps/files_external/l10n/es.json b/apps/files_external/l10n/es.json index 1c60cd5fde..bf0624e96d 100644 --- a/apps/files_external/l10n/es.json +++ b/apps/files_external/l10n/es.json @@ -1,11 +1,21 @@ { "translations": { + "Fetching request tokens failed. Verify that your app key and secret are correct." : "Fallo al acceder a los tokens solicitados. Verfique que su clave de app y la clave secreta son correctas.", + "Fetching access tokens failed. Verify that your app key and secret are correct." : "Fallo al acceder a los tokens solicitados. Verfique que su clave de app y la clave secreta son correctas.", + "Please provide a valid app key and secret." : "Por favor facilite una clave de app y una clave secreta válidas.", "Step 1 failed. Exception: %s" : "El paso 1 falló. Excepción: %s", "Step 2 failed. Exception: %s" : "El paso 2 falló. Excepción: %s", "External storage" : "Almacenamiento externo", "Storage with id \"%i\" not found" : "No se ha encontrado almacenamiento con id \"%i\"", + "Invalid backend or authentication mechanism class" : "Sistema o mecanismo de autentificación inválido", "Invalid mount point" : "Punto de montaje no válido", + "Objectstore forbidden" : "Objeto de almacenaje prohibido", "Invalid storage backend \"%s\"" : "Motor de almacenamiento no válido «%s»", - "Not permitted to use authentication mechanism \"%s\"" : "No está permitido usar el mecanismo de autenticación \"%s\"", + "Not permitted to use backend \"%s\"" : "No se permite usar el mecanismo \"%s\"", + "Not permitted to use authentication mechanism \"%s\"" : "No está permitido usar el mecanismo de autentificación \"%s\"", + "Unsatisfied backend parameters" : "Los parámetros del sistema no son válidos", + "Unsatisfied authentication mechanism parameters" : "Los parámetros del mecanismo de autentificación no son válidos", + "Insufficient data: %s" : "Datos insuficientes: %s", + "%s" : "%s", "Personal" : "Personal", "System" : "Sistema", "Grant access" : "Conceder acceso", @@ -82,7 +92,7 @@ "Scope" : "Ámbito", "External Storage" : "Almacenamiento externo", "Folder name" : "Nombre de la carpeta", - "Authentication" : "Autenticación", + "Authentication" : "Autentificación", "Configuration" : "Configuración", "Available for" : "Disponible para", "Add storage" : "Añadir almacenamiento", diff --git a/apps/files_external/l10n/fr.js b/apps/files_external/l10n/fr.js index 8ff4fcdfdd..8d5f7e911b 100644 --- a/apps/files_external/l10n/fr.js +++ b/apps/files_external/l10n/fr.js @@ -17,6 +17,7 @@ OC.L10N.register( "Unsatisfied backend parameters" : "Paramètres manquants pour le service", "Unsatisfied authentication mechanism parameters" : "Paramètres manquants pour la méthode d'authentification", "Insufficient data: %s" : "Données insuffisantes : %s", + "%s" : "%s", "Personal" : "Personnel", "System" : "Système", "Grant access" : "Autoriser l'accès", diff --git a/apps/files_external/l10n/fr.json b/apps/files_external/l10n/fr.json index 9a610bd964..cae66119a4 100644 --- a/apps/files_external/l10n/fr.json +++ b/apps/files_external/l10n/fr.json @@ -15,6 +15,7 @@ "Unsatisfied backend parameters" : "Paramètres manquants pour le service", "Unsatisfied authentication mechanism parameters" : "Paramètres manquants pour la méthode d'authentification", "Insufficient data: %s" : "Données insuffisantes : %s", + "%s" : "%s", "Personal" : "Personnel", "System" : "Système", "Grant access" : "Autoriser l'accès", diff --git a/apps/files_external/l10n/lt_LT.js b/apps/files_external/l10n/lt_LT.js index dabdeb8bd3..3a871070f4 100644 --- a/apps/files_external/l10n/lt_LT.js +++ b/apps/files_external/l10n/lt_LT.js @@ -3,6 +3,7 @@ OC.L10N.register( { "Fetching request tokens failed. Verify that your app key and secret are correct." : "Nepavyko atsiųsti užklausos žymės. Patikrinkite savo programos raktą ir paslaptį.", "Step 1 failed. Exception: %s" : "1 žingsnio klaida: %s", + "Step 2 failed. Exception: %s" : "2 žingsnio klaida: %s", "External storage" : "Išorinė saugykla", "Personal" : "Asmeniniai", "Grant access" : "Suteikti priėjimą", diff --git a/apps/files_external/l10n/lt_LT.json b/apps/files_external/l10n/lt_LT.json index 2b05c277ee..854f753aca 100644 --- a/apps/files_external/l10n/lt_LT.json +++ b/apps/files_external/l10n/lt_LT.json @@ -1,6 +1,7 @@ { "translations": { "Fetching request tokens failed. Verify that your app key and secret are correct." : "Nepavyko atsiųsti užklausos žymės. Patikrinkite savo programos raktą ir paslaptį.", "Step 1 failed. Exception: %s" : "1 žingsnio klaida: %s", + "Step 2 failed. Exception: %s" : "2 žingsnio klaida: %s", "External storage" : "Išorinė saugykla", "Personal" : "Asmeniniai", "Grant access" : "Suteikti priėjimą", diff --git a/apps/files_external/l10n/nl.js b/apps/files_external/l10n/nl.js index 05d1a3f6de..5051689216 100644 --- a/apps/files_external/l10n/nl.js +++ b/apps/files_external/l10n/nl.js @@ -17,6 +17,7 @@ OC.L10N.register( "Unsatisfied backend parameters" : "Onvoldoende backend parameters", "Unsatisfied authentication mechanism parameters" : "Onvoldoende authenticatiemechanisme parameters", "Insufficient data: %s" : "Onvoldoende gegevens: %s", + "%s" : "%s", "Personal" : "Persoonlijk", "System" : "Systeem", "Grant access" : "Sta toegang toe", diff --git a/apps/files_external/l10n/nl.json b/apps/files_external/l10n/nl.json index e30870e4ae..d8a254bad1 100644 --- a/apps/files_external/l10n/nl.json +++ b/apps/files_external/l10n/nl.json @@ -15,6 +15,7 @@ "Unsatisfied backend parameters" : "Onvoldoende backend parameters", "Unsatisfied authentication mechanism parameters" : "Onvoldoende authenticatiemechanisme parameters", "Insufficient data: %s" : "Onvoldoende gegevens: %s", + "%s" : "%s", "Personal" : "Persoonlijk", "System" : "Systeem", "Grant access" : "Sta toegang toe", diff --git a/apps/files_external/l10n/th_TH.js b/apps/files_external/l10n/th_TH.js index 6c1efa5aac..bb9c2dc909 100644 --- a/apps/files_external/l10n/th_TH.js +++ b/apps/files_external/l10n/th_TH.js @@ -17,6 +17,7 @@ OC.L10N.register( "Unsatisfied backend parameters" : "พารามิเตอร์แบ็กเอนด์ไม่ได้รับอนุญาต", "Unsatisfied authentication mechanism parameters" : "การรับรองความถูกต้องไม่เพียงพอ", "Insufficient data: %s" : "ข้อมูลไม่เพียงพอ: %s", + "%s" : "%s", "Personal" : "ส่วนตัว", "System" : "ระบบ", "Grant access" : "อนุญาตให้เข้าถึงได้", diff --git a/apps/files_external/l10n/th_TH.json b/apps/files_external/l10n/th_TH.json index 3de48d733d..f38d99ae88 100644 --- a/apps/files_external/l10n/th_TH.json +++ b/apps/files_external/l10n/th_TH.json @@ -15,6 +15,7 @@ "Unsatisfied backend parameters" : "พารามิเตอร์แบ็กเอนด์ไม่ได้รับอนุญาต", "Unsatisfied authentication mechanism parameters" : "การรับรองความถูกต้องไม่เพียงพอ", "Insufficient data: %s" : "ข้อมูลไม่เพียงพอ: %s", + "%s" : "%s", "Personal" : "ส่วนตัว", "System" : "ระบบ", "Grant access" : "อนุญาตให้เข้าถึงได้", diff --git a/apps/files_external/lib/api.php b/apps/files_external/lib/api.php index af9b802e52..f0c9e568c9 100644 --- a/apps/files_external/lib/api.php +++ b/apps/files_external/lib/api.php @@ -28,7 +28,7 @@ class Api { /** * Formats the given mount config to a mount entry. - * + * * @param string $mountPoint mount point name, relative to the data dir * @param array $mountConfig mount config to format * @@ -59,7 +59,9 @@ class Api { 'type' => 'dir', 'backend' => $mountConfig['backend'], 'scope' => ( $isSystemMount ? 'system' : 'personal' ), - 'permissions' => $permissions + 'permissions' => $permissions, + 'id' => $mountConfig['id'], + 'class' => $mountConfig['class'] ); return $entry; } diff --git a/apps/files_external/lib/smb.php b/apps/files_external/lib/smb.php index f58cd9849f..a94840ead5 100644 --- a/apps/files_external/lib/smb.php +++ b/apps/files_external/lib/smb.php @@ -33,7 +33,6 @@ use Icewind\SMB\Exception\Exception; use Icewind\SMB\Exception\NotFoundException; use Icewind\SMB\NativeServer; use Icewind\SMB\Server; -use Icewind\Streams\CallbackWrapper; use Icewind\Streams\IteratorDirectory; use OC\Files\Filesystem; diff --git a/apps/files_external/lib/storageconfig.php b/apps/files_external/lib/storageconfig.php index 97e0386be7..49a40a9a5d 100644 --- a/apps/files_external/lib/storageconfig.php +++ b/apps/files_external/lib/storageconfig.php @@ -163,7 +163,7 @@ class StorageConfig implements \JsonSerializable { } /** - * @param Backend + * @param Backend $backend */ public function setBackend(Backend $backend) { $this->backend= $backend; @@ -177,7 +177,7 @@ class StorageConfig implements \JsonSerializable { } /** - * @param AuthMechanism + * @param AuthMechanism $authMechanism */ public function setAuthMechanism(AuthMechanism $authMechanism) { $this->authMechanism = $authMechanism; diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php index e946e7feb7..a64a02a4ed 100644 --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -354,9 +354,18 @@ class Swift extends \OC\Files\Storage\Common { } $tmpFile = \OCP\Files::tmpFile($ext); \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); - if ($this->file_exists($path)) { + // Fetch existing file if required + if ($mode[0] !== 'w' && $this->file_exists($path)) { + if ($mode[0] === 'x') { + // File cannot already exist + return false; + } $source = $this->fopen($path, 'r'); file_put_contents($tmpFile, $source); + // Seek to end if required + if ($mode[0] === 'a') { + fseek($tmpFile, 0, SEEK_END); + } } self::$tmpFiles[$tmpFile] = $path; diff --git a/apps/files_external/list.php b/apps/files_external/list.php index b98db79de8..4bbe5588c2 100644 --- a/apps/files_external/list.php +++ b/apps/files_external/list.php @@ -23,6 +23,11 @@ OCP\User::checkLoggedIn(); $tmpl = new OCP\Template('files_external', 'list', ''); +/* Load Status Manager */ +\OCP\Util::addStyle('files_external', 'external'); +\OCP\Util::addScript('files_external', 'statusmanager'); +\OCP\Util::addScript('files_external', 'rollingqueue'); + OCP\Util::addScript('files_external', 'app'); OCP\Util::addScript('files_external', 'mountsfilelist'); diff --git a/apps/files_external/tests/amazons3migration.php b/apps/files_external/tests/amazons3migration.php index 33fb6119a9..cc47107c7f 100644 --- a/apps/files_external/tests/amazons3migration.php +++ b/apps/files_external/tests/amazons3migration.php @@ -130,6 +130,9 @@ class AmazonS3Migration extends \Test\TestCase { return $storages; } + /** + * @param string $id + */ public function deleteStorage($id) { $stmt = \OC::$server->getDatabaseConnection()->prepare( 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?' diff --git a/apps/files_external/tests/controller/storagescontrollertest.php b/apps/files_external/tests/controller/storagescontrollertest.php index 8a7acf8100..747bcd46e1 100644 --- a/apps/files_external/tests/controller/storagescontrollertest.php +++ b/apps/files_external/tests/controller/storagescontrollertest.php @@ -48,6 +48,9 @@ abstract class StoragesControllerTest extends \Test\TestCase { \OC_Mount_Config::$skipTest = false; } + /** + * @return \OCA\Files_External\Lib\Backend\Backend + */ protected function getBackendMock($class = '\OCA\Files_External\Lib\Backend\SMB', $storageClass = '\OC\Files\Storage\SMB') { $backend = $this->getMockBuilder('\OCA\Files_External\Lib\Backend\Backend') ->disableOriginalConstructor() @@ -59,6 +62,9 @@ abstract class StoragesControllerTest extends \Test\TestCase { return $backend; } + /** + * @return \OCA\Files_External\Lib\Auth\AuthMechanism + */ protected function getAuthMechMock($scheme = 'null', $class = '\OCA\Files_External\Lib\Auth\NullMechanism') { $authMech = $this->getMockBuilder('\OCA\Files_External\Lib\Auth\AuthMechanism') ->disableOriginalConstructor() diff --git a/apps/files_external/tests/service/backendservicetest.php b/apps/files_external/tests/service/backendservicetest.php index 5097b479a5..e9cb0e2c36 100644 --- a/apps/files_external/tests/service/backendservicetest.php +++ b/apps/files_external/tests/service/backendservicetest.php @@ -35,6 +35,11 @@ class BackendServiceTest extends \Test\TestCase { $this->l10n = $this->getMock('\OCP\IL10N'); } + /** + * @param string $class + * + * @return \OCA\Files_External\Lib\Backend\Backend + */ protected function getBackendMock($class) { $backend = $this->getMockBuilder('\OCA\Files_External\Lib\Backend\Backend') ->disableOriginalConstructor() diff --git a/apps/files_sharing/appinfo/application.php b/apps/files_sharing/appinfo/application.php index ffe3a6a513..6af450405a 100644 --- a/apps/files_sharing/appinfo/application.php +++ b/apps/files_sharing/appinfo/application.php @@ -25,7 +25,6 @@ namespace OCA\Files_Sharing\AppInfo; -use OCA\Files_Sharing\Helper; use OCA\Files_Sharing\MountProvider; use OCP\AppFramework\App; use OC\AppFramework\Utility\SimpleContainer; diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js index 0b78d200b9..70c1ba5c0c 100644 --- a/apps/files_sharing/js/public.js +++ b/apps/files_sharing/js/public.js @@ -102,12 +102,12 @@ OCA.Sharing.PublicApp = { // dynamically load image previews var bottomMargin = 350; - var previewWidth = Math.ceil($(window).width() * window.devicePixelRatio); - var previewHeight = Math.ceil(($(window).height() - bottomMargin) * window.devicePixelRatio); + var previewWidth = $(window).width(); + var previewHeight = $(window).height() - bottomMargin; previewHeight = Math.max(200, previewHeight); var params = { - x: previewWidth, - y: previewHeight, + x: Math.ceil(previewWidth * window.devicePixelRatio), + y: Math.ceil(previewHeight * window.devicePixelRatio), a: 'true', file: encodeURIComponent(this.initialDir + $('#filename').val()), t: token, @@ -115,6 +115,10 @@ OCA.Sharing.PublicApp = { }; var img = $(''); + img.css({ + 'max-width': previewWidth, + 'max-height': previewHeight + }); var fileSize = parseInt($('#filesize').val(), 10); var maxGifSize = parseInt($('#maxSizeAnimateGif').val(), 10); diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index 2e615e231f..c903241378 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -98,7 +98,7 @@ class Shared_Cache extends Cache { /** * get the stored metadata of a file or folder * - * @param string|int $file + * @param string $file * @return array|false */ public function get($file) { diff --git a/apps/files_sharing/lib/controllers/externalsharescontroller.php b/apps/files_sharing/lib/controllers/externalsharescontroller.php index edf065ab47..ec57606566 100644 --- a/apps/files_sharing/lib/controllers/externalsharescontroller.php +++ b/apps/files_sharing/lib/controllers/externalsharescontroller.php @@ -45,7 +45,6 @@ class ExternalSharesController extends Controller { /** * @param string $appName * @param IRequest $request - * @param bool $incomingShareEnabled * @param \OCA\Files_Sharing\External\Manager $externalManager * @param IClientService $clientService */ @@ -84,7 +83,7 @@ class ExternalSharesController extends Controller { * @NoAdminRequired * @NoOutgoingFederatedSharingRequired * - * @param $id + * @param integer $id * @return JSONResponse */ public function destroy($id) { diff --git a/apps/files_sharing/lib/controllers/sharecontroller.php b/apps/files_sharing/lib/controllers/sharecontroller.php index 4b446d79ad..fe7b159449 100644 --- a/apps/files_sharing/lib/controllers/sharecontroller.php +++ b/apps/files_sharing/lib/controllers/sharecontroller.php @@ -124,7 +124,7 @@ class ShareController extends Controller { * @UseSession * * Authenticates against password-protected shares - * @param $token + * @param string $token * @param string $password * @return RedirectResponse|TemplateResponse */ diff --git a/apps/files_sharing/lib/middleware/sharingcheckmiddleware.php b/apps/files_sharing/lib/middleware/sharingcheckmiddleware.php index 22b9d32a27..04dd28574d 100644 --- a/apps/files_sharing/lib/middleware/sharingcheckmiddleware.php +++ b/apps/files_sharing/lib/middleware/sharingcheckmiddleware.php @@ -27,7 +27,6 @@ namespace OCA\Files_Sharing\Middleware; use OCP\App\IAppManager; use OCP\AppFramework\Http\NotFoundResponse; use OCP\AppFramework\Middleware; -use OCP\AppFramework\Http\TemplateResponse; use OCP\Files\NotFoundException; use OCP\IConfig; use OCP\AppFramework\Utility\IControllerMethodReflector; diff --git a/apps/files_sharing/lib/sharedmount.php b/apps/files_sharing/lib/sharedmount.php index 275fea97c7..f205b1e78a 100644 --- a/apps/files_sharing/lib/sharedmount.php +++ b/apps/files_sharing/lib/sharedmount.php @@ -48,6 +48,12 @@ class SharedMount extends MountPoint implements MoveableMount { */ private $user; + /** + * @param string $storage + * @param string $mountpoint + * @param array|null $arguments + * @param \OCP\Files\Storage\IStorageFactory $loader + */ public function __construct($storage, $mountpoint, $arguments = null, $loader = null) { $this->user = $arguments['user']; $this->recipientView = new View('/' . $this->user . '/files'); @@ -59,6 +65,9 @@ class SharedMount extends MountPoint implements MoveableMount { /** * check if the parent folder exists otherwise move the mount point up + * + * @param array $share + * @return string */ private function verifyMountPoint(&$share) { @@ -121,6 +130,7 @@ class SharedMount extends MountPoint implements MoveableMount { * * @param string $path the absolute path * @return string e.g. turns '/admin/files/test.txt' into '/test.txt' + * @throws \OCA\Files_Sharing\Exceptions\BrokenPath */ protected function stripUserFilesPath($path) { $trimmed = ltrim($path, '/'); @@ -183,7 +193,7 @@ class SharedMount extends MountPoint implements MoveableMount { */ public function removeMount() { $mountManager = \OC\Files\Filesystem::getMountManager(); - /** @var \OC\Files\Storage\Shared */ + /** @var $storage \OC\Files\Storage\Shared */ $storage = $this->getStorage(); $result = $storage->unshareStorage(); $mountManager->removeMount($this->mountPoint); @@ -191,7 +201,12 @@ class SharedMount extends MountPoint implements MoveableMount { return $result; } + /** + * @return array + */ public function getShare() { - return $this->getStorage()->getShare(); + /** @var $storage \OC\Files\Storage\Shared */ + $storage = $this->getStorage(); + return $storage->getShare(); } } diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 4807b5ee73..cda3f564d5 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -32,8 +32,6 @@ namespace OC\Files\Storage; use OC\Files\Filesystem; use OCA\Files_Sharing\ISharedStorage; -use OCA\Files_Sharing\Propagator; -use OCA\Files_Sharing\SharedMount; use OCP\Lock\ILockingProvider; /** diff --git a/apps/files_sharing/tests/api/shareestest.php b/apps/files_sharing/tests/api/shareestest.php index a3e3a6dee6..96ffe4682c 100644 --- a/apps/files_sharing/tests/api/shareestest.php +++ b/apps/files_sharing/tests/api/shareestest.php @@ -88,6 +88,11 @@ class ShareesTest extends TestCase { ); } + /** + * @param string $uid + * @param string $displayName + * @return \OCP\IUser|\PHPUnit_Framework_MockObject_MockObject + */ protected function getUserMock($uid, $displayName) { $user = $this->getMockBuilder('OCP\IUser') ->disableOriginalConstructor() @@ -104,6 +109,10 @@ class ShareesTest extends TestCase { return $user; } + /** + * @param string $gid + * @return \OCP\IGroup|\PHPUnit_Framework_MockObject_MockObject + */ protected function getGroupMock($gid) { $group = $this->getMockBuilder('OCP\IGroup') ->disableOriginalConstructor() diff --git a/apps/files_sharing/tests/locking.php b/apps/files_sharing/tests/locking.php index 3b8900f206..ad7b6b3e8a 100644 --- a/apps/files_sharing/tests/locking.php +++ b/apps/files_sharing/tests/locking.php @@ -24,7 +24,6 @@ namespace OCA\Files_sharing\Tests; use OC\Files\Filesystem; use OC\Files\View; -use OC\Lock\MemcacheLockingProvider; use OCP\Lock\ILockingProvider; /** diff --git a/apps/files_sharing/tests/share.php b/apps/files_sharing/tests/share.php index b5ba0e3ad5..05013ad2e0 100644 --- a/apps/files_sharing/tests/share.php +++ b/apps/files_sharing/tests/share.php @@ -220,6 +220,10 @@ class Test_Files_Sharing extends OCA\Files_sharing\Tests\TestCase { } + /** + * @param OC\Files\FileInfo[] $content + * @param string[] $expected + */ public function verifyDirContent($content, $expected) { foreach ($content as $c) { if (!in_array($c['name'], $expected)) { diff --git a/apps/files_trashbin/lib/trashbin.php b/apps/files_trashbin/lib/trashbin.php index d492810b95..bd6798f0ef 100644 --- a/apps/files_trashbin/lib/trashbin.php +++ b/apps/files_trashbin/lib/trashbin.php @@ -147,7 +147,7 @@ class Trashbin { * * @param string $sourcePath * @param string $owner - * @param $targetPath + * @param string $targetPath * @param $user * @param integer $timestamp */ @@ -214,13 +214,13 @@ class Trashbin { /** @var \OC\Files\Storage\Storage $sourceStorage */ list($sourceStorage, $sourceInternalPath) = $ownerView->resolvePath('/files/' . $ownerPath); try { - $sizeOfAddedFiles = $sourceStorage->filesize($sourceInternalPath); + $moveSuccessful = true; if ($trashStorage->file_exists($trashInternalPath)) { $trashStorage->unlink($trashInternalPath); } $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath); } catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) { - $sizeOfAddedFiles = false; + $moveSuccessful = false; if ($trashStorage->file_exists($trashInternalPath)) { $trashStorage->unlink($trashInternalPath); } @@ -234,7 +234,7 @@ class Trashbin { $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath); - if ($sizeOfAddedFiles !== false) { + if ($moveSuccessful) { $query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)"); $result = $query->execute(array($filename, $timestamp, $location, $owner)); if (!$result) { @@ -258,7 +258,7 @@ class Trashbin { self::scheduleExpire($owner); } - return ($sizeOfAddedFiles === false) ? false : true; + return $moveSuccessful; } /** @@ -268,18 +268,14 @@ class Trashbin { * @param string $owner owner user id * @param string $ownerPath path relative to the owner's home storage * @param integer $timestamp when the file was deleted - * - * @return int size of stored versions */ private static function retainVersions($filename, $owner, $ownerPath, $timestamp) { - $size = 0; if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) { $user = \OCP\User::getUser(); $rootView = new \OC\Files\View('/'); if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) { - $size += self::calculateSize(new \OC\Files\View('/' . $owner . '/files_versions/' . $ownerPath)); if ($owner !== $user) { self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView); } @@ -287,7 +283,6 @@ class Trashbin { } else if ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) { foreach ($versions as $v) { - $size += $rootView->filesize($owner . '/files_versions/' . $v['path'] . '.v' . $v['version']); if ($owner !== $user) { self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp); } @@ -295,8 +290,6 @@ class Trashbin { } } } - - return $size; } /** @@ -417,7 +410,7 @@ class Trashbin { * @param string $uniqueFilename new file name to restore the file without overwriting existing files * @param string $location location if file * @param int $timestamp deletion time - * @return bool + * @return false|null */ private static function restoreVersions(\OC\Files\View $view, $file, $filename, $uniqueFilename, $location, $timestamp) { @@ -507,9 +500,10 @@ class Trashbin { /** * @param \OC\Files\View $view - * @param $file - * @param $filename - * @param $timestamp + * @param string $file + * @param string $filename + * @param integer|null $timestamp + * @param string $user * @return int */ private static function deleteVersions(\OC\Files\View $view, $file, $filename, $timestamp, $user) { @@ -633,18 +627,16 @@ class Trashbin { public static function expire($user) { $trashBinSize = self::getTrashbinSize($user); $availableSpace = self::calculateFreeSpace($trashBinSize, $user); - $size = 0; $dirContent = Helper::getTrashFiles('/', $user, 'mtime'); // delete all files older then $retention_obligation list($delSize, $count) = self::deleteExpiredFiles($dirContent, $user); - $size += $delSize; - $availableSpace += $size; + $availableSpace += $delSize; // delete files from trash until we meet the trash bin size limit again - $size += self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace); + self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace); } /** @@ -693,7 +685,7 @@ class Trashbin { * * @param array $files list of files sorted by mtime * @param string $user - * @return array size of deleted files and number of deleted files + * @return integer[] size of deleted files and number of deleted files */ public static function deleteExpiredFiles($files, $user) { $application = new Application(); diff --git a/apps/files_trashbin/tests/trashbin.php b/apps/files_trashbin/tests/trashbin.php index c53ed8d8a9..db7e7e6e84 100644 --- a/apps/files_trashbin/tests/trashbin.php +++ b/apps/files_trashbin/tests/trashbin.php @@ -248,8 +248,8 @@ class Test_Trashbin extends \Test\TestCase { /** * verify that the array contains the expected results - * @param array $result - * @param array $expected + * @param OCP\Files\FileInfo[] $result + * @param string[] $expected */ private function verifyArray($result, $expected) { $this->assertSame(count($expected), count($result)); @@ -268,6 +268,11 @@ class Test_Trashbin extends \Test\TestCase { } } + /** + * @param OCP\Files\FileInfo[] $files + * @param string $trashRoot + * @param integer $expireDate + */ private function manipulateDeleteTime($files, $trashRoot, $expireDate) { $counter = 0; foreach ($files as &$file) { @@ -627,7 +632,6 @@ class Test_Trashbin extends \Test\TestCase { /** * @param string $user * @param bool $create - * @param bool $password */ public static function loginHelper($user, $create = false) { if ($create) { @@ -650,11 +654,20 @@ class Test_Trashbin extends \Test\TestCase { // just a dummy class to make protected methods available for testing class TrashbinForTesting extends Files_Trashbin\Trashbin { + + /** + * @param OCP\Files\FileInfo[] $files + * @param integer $limit + */ public function dummyDeleteExpiredFiles($files, $limit) { // dummy value for $retention_obligation because it is not needed here return parent::deleteExpiredFiles($files, \Test_Trashbin::TEST_TRASHBIN_USER1, $limit, 0); } + /** + * @param OCP\Files\FileInfo[] $files + * @param integer $availableSpace + */ public function dummyDeleteFiles($files, $availableSpace) { return parent::deleteFiles($files, \Test_Trashbin::TEST_TRASHBIN_USER1, $availableSpace); } diff --git a/apps/files_versions/lib/storage.php b/apps/files_versions/lib/storage.php index 6737bf20f9..29876b3e38 100644 --- a/apps/files_versions/lib/storage.php +++ b/apps/files_versions/lib/storage.php @@ -649,7 +649,7 @@ class Storage { /** * Expire versions which exceed the quota * - * @param $filename + * @param string $filename * @param int|null $versionsSize * @param int $offset * @return bool|int|null diff --git a/apps/provisioning_api/tests/groupstest.php b/apps/provisioning_api/tests/groupstest.php index d37f4412e2..f4f3b19494 100644 --- a/apps/provisioning_api/tests/groupstest.php +++ b/apps/provisioning_api/tests/groupstest.php @@ -30,13 +30,13 @@ use OCP\IUserSession; use OCP\IRequest; class GroupsTest extends \Test\TestCase { - /** @var IGroupManager */ + /** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject */ protected $groupManager; - /** @var IUserSession */ + /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */ protected $userSession; - /** @var IRequest */ + /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ protected $request; - /** @var \OC\SubAdmin */ + /** @var \OC\SubAdmin|\PHPUnit_Framework_MockObject_MockObject */ protected $subAdminManager; /** @var \OCA\Provisioning_API\Groups */ protected $api; @@ -58,6 +58,10 @@ class GroupsTest extends \Test\TestCase { ); } + /** + * @param string $gid + * @return \OCP\IGroup|\PHPUnit_Framework_MockObject_MockObject + */ private function createGroup($gid) { $group = $this->getMock('OCP\IGroup'); $group @@ -66,6 +70,10 @@ class GroupsTest extends \Test\TestCase { return $group; } + /** + * @param string $uid + * @return \OCP\IUser|\PHPUnit_Framework_MockObject_MockObject + */ private function createUser($uid) { $user = $this->getMock('OCP\IUser'); $user diff --git a/apps/user_ldap/ajax/testConfiguration.php b/apps/user_ldap/ajax/testConfiguration.php index f5fd5f23b8..47fc776983 100644 --- a/apps/user_ldap/ajax/testConfiguration.php +++ b/apps/user_ldap/ajax/testConfiguration.php @@ -49,7 +49,7 @@ try { * pass (like e.g. expected syntax error). */ try { - $ldapWrapper->read($connection->getConnectionResource(), 'neverwhere', 'objectClass=*', array('dn')); + $ldapWrapper->read($connection->getConnectionResource(), '', 'objectClass=*', array('dn')); } catch (\Exception $e) { if($e->getCode() === 1) { OCP\JSON::error(array('message' => $l->t('The configuration is invalid: anonymous bind is not allowed.'))); diff --git a/apps/user_ldap/l10n/lt_LT.js b/apps/user_ldap/l10n/lt_LT.js index e27dcc3e8d..c263e95220 100644 --- a/apps/user_ldap/l10n/lt_LT.js +++ b/apps/user_ldap/l10n/lt_LT.js @@ -4,7 +4,9 @@ OC.L10N.register( "Failed to clear the mappings." : "Nepavyko išvalyti sąsajų.", "Failed to delete the server configuration" : "Nepavyko pašalinti serverio konfigūracijos", "The configuration is valid and the connection could be established!" : "Konfigūracija yra tinkama bei prisijungta sėkmingai!", + "The configuration is invalid. Please have a look at the logs for further details." : "Neteisinga konfigūracija. Daugiau informacijos rasite žurnaluose.", "No action specified" : "Nepasirinktas veiksmas", + "No data specified" : "Nepateikta duomenų", "Select groups" : "Pasirinkti grupes", "Do you really want to delete the current Server Configuration?" : "Ar tikrai norite ištrinti dabartinę serverio konfigūraciją?", "Confirm Deletion" : "Patvirtinkite trynimą", diff --git a/apps/user_ldap/l10n/lt_LT.json b/apps/user_ldap/l10n/lt_LT.json index fa1526c063..64e2bae467 100644 --- a/apps/user_ldap/l10n/lt_LT.json +++ b/apps/user_ldap/l10n/lt_LT.json @@ -2,7 +2,9 @@ "Failed to clear the mappings." : "Nepavyko išvalyti sąsajų.", "Failed to delete the server configuration" : "Nepavyko pašalinti serverio konfigūracijos", "The configuration is valid and the connection could be established!" : "Konfigūracija yra tinkama bei prisijungta sėkmingai!", + "The configuration is invalid. Please have a look at the logs for further details." : "Neteisinga konfigūracija. Daugiau informacijos rasite žurnaluose.", "No action specified" : "Nepasirinktas veiksmas", + "No data specified" : "Nepateikta duomenų", "Select groups" : "Pasirinkti grupes", "Do you really want to delete the current Server Configuration?" : "Ar tikrai norite ištrinti dabartinę serverio konfigūraciją?", "Confirm Deletion" : "Patvirtinkite trynimą", diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index 42e57e8296..667f107623 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -177,7 +177,7 @@ class Access extends LDAPUtility implements user\IUserTools { //in case an error occurs , e.g. object does not exist return false; } - if (empty($attr)) { + if (empty($attr) && ($filter === 'objectclass=*' || $this->ldap->countEntries($cr, $rr) === 1)) { \OCP\Util::writeLog('user_ldap', 'readAttribute: '.$dn.' found', \OCP\Util::DEBUG); return array(); } diff --git a/apps/user_ldap/user_ldap.php b/apps/user_ldap/user_ldap.php index fc8ce36163..0097dda89b 100644 --- a/apps/user_ldap/user_ldap.php +++ b/apps/user_ldap/user_ldap.php @@ -204,7 +204,7 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn $dn = $user->getDN(); //check if user really still exists by reading its entry - if(!is_array($this->access->readAttribute($dn, ''))) { + if(!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) { $lcr = $this->access->connection->getConnectionResource(); if(is_null($lcr)) { throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost); diff --git a/build/integration/run.sh b/build/integration/run.sh index 76c01068de..5a222bda3e 100755 --- a/build/integration/run.sh +++ b/build/integration/run.sh @@ -2,6 +2,9 @@ composer install +SCENARIO_TO_RUN=$1 +HIDE_OC_LOGS=$2 + # avoid port collision on jenkins - use $EXECUTOR_NUMBER if [ -z "$EXECUTOR_NUMBER" ]; then EXECUTOR_NUMBER=0 @@ -21,13 +24,15 @@ echo $PHPPID_FED export TEST_SERVER_URL="http://localhost:$PORT/ocs/" export TEST_SERVER_FED_URL="http://localhost:$PORT_FED/ocs/" -vendor/bin/behat -f junit -f pretty $1 +vendor/bin/behat -f junit -f pretty $SCENARIO_TO_RUN RESULT=$? kill $PHPPID kill $PHPPID_FED -tail "../../data/owncloud.log" +if [ -z $HIDE_OC_LOGS ]; then + tail "../../data/owncloud.log" +fi exit $RESULT diff --git a/config/config.sample.php b/config/config.sample.php index 034a1ebddb..c3abe3a2b8 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -213,6 +213,14 @@ $CONFIG = array( ) ), +/** + * If your user backend does not allow to reset the password (e.g. when it's a + * read-only user backend like LDAP), you can specify a custom link, where the + * user is redirected to, when clicking the "reset password" link after a failed + * login-attempt. + */ +'lost_password_link' => 'https://example.org/link/to/password/reset', + /** * Mail Parameters * diff --git a/core/css/apps.css b/core/css/apps.css index e9abbe0aee..9afd704434 100644 --- a/core/css/apps.css +++ b/core/css/apps.css @@ -6,7 +6,7 @@ width: 100%; } #app * { - -moz-box-sizing: border-box; box-sizing: border-box; + box-sizing: border-box; } @@ -21,7 +21,7 @@ width: 250px; height: 100%; float: left; - -moz-box-sizing: border-box; box-sizing: border-box; + box-sizing: border-box; background-color: #fff; padding-bottom: 44px; -webkit-user-select: none; @@ -35,12 +35,12 @@ height: 100%; width: inherit; overflow: auto; - -moz-box-sizing: border-box; box-sizing: border-box; + box-sizing: border-box; } #app-navigation li { position: relative; width: 100%; - -moz-box-sizing: border-box; box-sizing: border-box; + box-sizing: border-box; } #app-navigation .active.with-menu > a, @@ -67,7 +67,7 @@ min-height: 44px; padding: 0 12px; overflow: hidden; - -moz-box-sizing: border-box; box-sizing: border-box; + box-sizing: border-box; white-space: nowrap; text-overflow: ellipsis; color: #000; @@ -109,17 +109,13 @@ } #app-navigation .collapsible .collapse { - -moz-transform: rotate(-90deg); -webkit-transform: rotate(-90deg); -ms-transform:rotate(-90deg); - -o-transform:rotate(-90deg); transform: rotate(-90deg); } #app-navigation .collapsible.open .collapse { - -moz-transform: rotate(0); -webkit-transform: rotate(0); -ms-transform:rotate(0); - -o-transform:rotate(0); transform: rotate(0); } @@ -138,8 +134,6 @@ #app-navigation .collapsible.open { background-image: linear-gradient(top, rgb(238,238,238) 0%, rgb(245,245,245) 100%); - background-image: -o-linear-gradient(top, rgb(238,238,238) 0%, rgb(245,245,245) 100%); - background-image: -moz-linear-gradient(top, rgb(238,238,238) 0%, rgb(245,245,245) 100%); background-image: -webkit-linear-gradient(top, rgb(238,238,238) 0%, rgb(245,245,245) 100%); background-image: -ms-linear-gradient(top, rgb(238,238,238) 0%, rgb(245,245,245) 100%); } @@ -209,10 +203,7 @@ /* drag and drop */ #app-navigation .drag-and-drop { - -moz-transition: padding-bottom 500ms ease 0s; - -o-transition: padding-bottom 500ms ease 0s; -webkit-transition: padding-bottom 500ms ease 0s; - -ms-transition: padding-bottom 500ms ease 0s; transition: padding-bottom 500ms ease 0s; padding-bottom: 40px; } @@ -459,8 +450,6 @@ background: #fff; border-left: 1px solid #eee; -webkit-transition: margin-right 300ms; - -moz-transition: margin-right 300ms; - -o-transition: margin-right 300ms; transition: margin-right 300ms; overflow-x: hidden; overflow-y: auto; diff --git a/core/css/header.css b/core/css/header.css index 37f06ef063..4a5db088f9 100644 --- a/core/css/header.css +++ b/core/css/header.css @@ -39,7 +39,6 @@ height: 45px; line-height: 2.5em; background-color: #1d2d44; - -moz-box-sizing: border-box; box-sizing: border-box; } @@ -54,7 +53,6 @@ padding: 5px; padding-bottom: 0; height: 45px; /* header height */ - -moz-box-sizing: border-box; box-sizing: border-box; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; opacity: 1; @@ -185,7 +183,6 @@ } #navigation, #navigation * { - -moz-box-sizing:border-box; box-sizing:border-box; } #navigation li { @@ -272,7 +269,6 @@ height: 100%; max-width: 80%; white-space: nowrap; - -moz-box-sizing: border-box; box-sizing: border-box; } @@ -330,7 +326,7 @@ border-radius: 3px; border-top-left-radius: 0; border-top-right-radius: 0; - -moz-box-sizing: border-box; box-sizing: border-box; + box-sizing: border-box; } #expanddiv a { display: block; @@ -339,7 +335,6 @@ padding: 4px 12px 0; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; opacity: .7; - -moz-box-sizing: border-box; box-sizing: border-box; } #expanddiv a img { diff --git a/core/css/icons.css b/core/css/icons.css index 14b2101b33..836a84fd70 100644 --- a/core/css/icons.css +++ b/core/css/icons.css @@ -24,7 +24,6 @@ background-image: url('../img/loading-small.gif'); } .icon-32 { - -webkit-background-size: 32px !important; background-size: 32px !important; } @@ -47,10 +46,12 @@ .icon-checkmark { background-image: url('../img/actions/checkmark.svg'); } - .icon-checkmark-white { background-image: url('../img/actions/checkmark-white.svg'); } +.icon-checkmark-color { + background-image: url('../img/actions/checkmark-color.svg'); +} .icon-close { background-image: url('../img/actions/close.svg'); @@ -82,6 +83,16 @@ background-image: url('../img/actions/edit.svg'); } +.icon-error { + background-image: url('../img/actions/error.svg'); +} +.icon-error-white { + background-image: url('../img/actions/error-white.svg'); +} +.icon-error-color { + background-image: url('../img/actions/error-color.svg'); +} + .icon-external { background-image: url('../img/actions/external.svg'); } diff --git a/core/css/inputs.css b/core/css/inputs.css index 9f440a6c35..fe03af85f6 100644 --- a/core/css/inputs.css +++ b/core/css/inputs.css @@ -55,7 +55,7 @@ input[type="email"], input[type="url"], input[type="time"] { -webkit-appearance:textfield; -moz-appearance:textfield; - -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; + box-sizing:content-box; } input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active, input[type="password"]:hover, input[type="password"]:focus, input[type="password"]:active, @@ -108,10 +108,26 @@ html:not(.ie8) input[type="checkbox"].checkbox:checked + label:before { background-image: url('../img/actions/checkbox-checked.svg'); } +html:not(.ie8) input[type="checkbox"].checkbox:disabled + label:before { + background-image: url('../img/actions/checkbox-disabled.svg'); +} + +html:not(.ie8) input[type="checkbox"].checkbox:checked:disabled + label:before { + background-image: url('../img/actions/checkbox-checked-disabled.svg'); +} + html:not(.ie8) input[type="checkbox"].checkbox--white:checked + label:before { background-image: url('../img/actions/checkbox-checked-white.svg'); } +html:not(.ie8) input[type="checkbox"].checkbox--white:disabled + label:before { + background-image: url('../img/actions/checkbox-disabled-white.svg'); +} + +html:not(.ie8) input[type="checkbox"].checkbox--white:checked:disabled + label:before { + background-image: url('../img/actions/checkbox-checked-disabled.svg'); +} + html:not(.ie8) input[type="checkbox"].checkbox:hover+label:before, input[type="checkbox"]:focus+label:before { color:#111 !important; } @@ -119,7 +135,7 @@ html:not(.ie8) input[type="checkbox"].checkbox:hover+label:before, input[type="c input[type="time"] { width: initial; height: 31px; - -moz-box-sizing: border-box; box-sizing: border-box; + box-sizing: border-box; } select { diff --git a/core/css/mobile.css b/core/css/mobile.css index 288ae2979d..131907eb09 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -46,7 +46,6 @@ .error-wide { width: 100%; margin-left: 0 !important; - -moz-box-sizing: border-box; box-sizing: border-box; } diff --git a/core/css/share.css b/core/css/share.css index 15f8061b06..55ee5996a7 100644 --- a/core/css/share.css +++ b/core/css/share.css @@ -125,8 +125,6 @@ a.unshare { .shareTabView .error { color: #e9322d; border-color: #e9322d; - -webkit-box-shadow: 0 0 6px #f8b9b7; - -moz-box-shadow: 0 0 6px #f8b9b7; box-shadow: 0 0 6px #f8b9b7; } diff --git a/core/css/styles.css b/core/css/styles.css index 640aab81d2..62161d6927 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -26,11 +26,8 @@ body { #body-login { text-align: center; background: #1d2d44; /* Old browsers */ - background: -moz-linear-gradient(top, #35537a 0%, #1d2d44 100%); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#35537a), color-stop(100%,#1d2d44)); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, #35537a 0%,#1d2d44 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #35537a 0%,#1d2d44 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(top, #35537a 0%,#1d2d44 100%); /* IE10+ */ background: linear-gradient(top, #35537a 0%,#1d2d44 100%); /* W3C */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#35537a', endColorstr='#1d2d44',GradientType=0 ); /* IE6-9 */ } @@ -99,8 +96,6 @@ body { width: 0; cursor: pointer; -webkit-transition: all 100ms; - -moz-transition: all 100ms; - -o-transition: all 100ms; transition: all 100ms; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; opacity: .7; @@ -117,8 +112,6 @@ body { /* CONTENT ------------------------------------------------------------------ */ #controls { - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; box-sizing: border-box; position: fixed; top: 45px; @@ -149,8 +142,6 @@ body { #controls input[type='text'], #controls input[type='password'], #controls select { - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; box-sizing: border-box; display: inline-block; height: 36px; @@ -175,7 +166,6 @@ body { width: 100%; overflow-x: hidden; /* prevent horizontal scrollbar */ padding-top: 45px; - -moz-box-sizing:border-box; box-sizing:border-box; } /* allow horizontal scrollbar for personal and admin settings */ @@ -807,7 +797,7 @@ span.ui-icon {float: left; margin: 3px 7px 30px 0;} width: 100%; height: 30px; } #tagsdialog .bottombuttons * { float:left;} -#tagsdialog .taglist li { background:#f8f8f8; padding:.3em .8em; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; -webkit-transition:background-color 500ms; -moz-transition:background-color 500ms; -o-transition:background-color 500ms; transition:background-color 500ms; } +#tagsdialog .taglist li { background:#f8f8f8; padding:.3em .8em; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; -webkit-transition:background-color 500ms; transition:background-color 500ms; } #tagsdialog .taglist li:hover, #tagsdialog .taglist li:active { background:#eee; } #tagsdialog .addinput { width: 90%; clear: both; } @@ -826,9 +816,9 @@ span.ui-icon {float: left; margin: 3px 7px 30px 0;} .popup .close { position:absolute; top:0.2em; right:0.2em; height:20px; width:20px; background:url('../img/actions/close.svg') no-repeat center; } .popup h2 { font-size:20px; } .arrow { border-bottom:10px solid white; border-left:10px solid transparent; border-right:10px solid transparent; display:block; height:0; position:absolute; width:0; z-index:201; } -.arrow.left { left:-13px; bottom:1.2em; -webkit-transform:rotate(270deg); -moz-transform:rotate(270deg); -o-transform:rotate(270deg); -ms-transform:rotate(270deg); transform:rotate(270deg); } +.arrow.left { left:-13px; bottom:1.2em; -webkit-transform:rotate(270deg); -ms-transform:rotate(270deg); transform:rotate(270deg); } .arrow.up { top:-8px; right:6px; } -.arrow.down { -webkit-transform:rotate(180deg); -moz-transform:rotate(180deg); -o-transform:rotate(180deg); -ms-transform:rotate(180deg); transform:rotate(180deg); } +.arrow.down { -webkit-transform:rotate(180deg); -ms-transform:rotate(180deg); transform:rotate(180deg); } /* ---- BREADCRUMB ---- */ diff --git a/core/img/actions/add.svg b/core/img/actions/add.svg index ecbab6f13a..c97431782f 100644 --- a/core/img/actions/add.svg +++ b/core/img/actions/add.svg @@ -1,5 +1,6 @@ + - - + + diff --git a/core/img/actions/checkbox-checked-disabled.png b/core/img/actions/checkbox-checked-disabled.png new file mode 100644 index 0000000000..55980ba730 Binary files /dev/null and b/core/img/actions/checkbox-checked-disabled.png differ diff --git a/core/img/actions/checkbox-checked-disabled.svg b/core/img/actions/checkbox-checked-disabled.svg new file mode 100644 index 0000000000..de89df039d --- /dev/null +++ b/core/img/actions/checkbox-checked-disabled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/img/actions/checkbox-checked.svg b/core/img/actions/checkbox-checked.svg index c5aa3cd73b..c648957429 100644 --- a/core/img/actions/checkbox-checked.svg +++ b/core/img/actions/checkbox-checked.svg @@ -1,5 +1,5 @@ - + diff --git a/core/img/actions/checkbox-disabled-white.png b/core/img/actions/checkbox-disabled-white.png new file mode 100644 index 0000000000..e1f48439d2 Binary files /dev/null and b/core/img/actions/checkbox-disabled-white.png differ diff --git a/core/img/actions/checkbox-disabled-white.svg b/core/img/actions/checkbox-disabled-white.svg new file mode 100644 index 0000000000..52c2fee3b1 --- /dev/null +++ b/core/img/actions/checkbox-disabled-white.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/img/actions/checkbox-disabled.png b/core/img/actions/checkbox-disabled.png new file mode 100644 index 0000000000..a2ead20996 Binary files /dev/null and b/core/img/actions/checkbox-disabled.png differ diff --git a/core/img/actions/checkbox-disabled.svg b/core/img/actions/checkbox-disabled.svg new file mode 100644 index 0000000000..48d31f591f --- /dev/null +++ b/core/img/actions/checkbox-disabled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/img/actions/checkbox-mixed.svg b/core/img/actions/checkbox-mixed.svg index 7f3642912d..5f873d6a96 100644 --- a/core/img/actions/checkbox-mixed.svg +++ b/core/img/actions/checkbox-mixed.svg @@ -1,5 +1,5 @@ - + diff --git a/core/img/actions/checkbox.svg b/core/img/actions/checkbox.svg index fe8f727b89..bfb83e3708 100644 --- a/core/img/actions/checkbox.svg +++ b/core/img/actions/checkbox.svg @@ -1,5 +1,5 @@ - + diff --git a/core/img/actions/checkmark-color.png b/core/img/actions/checkmark-color.png new file mode 100644 index 0000000000..a8ab849cad Binary files /dev/null and b/core/img/actions/checkmark-color.png differ diff --git a/core/img/actions/checkmark-color.svg b/core/img/actions/checkmark-color.svg new file mode 100644 index 0000000000..584997f0ac --- /dev/null +++ b/core/img/actions/checkmark-color.svg @@ -0,0 +1,2 @@ + + diff --git a/core/img/actions/checkmark-white.svg b/core/img/actions/checkmark-white.svg index e6b63a4d59..964624a9ce 100644 --- a/core/img/actions/checkmark-white.svg +++ b/core/img/actions/checkmark-white.svg @@ -1,4 +1,4 @@ - - + + diff --git a/core/img/actions/checkmark.svg b/core/img/actions/checkmark.svg index 3fa2a3f1ba..e41472c8ef 100644 --- a/core/img/actions/checkmark.svg +++ b/core/img/actions/checkmark.svg @@ -1,2 +1,2 @@ - + diff --git a/core/img/actions/close.svg b/core/img/actions/close.svg index e060da3f8b..fb49c6ac89 100644 --- a/core/img/actions/close.svg +++ b/core/img/actions/close.svg @@ -5,6 +5,6 @@ - + diff --git a/core/img/actions/delete-hover.png b/core/img/actions/delete-hover.png index 3f8cb6eff9..ed12640df7 100644 Binary files a/core/img/actions/delete-hover.png and b/core/img/actions/delete-hover.png differ diff --git a/core/img/actions/delete-hover.svg b/core/img/actions/delete-hover.svg index 9583ec15b3..ecc6139a5e 100644 --- a/core/img/actions/delete-hover.svg +++ b/core/img/actions/delete-hover.svg @@ -1,4 +1,4 @@ - + diff --git a/core/img/actions/delete.png b/core/img/actions/delete.png index e891b370cc..20e894c7f7 100644 Binary files a/core/img/actions/delete.png and b/core/img/actions/delete.png differ diff --git a/core/img/actions/download.svg b/core/img/actions/download.svg index 0d698bca8d..f8513b63f7 100644 --- a/core/img/actions/download.svg +++ b/core/img/actions/download.svg @@ -1,5 +1,6 @@ + - + diff --git a/core/img/actions/error-color.png b/core/img/actions/error-color.png new file mode 100644 index 0000000000..7d00282312 Binary files /dev/null and b/core/img/actions/error-color.png differ diff --git a/core/img/actions/error-color.svg b/core/img/actions/error-color.svg new file mode 100644 index 0000000000..93cb9ffe0a --- /dev/null +++ b/core/img/actions/error-color.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/img/actions/error-white.png b/core/img/actions/error-white.png new file mode 100644 index 0000000000..6e15865401 Binary files /dev/null and b/core/img/actions/error-white.png differ diff --git a/core/img/actions/error-white.svg b/core/img/actions/error-white.svg new file mode 100644 index 0000000000..38c57f6e9c --- /dev/null +++ b/core/img/actions/error-white.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/img/actions/error.png b/core/img/actions/error.png new file mode 100644 index 0000000000..61df76b53e Binary files /dev/null and b/core/img/actions/error.png differ diff --git a/core/img/actions/error.svg b/core/img/actions/error.svg new file mode 100644 index 0000000000..38e4053c33 --- /dev/null +++ b/core/img/actions/error.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/img/actions/logout.png b/core/img/actions/logout.png index bada7a1261..ad230de98f 100644 Binary files a/core/img/actions/logout.png and b/core/img/actions/logout.png differ diff --git a/core/img/actions/logout.svg b/core/img/actions/logout.svg index 1a0ee16718..96bd207284 100644 --- a/core/img/actions/logout.svg +++ b/core/img/actions/logout.svg @@ -1,58 +1,4 @@ - - - - - image/svg+xml - - - - - - - - + + diff --git a/core/img/actions/menu.svg b/core/img/actions/menu.svg index f0e33df373..24a057aac7 100644 --- a/core/img/actions/menu.svg +++ b/core/img/actions/menu.svg @@ -1,12 +1,4 @@ + - - - - image/svg+xml - - - - - - + diff --git a/core/img/actions/public.svg b/core/img/actions/public.svg index 99a71c6cb5..7721c9e340 100644 --- a/core/img/actions/public.svg +++ b/core/img/actions/public.svg @@ -1,7 +1,7 @@ - - + + diff --git a/core/img/actions/radio-checked-disabled.png b/core/img/actions/radio-checked-disabled.png new file mode 100644 index 0000000000..09abc410f6 Binary files /dev/null and b/core/img/actions/radio-checked-disabled.png differ diff --git a/core/img/actions/radio-checked-disabled.svg b/core/img/actions/radio-checked-disabled.svg new file mode 100644 index 0000000000..294f8a7477 --- /dev/null +++ b/core/img/actions/radio-checked-disabled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/img/actions/radio-checked.png b/core/img/actions/radio-checked.png new file mode 100644 index 0000000000..94933b7b69 Binary files /dev/null and b/core/img/actions/radio-checked.png differ diff --git a/core/img/actions/radio-checked.svg b/core/img/actions/radio-checked.svg new file mode 100644 index 0000000000..31011c0ea2 --- /dev/null +++ b/core/img/actions/radio-checked.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/img/actions/radio-disabled.png b/core/img/actions/radio-disabled.png new file mode 100644 index 0000000000..ac7f49ed53 Binary files /dev/null and b/core/img/actions/radio-disabled.png differ diff --git a/core/img/actions/radio-disabled.svg b/core/img/actions/radio-disabled.svg new file mode 100644 index 0000000000..6058bb73fc --- /dev/null +++ b/core/img/actions/radio-disabled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/img/actions/radio-white.png b/core/img/actions/radio-white.png new file mode 100644 index 0000000000..04beefdff0 Binary files /dev/null and b/core/img/actions/radio-white.png differ diff --git a/core/img/actions/radio-white.svg b/core/img/actions/radio-white.svg new file mode 100644 index 0000000000..57611f4cdd --- /dev/null +++ b/core/img/actions/radio-white.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/img/actions/radio.png b/core/img/actions/radio.png new file mode 100644 index 0000000000..70ac4741b9 Binary files /dev/null and b/core/img/actions/radio.png differ diff --git a/core/img/actions/radio.svg b/core/img/actions/radio.svg new file mode 100644 index 0000000000..9df8925176 --- /dev/null +++ b/core/img/actions/radio.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/img/actions/starred.svg b/core/img/actions/starred.svg index 130bab366a..07e38827b2 100644 --- a/core/img/actions/starred.svg +++ b/core/img/actions/starred.svg @@ -1,6 +1,6 @@ - + diff --git a/core/img/actions/toggle.svg b/core/img/actions/toggle.svg index 730464a72c..c426ab8099 100644 --- a/core/img/actions/toggle.svg +++ b/core/img/actions/toggle.svg @@ -1,2 +1,2 @@ - + diff --git a/core/img/actions/view-close.png b/core/img/actions/view-close.png index 0874381a57..eae9acc7a4 100644 Binary files a/core/img/actions/view-close.png and b/core/img/actions/view-close.png differ diff --git a/core/img/actions/view-close.svg b/core/img/actions/view-close.svg index 2b91e382eb..e7dde9b79b 100644 --- a/core/img/actions/view-close.svg +++ b/core/img/actions/view-close.svg @@ -5,6 +5,6 @@ - + diff --git a/core/img/actions/view-next.png b/core/img/actions/view-next.png index d8c749bec9..be8cc15cec 100644 Binary files a/core/img/actions/view-next.png and b/core/img/actions/view-next.png differ diff --git a/core/img/actions/view-next.svg b/core/img/actions/view-next.svg index 4b719842ef..713d1bc757 100644 --- a/core/img/actions/view-next.svg +++ b/core/img/actions/view-next.svg @@ -5,6 +5,6 @@ - + diff --git a/core/img/actions/view-pause.png b/core/img/actions/view-pause.png index 87a18128ad..94cd1a5dc6 100644 Binary files a/core/img/actions/view-pause.png and b/core/img/actions/view-pause.png differ diff --git a/core/img/actions/view-pause.svg b/core/img/actions/view-pause.svg index e9ff43be0b..8945bf4565 100644 --- a/core/img/actions/view-pause.svg +++ b/core/img/actions/view-pause.svg @@ -5,10 +5,10 @@ - - + + - + diff --git a/core/img/actions/view-play.png b/core/img/actions/view-play.png index b07c6de3cf..a8398d5758 100644 Binary files a/core/img/actions/view-play.png and b/core/img/actions/view-play.png differ diff --git a/core/img/actions/view-play.svg b/core/img/actions/view-play.svg index e617e29cb0..6359e6d018 100644 --- a/core/img/actions/view-play.svg +++ b/core/img/actions/view-play.svg @@ -5,10 +5,10 @@ - - + + - + diff --git a/core/img/actions/view-previous.png b/core/img/actions/view-previous.png index f601ec2ba4..86e2a80962 100644 Binary files a/core/img/actions/view-previous.png and b/core/img/actions/view-previous.png differ diff --git a/core/img/actions/view-previous.svg b/core/img/actions/view-previous.svg index 8a5013aad4..60f6cd5928 100644 --- a/core/img/actions/view-previous.svg +++ b/core/img/actions/view-previous.svg @@ -5,6 +5,6 @@ - + diff --git a/core/js/config.php b/core/js/config.php index 463e334ef2..e51ae90372 100644 --- a/core/js/config.php +++ b/core/js/config.php @@ -136,11 +136,12 @@ $array = array( "firstDay" => json_encode($l->getFirstWeekDay()) , "oc_config" => json_encode( array( - 'session_lifetime' => min(\OCP\Config::getSystemValue('session_lifetime', ini_get('session.gc_maxlifetime')), ini_get('session.gc_maxlifetime')), + 'session_lifetime' => min(\OCP\Config::getSystemValue('session_lifetime', OC::$server->getIniWrapper()->getNumeric('session.gc_maxlifetime')), OC::$server->getIniWrapper()->getNumeric('session.gc_maxlifetime')), 'session_keepalive' => \OCP\Config::getSystemValue('session_keepalive', true), 'version' => implode('.', OC_Util::getVersion()), 'versionstring' => OC_Util::getVersionString(), 'enable_avatars' => \OC::$server->getConfig()->getSystemValue('enable_avatars', true), + 'lost_password_link'=> \OC::$server->getConfig()->getSystemValue('lost_password_link', null), 'modRewriteWorking' => (getenv('front_controller_active') === 'true'), ) ), diff --git a/core/js/js.js b/core/js/js.js index cbdffd0f01..2937d3f6eb 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -174,7 +174,6 @@ var OC={ * @param {string} type the type of the file to link to (e.g. css,img,ajax.template) * @param {string} file the filename * @return {string} Absolute URL for a file in an app - * @deprecated use OC.generateUrl() instead */ filePath:function(app,type,file){ var isCore=OC.coreApps.indexOf(app)!==-1, diff --git a/core/js/lostpassword.js b/core/js/lostpassword.js index 294a9d8c1c..df28c2308c 100644 --- a/core/js/lostpassword.js +++ b/core/js/lostpassword.js @@ -13,22 +13,26 @@ OC.Lostpassword = { resetErrorMsg : t('core', 'Password can not be changed. Please contact your administrator.'), init : function() { - $('#lost-password').click(OC.Lostpassword.sendLink); + $('#lost-password').click(OC.Lostpassword.resetLink); $('#reset-password #submit').click(OC.Lostpassword.resetPassword); }, - sendLink : function(event){ + resetLink : function(event){ event.preventDefault(); if (!$('#user').val().length){ $('#submit').trigger('click'); } else { - $.post( + if (OC.config['lost_password_link']) { + window.location = OC.config['lost_password_link']; + } else { + $.post( OC.generateUrl('/lostpassword/email'), { user : $('#user').val() }, OC.Lostpassword.sendLinkDone - ); + ); + } } }, diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index 29dce21486..452599fb7f 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -48,7 +48,7 @@ ' {{/if}}' + '{{else}}' + // FIXME: this doesn't belong in this view - '' + + '{{#if noSharingPlaceholder}}{{/if}}' + '{{/if}}' ; @@ -238,15 +238,18 @@ render: function() { var linkShareTemplate = this.template(); + var resharingAllowed = this.model.sharePermissionPossible(); - if( !this.model.sharePermissionPossible() + if(!resharingAllowed || !this.showLink || !this.configModel.isShareWithLinkAllowed()) { - this.$el.html(linkShareTemplate({ - shareAllowed: false, - noSharingPlaceholder: t('core', 'Resharing is not allowed') - })); + var templateData = {shareAllowed: false}; + if (!resharingAllowed) { + // add message + templateData.noSharingPlaceholder = t('core', 'Resharing is not allowed'); + } + this.$el.html(linkShareTemplate(templateData)); return this; } diff --git a/core/js/sharedialogshareelistview.js b/core/js/sharedialogshareelistview.js index dfe5789e77..daed8a439b 100644 --- a/core/js/sharedialogshareelistview.js +++ b/core/js/sharedialogshareelistview.js @@ -245,12 +245,14 @@ onUnshare: function(event) { var $element = $(event.target); - console.log($element); + if (!$element.is('a')) { + $element = $element.closest('a'); + } - var $loading = $element.siblings('.icon-loading-small').eq(0); + var $loading = $element.find('.icon-loading-small').eq(0); if(!$loading.hasClass('hidden')) { // in process - return; + return false; } $loading.removeClass('hidden'); diff --git a/core/js/tests/specs/sharedialogviewSpec.js b/core/js/tests/specs/sharedialogviewSpec.js index 6d5243b0e8..bfd3d98786 100644 --- a/core/js/tests/specs/sharedialogviewSpec.js +++ b/core/js/tests/specs/sharedialogviewSpec.js @@ -218,6 +218,7 @@ describe('OC.Share.ShareDialogView', function() { dialog.render(); expect(dialog.$el.find('.linkCheckbox').length).toEqual(0); + expect(dialog.$el.find('.shareWithField').length).toEqual(1); }); it('shows populated link share when a link share exists', function() { // this is how the OC.Share class does it... diff --git a/core/js/update.js b/core/js/update.js index 090f8fa5d2..1626b6f2c4 100644 --- a/core/js/update.js +++ b/core/js/update.js @@ -84,7 +84,7 @@ .append(t('core', 'The update was successful. Redirecting you to ownCloud now.')) .appendTo($el); setTimeout(function () { - OC.redirect(OC.webroot); + OC.redirect(OC.webroot + '/'); }, 3000); } }); diff --git a/core/l10n/fr.js b/core/l10n/fr.js index 9dae27cd45..4bb7aa82c9 100644 --- a/core/l10n/fr.js +++ b/core/l10n/fr.js @@ -191,6 +191,7 @@ OC.L10N.register( "Couldn't reset password because the token is invalid" : "Impossible de réinitialiser le mot de passe car le jeton n'est pas valable.", "Couldn't reset password because the token is expired" : "Impossible de réinitialiser le mot de passe car le jeton a expiré.", "Couldn't send reset email. Please make sure your username is correct." : "Impossible d'envoyer le courriel de réinitialisation. Veuillez vérifier que votre nom d'utilisateur est correct.", + "Could not send reset email because there is no email address for this username. Please contact your administrator." : "Impossible d'envoyer le courriel de réinitialisation car il n'y a aucune adresse de courriel pour cet utilisateur. Veuillez contacter votre administrateur.", "%s password reset" : "Réinitialisation de votre mot de passe %s", "Use the following link to reset your password: {link}" : "Utilisez le lien suivant pour réinitialiser votre mot de passe : {link}", "New password" : "Nouveau mot de passe", diff --git a/core/l10n/fr.json b/core/l10n/fr.json index ee8f44595e..02347de170 100644 --- a/core/l10n/fr.json +++ b/core/l10n/fr.json @@ -189,6 +189,7 @@ "Couldn't reset password because the token is invalid" : "Impossible de réinitialiser le mot de passe car le jeton n'est pas valable.", "Couldn't reset password because the token is expired" : "Impossible de réinitialiser le mot de passe car le jeton a expiré.", "Couldn't send reset email. Please make sure your username is correct." : "Impossible d'envoyer le courriel de réinitialisation. Veuillez vérifier que votre nom d'utilisateur est correct.", + "Could not send reset email because there is no email address for this username. Please contact your administrator." : "Impossible d'envoyer le courriel de réinitialisation car il n'y a aucune adresse de courriel pour cet utilisateur. Veuillez contacter votre administrateur.", "%s password reset" : "Réinitialisation de votre mot de passe %s", "Use the following link to reset your password: {link}" : "Utilisez le lien suivant pour réinitialiser votre mot de passe : {link}", "New password" : "Nouveau mot de passe", diff --git a/core/l10n/nl.js b/core/l10n/nl.js index ffff693e95..9e2bc2d345 100644 --- a/core/l10n/nl.js +++ b/core/l10n/nl.js @@ -18,6 +18,8 @@ OC.L10N.register( "Repair error: " : "Reparatiefout:", "Set log level to debug - current level: \"%s\"" : "Instellen logniveau op debug - huidige niveau: \"%s\"", "Reset log level to \"%s\"" : "Terugzetten logniveau op \"#%s\"", + "Starting code integrity check" : "Starten code betrouwbaarheidscontrole", + "Finished code integrity check" : "Gereed met code betrouwbaarheidscontrole", "%s (3rdparty)" : "%s (3rdparty)", "%s (incompatible)" : "%s (incompatibel)", "Following apps have been disabled: %s" : "De volgende apps zijn gedeactiveerd: %s", @@ -77,6 +79,7 @@ OC.L10N.register( "Oct." : "Okt.", "Nov." : "Nov.", "Dec." : "Dec.", + "There were problems with the code integrity check. More information…" : "Er traden problemen op tijdens de code betrouwbaarheidscontrole. Meer informatie…", "Settings" : "Instellingen", "Saving..." : "Opslaan", "seconds ago" : "seconden geleden", @@ -116,6 +119,7 @@ OC.L10N.register( "Your PHP version ({version}) is no longer supported by PHP. We encourage you to upgrade your PHP version to take advantage of performance and security updates provided by PHP." : "UwPHP versie ({version}) wordt niet langer ondersteund door PHP. We adviseren u om uw PHP versie te upgraden voor betere prestaties en security updates geleverd door PHP.", "The reverse proxy headers configuration is incorrect, or you are accessing ownCloud from a trusted proxy. If you are not accessing ownCloud from a trusted proxy, this is a security issue and can allow an attacker to spoof their IP address as visible to ownCloud. Further information can be found in our documentation." : "De reverse proxy headerconfiguratie is onjuist, of u hebt toegang tot ownCloud via een vertrouwde proxy. Als u ownCloud niet via een vertrouwde proxy benadert, dan levert dan een beveiligingsrisico op, waardoor een aanvaller het IP-adres dat ownCloud ziet kan spoofen. Meer informatie is te vinden in onze documentatie.", "Memcached is configured as distributed cache, but the wrong PHP module \"memcache\" is installed. \\OC\\Memcache\\Memcached only supports \"memcached\" and not \"memcache\". See the memcached wiki about both modules." : "Memcached is geconfigureerd als gedistribueerde cache, maar de verkeerde PHP module \"memcache\" is geïnstalleerd. \\OC\\Memcache\\Memcached ondersteunt alleen \"memcached\" en niet \"memcache\". Zie de memcached wiki over beide modules.", + "Some files have not passed the integrity check. Further information on how to resolve this issue can be found in our documentation. (List of invalid files… / Rescan…)" : "Sommige bestanden kwamen niet door de code betrouwbaarheidscontrole. Meer informatie over het oplossen van dit probleem kan worden gevonden in onze documentatie. (Lijst met ongeldige bestanden… / Opnieuw…)", "Error occurred while checking server setup" : "Een fout trad op bij checken serverconfiguratie", "The \"{header}\" HTTP header is not configured to equal to \"{expected}\". This is a potential security or privacy risk and we recommend adjusting this setting." : "De \"{header}\" HTTP header is niet overeenkomstig met \"{expected}\" geconfigureerd. Dit is een potentieel security of privacy risico en we adviseren om deze instelling te wijzigen.", "The \"Strict-Transport-Security\" HTTP header is not configured to least \"{seconds}\" seconds. For enhanced security we recommend enabling HSTS as described in our security tips." : "De \"Strict-Transport-Security\" HTTP header is niet geconfigureerd als minimaal \"{seconds}\" seconden. Voor verbeterde beveiliging adviseren we HSTS in te schakelen zoals beschreven in onze security tips.", @@ -187,6 +191,7 @@ OC.L10N.register( "Couldn't reset password because the token is invalid" : "Kon het wachtwoord niet herstellen, omdat het token ongeldig is", "Couldn't reset password because the token is expired" : "Kon het wachtwoord niet herstellen, omdat het token verlopen is", "Couldn't send reset email. Please make sure your username is correct." : "Kon e-mail niet versturen. Verifieer of uw gebruikersnaam correct is.", + "Could not send reset email because there is no email address for this username. Please contact your administrator." : "Kon geen herstel e-mail versturen, omdat er geen e-mailadres bekend is bij deze gebruikersnaam. Neem contact op met uw beheerder.", "%s password reset" : "%s wachtwoord reset", "Use the following link to reset your password: {link}" : "Gebruik de volgende link om uw wachtwoord te resetten: {link}", "New password" : "Nieuw wachtwoord", diff --git a/core/l10n/nl.json b/core/l10n/nl.json index a387d49609..d565545ce3 100644 --- a/core/l10n/nl.json +++ b/core/l10n/nl.json @@ -16,6 +16,8 @@ "Repair error: " : "Reparatiefout:", "Set log level to debug - current level: \"%s\"" : "Instellen logniveau op debug - huidige niveau: \"%s\"", "Reset log level to \"%s\"" : "Terugzetten logniveau op \"#%s\"", + "Starting code integrity check" : "Starten code betrouwbaarheidscontrole", + "Finished code integrity check" : "Gereed met code betrouwbaarheidscontrole", "%s (3rdparty)" : "%s (3rdparty)", "%s (incompatible)" : "%s (incompatibel)", "Following apps have been disabled: %s" : "De volgende apps zijn gedeactiveerd: %s", @@ -75,6 +77,7 @@ "Oct." : "Okt.", "Nov." : "Nov.", "Dec." : "Dec.", + "There were problems with the code integrity check. More information…" : "Er traden problemen op tijdens de code betrouwbaarheidscontrole. Meer informatie…", "Settings" : "Instellingen", "Saving..." : "Opslaan", "seconds ago" : "seconden geleden", @@ -114,6 +117,7 @@ "Your PHP version ({version}) is no longer supported by PHP. We encourage you to upgrade your PHP version to take advantage of performance and security updates provided by PHP." : "UwPHP versie ({version}) wordt niet langer ondersteund door PHP. We adviseren u om uw PHP versie te upgraden voor betere prestaties en security updates geleverd door PHP.", "The reverse proxy headers configuration is incorrect, or you are accessing ownCloud from a trusted proxy. If you are not accessing ownCloud from a trusted proxy, this is a security issue and can allow an attacker to spoof their IP address as visible to ownCloud. Further information can be found in our documentation." : "De reverse proxy headerconfiguratie is onjuist, of u hebt toegang tot ownCloud via een vertrouwde proxy. Als u ownCloud niet via een vertrouwde proxy benadert, dan levert dan een beveiligingsrisico op, waardoor een aanvaller het IP-adres dat ownCloud ziet kan spoofen. Meer informatie is te vinden in onze documentatie.", "Memcached is configured as distributed cache, but the wrong PHP module \"memcache\" is installed. \\OC\\Memcache\\Memcached only supports \"memcached\" and not \"memcache\". See the memcached wiki about both modules." : "Memcached is geconfigureerd als gedistribueerde cache, maar de verkeerde PHP module \"memcache\" is geïnstalleerd. \\OC\\Memcache\\Memcached ondersteunt alleen \"memcached\" en niet \"memcache\". Zie de memcached wiki over beide modules.", + "Some files have not passed the integrity check. Further information on how to resolve this issue can be found in our documentation. (List of invalid files… / Rescan…)" : "Sommige bestanden kwamen niet door de code betrouwbaarheidscontrole. Meer informatie over het oplossen van dit probleem kan worden gevonden in onze documentatie. (Lijst met ongeldige bestanden… / Opnieuw…)", "Error occurred while checking server setup" : "Een fout trad op bij checken serverconfiguratie", "The \"{header}\" HTTP header is not configured to equal to \"{expected}\". This is a potential security or privacy risk and we recommend adjusting this setting." : "De \"{header}\" HTTP header is niet overeenkomstig met \"{expected}\" geconfigureerd. Dit is een potentieel security of privacy risico en we adviseren om deze instelling te wijzigen.", "The \"Strict-Transport-Security\" HTTP header is not configured to least \"{seconds}\" seconds. For enhanced security we recommend enabling HSTS as described in our security tips." : "De \"Strict-Transport-Security\" HTTP header is niet geconfigureerd als minimaal \"{seconds}\" seconden. Voor verbeterde beveiliging adviseren we HSTS in te schakelen zoals beschreven in onze security tips.", @@ -185,6 +189,7 @@ "Couldn't reset password because the token is invalid" : "Kon het wachtwoord niet herstellen, omdat het token ongeldig is", "Couldn't reset password because the token is expired" : "Kon het wachtwoord niet herstellen, omdat het token verlopen is", "Couldn't send reset email. Please make sure your username is correct." : "Kon e-mail niet versturen. Verifieer of uw gebruikersnaam correct is.", + "Could not send reset email because there is no email address for this username. Please contact your administrator." : "Kon geen herstel e-mail versturen, omdat er geen e-mailadres bekend is bij deze gebruikersnaam. Neem contact op met uw beheerder.", "%s password reset" : "%s wachtwoord reset", "Use the following link to reset your password: {link}" : "Gebruik de volgende link om uw wachtwoord te resetten: {link}", "New password" : "Nieuw wachtwoord", diff --git a/core/templates/login.php b/core/templates/login.php index 03be6258fd..e87b871c67 100644 --- a/core/templates/login.php +++ b/core/templates/login.php @@ -12,7 +12,7 @@ script('core', [
'); + print_unescaped(''); } ?>
@@ -56,11 +56,15 @@ script('core', [

- + t('Wrong password. Reset it?')); ?> - + +

+ t('Wrong password.')); ?> +

+