diff --git a/lib/private/share/share.php b/lib/private/share/share.php index 646511fd64db..759ad7a828b1 100644 --- a/lib/private/share/share.php +++ b/lib/private/share/share.php @@ -1074,6 +1074,15 @@ protected static function unshareItem(array $item) { \OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams); } + /** + * Check if a backend class has been registered for the specified item type + * @param string $itemType + * @return boolean True if a backend has been registered, false otherwise. + */ + public static function hasBackend($itemType) { + return isset(self::$backendTypes[$itemType]); + } + /** * Get the backend class for the specified item type * @param string $itemType @@ -1130,7 +1139,7 @@ public static function isResharingAllowed() { * @param string $itemType * @return array */ - private static function getCollectionItemTypes($itemType) { + public static function getCollectionItemTypes($itemType) { $collectionTypes = array($itemType); foreach (self::$backendTypes as $type => $backend) { if (in_array($backend['collectionOf'], $collectionTypes)) { @@ -1138,7 +1147,7 @@ private static function getCollectionItemTypes($itemType) { } } // TODO Add option for collections to be collection of themselves, only 'folder' does it now... - if (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder') { + if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) { unset($collectionTypes[0]); } // Return array if collections were found or the item type is a diff --git a/lib/private/tags.php b/lib/private/tags.php index 0b62caf2dd89..22fa4c70138b 100644 --- a/lib/private/tags.php +++ b/lib/private/tags.php @@ -5,6 +5,7 @@ * @author Thomas Tanghus * @copyright 2012-2013 Thomas Tanghus * @copyright 2012 Bart Visscher bartv@thisnet.nl +* @copyright 2014 Bernhard Reiter * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -34,14 +35,14 @@ namespace OC; +use OC\Tags\Backend\Database; + class Tags implements \OCP\ITags { /** - * Tags - * - * @var array + * FIXME var Backend\AbstractBackend */ - private $tags = array(); + private $backends = array(); /** * Used for storing objectid/categoryname pairs while rescanning. @@ -64,56 +65,26 @@ class Tags implements \OCP\ITags { */ private $user = null; - const TAG_TABLE = '*PREFIX*vcategory'; - const RELATION_TABLE = '*PREFIX*vcategory_to_object'; + public static $backendClasses = array( + 'local' => 'OC\Tags\Backend\Database', + 'shared' => 'OC\Tags\Backend\Shared' + ); const TAG_FAVORITE = '_$!!$_'; /** * Constructor. * - * @param string $user The user whos data the object will operate on. + * @param string $user The user whose data the object will operate on. * @param string $type */ public function __construct($user, $type, $defaultTags = array()) { $this->user = $user; $this->type = $type; - $this->loadTags($defaultTags); + foreach (self::$backendClasses as $backendName => $class) + $this->backends[$backendName] = new $class($user, $type); } - /** - * Load tags from db. - * - */ - protected function loadTags($defaultTags=array()) { - $this->tags = array(); - $result = null; - $sql = 'SELECT `id`, `category` FROM `' . self::TAG_TABLE . '` ' - . 'WHERE `uid` = ? AND `type` = ? ORDER BY `category`'; - try { - $stmt = \OCP\DB::prepare($sql); - $result = $stmt->execute(array($this->user, $this->type)); - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('core', __METHOD__. ', DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); - } - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - \OCP\Util::ERROR); - } - - if(!is_null($result)) { - while( $row = $result->fetchRow()) { - $this->tags[$row['id']] = $row['category']; - } - } - - if(count($defaultTags) > 0 && count($this->tags) === 0) { - $this->addMultiple($defaultTags, true); - } - \OCP\Util::writeLog('core', __METHOD__.', tags: ' . print_r($this->tags, true), - \OCP\Util::DEBUG); - - } /** * Check if any tags are saved for this type and user. @@ -121,7 +92,7 @@ protected function loadTags($defaultTags=array()) { * @return boolean. */ public function isEmpty() { - return count($this->tags) === 0; + return $this->backend->isEmpty(); } /** @@ -136,28 +107,30 @@ public function isEmpty() { * @return array */ public function getTags() { - if(!count($this->tags)) { + $tags = array(); + foreach ($this->backends as $name => $backend) + $tags += $backend->getUnsortedTags(); + + if(!count($tags)) return array(); - } - $tags = array_values($this->tags); uasort($tags, 'strnatcasecmp'); $tagMap = array(); - foreach($tags as $tag) { - if($tag !== self::TAG_FAVORITE) { + foreach($tags as $tagId => $tagName) { + if($tagName !== self::TAG_FAVORITE) { $tagMap[] = array( - 'id' => $this->array_searchi($tag, $this->tags), - 'name' => $tag + 'id' => $tagId, + 'name' => $tagName ); } } - return $tagMap; + return $tagMap; } /** - * Get the a list if items tagged with $tag. + * Get the a list of items tagged with $tag. * * Throws an exception if the tag could not be found. * @@ -165,47 +138,13 @@ public function getTags() { * @return array An array of object ids or false on error. */ public function getIdsForTag($tag) { - $result = null; - if(is_numeric($tag)) { - $tagId = $tag; - } elseif(is_string($tag)) { - $tag = trim($tag); - if($tag === '') { - \OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', \OCP\Util::DEBUG); - return false; - } - $tagId = $this->array_searchi($tag, $this->tags); - } - - if($tagId === false) { - $l10n = \OC_L10N::get('core'); - throw new \Exception( - $l10n->t('Could not find category "%s"', $tag) - ); - } + foreach ($this->backends as $name => $backend) + if ($id = $backend->hasTag($tag)) + break; $ids = array(); - $sql = 'SELECT `objid` FROM `' . self::RELATION_TABLE - . '` WHERE `categoryid` = ?'; - - try { - $stmt = \OCP\DB::prepare($sql); - $result = $stmt->execute(array($tagId)); - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); - return false; - } - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - \OCP\Util::ERROR); - return false; - } - - if(!is_null($result)) { - while( $row = $result->fetchRow()) { - $ids[] = (int)$row['objid']; - } - } + foreach ($this->backends as $name => $backend) + $ids += $backend->getIdsForTag($id); return $ids; } @@ -214,14 +153,19 @@ public function getIdsForTag($tag) { * Checks whether a tag is already saved. * * @param string $name The name to check for. - * @return bool + * @return The tag's id, or false if no tag of that name has been saved yet. */ public function hasTag($name) { - return $this->in_arrayi($name, $this->tags); + $id = false; + foreach ($this->backends as $name => $backend) + if ($id = $backend->hasTag($name)) + break; + + return $id; } /** - * Add a new tag. + * Add a new tag, owned by the current user. * * @param string $name A string with a name of the tag * @return false|string the id of the added tag or false on error. @@ -237,31 +181,8 @@ public function add($name) { \OCP\Util::writeLog('core', __METHOD__.', name: ' . $name. ' exists already', \OCP\Util::DEBUG); return false; } - try { - $result = \OCP\DB::insertIfNotExist( - self::TAG_TABLE, - array( - 'uid' => $this->user, - 'type' => $this->type, - 'category' => $name, - ) - ); - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); - return false; - } elseif((int)$result === 0) { - \OCP\Util::writeLog('core', __METHOD__.', Tag already exists: ' . $name, \OCP\Util::DEBUG); - return false; - } - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - \OCP\Util::ERROR); - return false; - } - $id = \OCP\DB::insertid(self::TAG_TABLE); - \OCP\Util::writeLog('core', __METHOD__.', id: ' . $id, \OCP\Util::DEBUG); - $this->tags[$id] = $name; - return $id; + + return $this->backends['local']->add($name); } /** @@ -280,28 +201,13 @@ public function rename($from, $to) { return false; } - $id = $this->array_searchi($from, $this->tags); - if($id === false) { - \OCP\Util::writeLog('core', __METHOD__.', tag: ' . $from. ' does not exist', \OCP\Util::DEBUG); - return false; - } + $id = false; + foreach ($this->backends as $name => $backend) + if ($id = $backend->hasTag($from)) + return $backend->rename($id, $to); - $sql = 'UPDATE `' . self::TAG_TABLE . '` SET `category` = ? ' - . 'WHERE `uid` = ? AND `type` = ? AND `id` = ?'; - try { - $stmt = \OCP\DB::prepare($sql); - $result = $stmt->execute(array($to, $this->user, $this->type, $id)); - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); - return false; - } - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - \OCP\Util::ERROR); - return false; - } - $this->tags[$id] = $to; - return true; + \OCP\Util::writeLog('core', __METHOD__.', tag: ' . $from. ' does not exist', \OCP\Util::DEBUG); + return false; } /** @@ -322,8 +228,7 @@ public function addMultiple($names, $sync=false, $id = null) { $newones = array(); foreach($names as $name) { - if(($this->in_arrayi( - $name, $this->tags) == false) && $name !== '') { + if(($this->hasTag($name) == false) && $name !== '') { $newones[] = $name; } if(!is_null($id) ) { @@ -333,61 +238,12 @@ public function addMultiple($names, $sync=false, $id = null) { } $this->tags = array_merge($this->tags, $newones); if($sync === true) { - $this->save(); + $this->backend->save(); } return true; } - /** - * Save the list of tags and their object relations - */ - protected function save() { - if(is_array($this->tags)) { - foreach($this->tags as $tag) { - try { - \OCP\DB::insertIfNotExist(self::TAG_TABLE, - array( - 'uid' => $this->user, - 'type' => $this->type, - 'category' => $tag, - )); - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - \OCP\Util::ERROR); - } - } - // reload tags to get the proper ids. - $this->loadTags(); - // Loop through temporarily cached objectid/tagname pairs - // and save relations. - $tags = $this->tags; - // For some reason this is needed or array_search(i) will return 0..? - ksort($tags); - foreach(self::$relations as $relation) { - $tagId = $this->array_searchi($relation['tag'], $tags); - \OCP\Util::writeLog('core', __METHOD__ . 'catid, ' . $relation['tag'] . ' ' . $tagId, \OCP\Util::DEBUG); - if($tagId) { - try { - \OCP\DB::insertIfNotExist(self::RELATION_TABLE, - array( - 'objid' => $relation['objid'], - 'categoryid' => $tagId, - 'type' => $this->type, - )); - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - \OCP\Util::ERROR); - } - } - } - self::$relations = array(); // reset - } else { - \OCP\Util::writeLog('core', __METHOD__.', $this->tags is not an array! ' - . print_r($this->tags, true), \OCP\Util::ERROR); - } - } - /** * Delete tags and tag/object relations for a user. * @@ -396,48 +252,7 @@ protected function save() { * @param array $arguments */ public static function post_deleteUser($arguments) { - // Find all objectid/tagId pairs. - $result = null; - try { - $stmt = \OCP\DB::prepare('SELECT `id` FROM `' . self::TAG_TABLE . '` ' - . 'WHERE `uid` = ?'); - $result = $stmt->execute(array($arguments['uid'])); - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); - } - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - \OCP\Util::ERROR); - } - - if(!is_null($result)) { - try { - $stmt = \OCP\DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` ' - . 'WHERE `categoryid` = ?'); - while( $row = $result->fetchRow()) { - try { - $stmt->execute(array($row['id'])); - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - \OCP\Util::ERROR); - } - } - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - \OCP\Util::ERROR); - } - } - try { - $stmt = \OCP\DB::prepare('DELETE FROM `' . self::TAG_TABLE . '` ' - . 'WHERE `uid` = ?'); - $result = $stmt->execute(array($arguments['uid'])); - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('core', __METHOD__. ', DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); - } - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__ . ', exception: ' - . $e->getMessage(), \OCP\Util::ERROR); - } + $this->backend->post_deleteUser($arguments); } /** @@ -447,28 +262,7 @@ public static function post_deleteUser($arguments) { * @return boolean Returns false on error. */ public function purgeObjects(array $ids) { - if(count($ids) === 0) { - // job done ;) - return true; - } - $updates = $ids; - try { - $query = 'DELETE FROM `' . self::RELATION_TABLE . '` '; - $query .= 'WHERE `objid` IN (' . str_repeat('?,', count($ids)-1) . '?) '; - $query .= 'AND `type`= ?'; - $updates[] = $this->type; - $stmt = \OCP\DB::prepare($query); - $result = $stmt->execute($updates); - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); - return false; - } - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: ' . $e->getMessage(), - \OCP\Util::ERROR); - return false; - } - return true; + return $this->backend->purgeObjects($ids); } /** @@ -517,32 +311,39 @@ public function removeFromFavorites($objid) { * @return boolean Returns false on error. */ public function tagAs($objid, $tag) { + $backendToUse = $this->backends['local']; // Default backend if(is_string($tag) && !is_numeric($tag)) { $tag = trim($tag); if($tag === '') { - \OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', \OCP\Util::DEBUG); + \OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', + \OCP\Util::DEBUG); return false; } - if(!$this->hasTag($tag)) { - $this->add($tag); + + $tagId = false; + foreach ($this->backends as $name => $backend) { + if ($tagId = $backend->hasTag($tag)) { + $backendToUse = $backend; + break; + } + } + + if(!$tagId) { + // Add new tag to this user's tags (i.e. using the local backend). + $tagId = $backendToUse->add($tag); } - $tagId = $this->array_searchi($tag, $this->tags); + } else { - $tagId = $tag; - } - try { - \OCP\DB::insertIfNotExist(self::RELATION_TABLE, - array( - 'objid' => $objid, - 'categoryid' => $tagId, - 'type' => $this->type, - )); - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - \OCP\Util::ERROR); - return false; + foreach ($this->backends as $name => $backend) { + if ($backend->hasTagId($tag)) { + $backendToUse = $backend; + $tagId = $tag; + break; + } + } } - return true; + + return $backendToUse->tagAs($objid, $tagId); } /** @@ -559,31 +360,32 @@ public function unTag($objid, $tag) { \OCP\Util::writeLog('core', __METHOD__.', Tag name is empty', \OCP\Util::DEBUG); return false; } - $tagId = $this->array_searchi($tag, $this->tags); - } else { - $tagId = $tag; - } - try { - $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` ' - . 'WHERE `objid` = ? AND `categoryid` = ? AND `type` = ?'; - $stmt = \OCP\DB::prepare($sql); - $stmt->execute(array($objid, $tagId, $this->type)); - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - \OCP\Util::ERROR); + foreach ($this->backends as $name => $backend) + if ($tagId = $backend->hasTag($tag)) + return $backend->unTag($objid, $tagId); + + // Tag with given name not found. return false; + + } else { + foreach ($this->backends as $name => $backend) + if ($backend->hasTagId($tag)) + return $backend->unTag($objid, $tag); } - return true; + + // Tag with given ID not found. + return false; } /** - * Delete tags from the + * Delete tags from the db. * * @param string[] $names An array of tags to delete * @return bool Returns false on error */ public function delete($names) { + $success = true; if(!is_array($names)) { $names = array($names); } @@ -591,47 +393,17 @@ public function delete($names) { $names = array_map('trim', $names); array_filter($names); - \OCP\Util::writeLog('core', __METHOD__ . ', before: ' - . print_r($this->tags, true), \OCP\Util::DEBUG); - foreach($names as $name) { - $id = null; - - if($this->hasTag($name)) { - $id = $this->array_searchi($name, $this->tags); - unset($this->tags[$id]); - } - try { - $stmt = \OCP\DB::prepare('DELETE FROM `' . self::TAG_TABLE . '` WHERE ' - . '`uid` = ? AND `type` = ? AND `category` = ?'); - $result = $stmt->execute(array($this->user, $this->type, $name)); - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); - } - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__ . ', exception: ' - . $e->getMessage(), \OCP\Util::ERROR); - return false; - } - if(!is_null($id) && $id !== false) { - try { - $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` ' - . 'WHERE `categoryid` = ?'; - $stmt = \OCP\DB::prepare($sql); - $result = $stmt->execute(array($id)); - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('core', - __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), - \OCP\Util::ERROR); - return false; - } - } catch(\Exception $e) { - \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - \OCP\Util::ERROR); - return false; + foreach ($this->backends as $backendName => $backend) { + $tagsInBackend = array(); + foreach ($names as $name) { + if ($backend->hasTag($name)) { + $tagsInBackend[] = $name; + unset($name); } } + $success &= $backend->delete($tagsInBackend); } - return true; + return $success; } // case-insensitive in_array diff --git a/lib/private/tags/backend/abstractbackend.php b/lib/private/tags/backend/abstractbackend.php new file mode 100644 index 000000000000..3e3f6073ceb4 --- /dev/null +++ b/lib/private/tags/backend/abstractbackend.php @@ -0,0 +1,191 @@ + +* @copyright 2012 Bart Visscher bartv@thisnet.nl +* @copyright 2014 Bernhard Reiter +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see . +* +*/ + +namespace OC\Tags\Backend; + +/** + * Subclass this class for tags backends. + */ +abstract class AbstractBackend { + + /** + * The name of the backend. + * + * @var string + */ + public $name; + + /** + * The tags this backend holds. + * + * @var array + */ + protected $tags = array(); + + /** + * The type of the items for which this backends holds tags + * + * @var string + */ + protected $type; + + /** + * The user whose tags this backend holds + * + * @var string + */ + protected $user; + + /** + * Constructor. + * + * @param string $user The user whose data the object will operate on. + * @param string $type + */ + public function __construct( + $user, + $type, + $defaultTags = array() + ) { + $this->user = $user; + $this->type = $type; + $this->loadTags($this->user, $this->type, $defaultTags); + } + + /** + * Load tags from db. + * + */ + public abstract function loadTags($user, $type, $defaultTags=array()); + + /** + * Check if any tags are saved for this type and user. + * + * @return boolean. + */ + public function isEmpty() { + return count($this->tags) === 0; + } + + /** + * Get a list of all tags stored in the backend. + */ + public function getUnsortedTags() { + return $this->tags; + } + + /** + * Get the list of items tagged with $tag. + * + * Throws an exception if the tag could not be found. + * + * @param string $tag Tag id or name. + * @return array An array of object ids or false on error. + */ + public abstract function getIdsForTag($tag); + + /** + * Checks whether a tag is already saved. + * + * @param string $name The name to check for. + * @return string The tag's id or false if it hasn't been saved yet. + */ + public function hasTag($name) { + return $this->array_searchi($name, $this->tags); + } + + /** + * Checks whether a tag given by id is already saved. + * + * @param int $id The id to check for. + * @return bool False if the tag hasn't been saved yet. + */ + public function hasTagId($id) { + return array_key_exists($id, $this->tags); + } + + /** + * Add a new tag. + * + * @param string $name A string with a name of the tag + * @return false|string the id of the added tag or false on error. + */ + public abstract function add($name); + + /** + * Rename tag. + * + * @param string $id The id of the existing tag + * @param string $to The new name of the tag. + * @return bool + */ + public abstract function rename($id, $to); + + /** + * Save the list of tags and their object relations + */ + public abstract function save(); + + /** + * Delete tag/object relations from the db + * + * @param array $ids The ids of the objects + * @return boolean Returns false on error. + */ + public abstract function purgeObjects(array $ids); + + /** + * Creates a tag/object relation. + * + * @param int $objid The id of the object + * @param string $tag The id or name of the tag + * @return boolean Returns false on error. + */ + public abstract function tagAs($objid, $tag); + + /** + * Delete single tag/object relation from the db + * + * @param int $objid The id of the object + * @param string $tag The id or name of the tag + * @return boolean + */ + public abstract function unTag($objid, $tag); + + /** + * Delete tags from the + * + * @param string[] $names An array of tags to delete + * @return bool Returns false on error + */ + public abstract function delete($names); + + // case-insensitive array_search + protected function array_searchi($needle, $haystack) { + if(!is_array($haystack)) { + return false; + } + return array_search(strtolower($needle), array_map('strtolower', $haystack)); + } +} diff --git a/lib/private/tags/backend/database.php b/lib/private/tags/backend/database.php new file mode 100644 index 000000000000..235afe5b3655 --- /dev/null +++ b/lib/private/tags/backend/database.php @@ -0,0 +1,431 @@ + +* @copyright 2012 Bart Visscher bartv@thisnet.nl +* @copyright 2014 Bernhard Reiter +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see . +* +*/ + +namespace OC\Tags\Backend; + +class Database extends AbstractBackend { + + /** + * The name of the backend. + * + * @var string + */ + public $name = 'local'; + + /** + * Tags + * + * @var array + */ + protected $tags = array(); + + /** + * Used for storing objectid/categoryname pairs while rescanning. + * + * @var array + */ + private static $relations = array(); + + const TAG_TABLE = '*PREFIX*vcategory'; + const RELATION_TABLE = '*PREFIX*vcategory_to_object'; + + /** + * Load tags from db. + * + */ + public function loadTags($user, $type, $defaultTags=array()) { + $this->tags = array(); + $result = null; + $sql = 'SELECT `id`, `category` FROM `' . self::TAG_TABLE . '` ' + . 'WHERE `uid` = ? AND `type` = ? ORDER BY `category`'; + try { + $stmt = \OCP\DB::prepare($sql); + $result = $stmt->execute(array($user, $type)); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('core', __METHOD__. ', DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); + } + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + } + + if(!is_null($result)) { + while( $row = $result->fetchRow()) { + $this->tags[$row['id']] = $row['category']; + } + } + + if(count($defaultTags) > 0 && count($this->tags) === 0) { + $this->addMultiple($defaultTags, true); + } + + return $this->tags; + } + + /** + * Get the list of items tagged with $tag. + * + * Throws an exception if the tag could not be found. + * + * @param string $tag Tag id or name. + * @return array An array of object ids or false on error. + */ + public function getIdsForTag($tag) { + return $this->getIdsForTagAndOwner($tag, $this->user); + } + + public function getIdsForTagAndOwner($tag, $user) { + $result = null; + $tagId = false; + if(is_numeric($tag)) { + $tagId = $tag; + } elseif(is_string($tag)) { + $tag = trim($tag); + if($tag === '') { + \OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', \OCP\Util::DEBUG); + return false; + } + $tagId = $this->array_searchi($tag, $this->tags); + } + + if($tagId === false) { + $l10n = \OC_L10N::get('core'); + throw new \Exception( + $l10n->t('Could not find category "%s"', $tag) + ); + } + + $ids = array(); + $sql = 'SELECT `objid` FROM `' . self::RELATION_TABLE + . '` WHERE `categoryid` = ?'; + // TODO: At this point, we'd need to find out the respective owners of the objects, as we're + // only looking for objects owned by $user. Unfortunately, there doesn't seem to be any + // straight-forward solution to this problem. + + try { + $stmt = \OCP\DB::prepare($sql); + $result = $stmt->execute(array($tagId)); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); + return false; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + return false; + } + + if(!is_null($result)) { + while( $row = $result->fetchRow()) { + $ids[] = (int)$row['objid']; + } + } + + return $ids; + } + + /** + * Add a new tag. + * + * @param string $name A string with a name of the tag + * @return false|string the id of the added tag or false on error. + */ + public function add($name) { + try { + $result = \OCP\DB::insertIfNotExist( + self::TAG_TABLE, + array( + 'uid' => $this->user, + 'type' => $this->type, + 'category' => $name, + ) + ); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); + return false; + } elseif((int)$result === 0) { + \OCP\Util::writeLog('core', __METHOD__.', Tag already exists: ' . $name, \OCP\Util::DEBUG); + return false; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + return false; + } + $id = \OCP\DB::insertid(self::TAG_TABLE); + \OCP\Util::writeLog('core', __METHOD__.', id: ' . $id, \OCP\Util::DEBUG); + $this->tags[$id] = $name; + return $id; + } + + /** + * Rename tag. + * + * @param string $from The id of the existing tag + * @param string $to The new name of the tag. + * @return bool + */ + public function rename($id, $to) { + $sql = 'UPDATE `' . self::TAG_TABLE . '` SET `category` = ? ' + . 'WHERE `type` = ? AND `id` = ?'; + try { + $stmt = \OCP\DB::prepare($sql); + $result = $stmt->execute(array($to, $this->type, $id)); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); + return false; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + return false; + } + $this->tags[$id] = $to; + return true; + } + + /** + * Save the list of tags and their object relations + */ + public function save() { + if(is_array($this->tags)) { + foreach($this->tags as $tag) { + try { + \OCP\DB::insertIfNotExist(self::TAG_TABLE, + array( + 'uid' => $this->user, + 'type' => $this->type, + 'category' => $tag, + )); + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + } + } + // reload tags to get the proper ids. + $this->loadTags(); + // Loop through temporarily cached objectid/tagname pairs + // and save relations. + $tags = $this->tags; + // For some reason this is needed or array_search(i) will return 0..? + ksort($tags); + foreach(self::$relations as $relation) { + $tagId = $this->array_searchi($relation['tag'], $tags); + \OCP\Util::writeLog('core', __METHOD__ . 'catid, ' . $relation['tag'] . ' ' . $tagId, \OCP\Util::DEBUG); + if($tagId) { + try { + \OCP\DB::insertIfNotExist(self::RELATION_TABLE, + array( + 'objid' => $relation['objid'], + 'categoryid' => $tagId, + 'type' => $this->type, + )); + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + } + } + } + self::$relations = array(); // reset + } else { + \OCP\Util::writeLog('core', __METHOD__.', $this->tags is not an array! ' + . print_r($this->tags, true), \OCP\Util::ERROR); + } + } + + /** + * Delete tags and tag/object relations for a user. + * + * For hooking up on post_deleteUser + * + * @param array $arguments + */ + public static function post_deleteUser($arguments) { + // Find all objectid/tagId pairs. + $result = null; + try { + $stmt = \OCP\DB::prepare('SELECT `id` FROM `' . self::TAG_TABLE . '` ' + . 'WHERE `uid` = ?'); + $result = $stmt->execute(array($arguments['uid'])); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); + } + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + } + + if(!is_null($result)) { + try { + $stmt = \OCP\DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` ' + . 'WHERE `categoryid` = ?'); + while( $row = $result->fetchRow()) { + try { + $stmt->execute(array($row['id'])); + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + } + } + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + } + } + try { + $stmt = \OCP\DB::prepare('DELETE FROM `' . self::TAG_TABLE . '` ' + . 'WHERE `uid` = ?'); + $result = $stmt->execute(array($arguments['uid'])); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('core', __METHOD__. ', DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); + } + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__ . ', exception: ' + . $e->getMessage(), \OCP\Util::ERROR); + } + } + + /** + * Delete tag/object relations from the db + * + * @param array $ids The ids of the objects + * @return boolean Returns false on error. + */ + public function purgeObjects(array $ids) { + if(count($ids) === 0) { + // job done ;) + return true; + } + $updates = $ids; + try { + $query = 'DELETE FROM `' . self::RELATION_TABLE . '` '; + $query .= 'WHERE `objid` IN (' . str_repeat('?,', count($ids)-1) . '?) '; + $query .= 'AND `type`= ?'; + $updates[] = $this->type; + $stmt = \OCP\DB::prepare($query); + $result = $stmt->execute($updates); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); + return false; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: ' . $e->getMessage(), + \OCP\Util::ERROR); + return false; + } + return true; + } + + /** + * Creates a tag/object relation. + * + * @param int $objid The id of the object + * @param int $tagId The id of the tag + * @return boolean Returns false on error. + */ + public function tagAs($objid, $tagId) { + try { + \OCP\DB::insertIfNotExist(self::RELATION_TABLE, + array( + 'objid' => $objid, + 'categoryid' => $tagId, + 'type' => $this->type, + )); + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + return false; + } + return true; + } + + /** + * Delete single tag/object relation from the db + * + * @param int $objid The id of the object + * @param string $tag The id or name of the tag + * @return boolean + */ + public function unTag($objid, $tagId) { + try { + $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` ' + . 'WHERE `objid` = ? AND `categoryid` = ? AND `type` = ?'; + $stmt = \OCP\DB::prepare($sql); + $stmt->execute(array($objid, $tagId, $this->type)); + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + return false; + } + return true; + } + + /** + * Delete tags from the db. + * + * @param string[] $names An array of tags to delete + * @return bool Returns false on error + */ + public function delete($names) { + foreach($names as $name) { + $id = null; + + if($this->hasTag($name)) { + $id = $this->array_searchi($name, $this->tags); + unset($this->tags[$id]); + } + try { + $stmt = \OCP\DB::prepare('DELETE FROM `' . self::TAG_TABLE . '` WHERE ' + . '`type` = ? AND `category` = ?'); + $result = $stmt->execute(array($this->type, $name)); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); + } + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__ . ', exception: ' + . $e->getMessage(), \OCP\Util::ERROR); + return false; + } + if(!is_null($id) && $id !== false) { + try { + $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` ' + . 'WHERE `categoryid` = ?'; + $stmt = \OCP\DB::prepare($sql); + $result = $stmt->execute(array($id)); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('core', + __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), + \OCP\Util::ERROR); + return false; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + return false; + } + } + } + return true; + } +} diff --git a/lib/private/tags/backend/shared.php b/lib/private/tags/backend/shared.php new file mode 100644 index 000000000000..b3f83ab17d9f --- /dev/null +++ b/lib/private/tags/backend/shared.php @@ -0,0 +1,99 @@ + +* @copyright 2012 Bart Visscher bartv@thisnet.nl +* @copyright 2014 Bernhard Reiter +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see . +* +*/ + +namespace OC\Tags\Backend; + +use OC\Tags\Backend\Database; +use OC\Share\Share; + +class Shared extends Database { + + /** + * The name of the backend. + * + * @var string + */ + public $name = 'shared'; + + /** + * The shared tags' owners; + * + * @var string + */ + private $owners; + + /** + * Load shared tags for a given user and type. + * + * @param string $user The user whose tags we're querying for. + * @param string $type The type for which we're querying the corresponding tags. + */ + public function loadTags($user, $type, $defaultTags=array()) { + // First, we find out if $type is part of a collection (and if that collection is part of + // another one and so on). Of these collection types, along with our original $type, we make a + // list of the ones for which a sharing backend has been registered. + // + // FIXME: Ideally, we wouldn't need to nest getItemsSharedWith in this loop but just call it + // with its $includeCollections parameter set to true. Unfortunately, this fails currently. + $this->owners = array(); + foreach (Share::getCollectionItemTypes($type) as $collectionType) { + if (\OCP\Share::hasBackend($collectionType)) { + $backend = \OCP\Share::getBackend($collectionType); + $allMaybeSharedItems[$collectionType] = \OCP\Share::getItemsSharedWith( + $collectionType, + \OCP\Share::FORMAT_NONE + ); + } + } + + // We take a look at all shared items of the given $type (or of the collections it is part of) + // and find out their owners. Then, we gather the tags for the original $type from all owners, + // and return them as elements of a list that look like "Tag (owner)". + $tags = array(); + foreach ($allMaybeSharedItems as $collectionType => $maybeSharedItems) { + foreach ($maybeSharedItems as $sharedItem) { + if (isset($sharedItem['id'])) { //workaround for https://github.com/owncloud/core/issues/2814 + $owner = $sharedItem['uid_owner']; + $this->owners[] = $owner; + // $displayname_owner = $sharedItem['displayname_owner']; + foreach (parent::loadTags($owner, $type, $defaultTags) as $id => $tag) { + // We might want to use some regular tag sharing backend for setting the target name. + $this->tags[$id] = $tag; // . ' (' . $owner . ')'; + } + } + } + } + + return $this->tags; + } + + public function getIdsForTag($tag) { + $ids = array(); + foreach ($this->owners as $owner) + $ids += parent::getIdsForTagAndOwner($tag, $owner); + + return $ids; + } + +} diff --git a/lib/public/share.php b/lib/public/share.php index e6519dd3e3a8..23e3ca35b3eb 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -305,6 +305,15 @@ public static function setExpirationDate($itemType, $itemSource, $date, $shareTi return \OC\Share\Share::setExpirationDate($itemType, $itemSource, $date, $shareTime); } + /** + * Check if a backend class has been registered for the specified item type + * @param string $itemType + * @return boolean True if a backend has been registered, false otherwise. + */ + public static function hasBackend($itemType) { + return \OC\Share\Share::hasBackend($itemType); + } + /** * Get the backend class for the specified item type * @param string $itemType