diff --git a/lib/public/iservercontainer.php b/lib/public/iservercontainer.php index 4478a4e8a6..0df746a28e 100644 --- a/lib/public/iservercontainer.php +++ b/lib/public/iservercontainer.php @@ -55,6 +55,13 @@ interface IServerContainer { */ function getPreviewManager(); + /** + * Returns the tag manager which can get and set tags for different object types + * + * @return \OCP\ITags + */ + function getTagManager(); + /** * Returns the root folder of ownCloud's data directory * diff --git a/lib/public/itags.php b/lib/public/itags.php new file mode 100644 index 0000000000..047d4f5f40 --- /dev/null +++ b/lib/public/itags.php @@ -0,0 +1,173 @@ + +* +* 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 OCP; + +// FIXME: Where should I put this? Or should it be implemented as a Listener? +\OC_Hook::connect('OC_User', 'post_deleteUser', 'OC\Tags', 'post_deleteUser'); + +/** + * Class for easily tagging objects by their id + * + * A tag can be e.g. 'Family', 'Work', 'Chore', 'Special Occation' or + * anything else that is either parsed from a vobject or that the user chooses + * to add. + * Tag names are not case-sensitive, but will be saved with the case they + * are entered in. If a user already has a tag 'family' for a type, and + * tries to add a tag named 'Family' it will be silently ignored. + */ + +interface ITags { + + /** + * Load tags from db. + * + * @param string $type The type identifier e.g. 'contact' or 'event'. + * @param array $defaultTags An array of default tags to be used if none are stored. + * @return \OCP\ITags + */ + public function loadTagsFor($type, $defaultTags=array()); + + /** + * Check if any tags are saved for this type and user. + * + * @return boolean. + */ + public function isEmpty(); + + /** + * Get the tags for a specific user. + * + * This returns an array with id/name maps: + * [ + * ['id' => 0, 'name' = 'First tag'], + * ['id' => 1, 'name' = 'Second tag'], + * ] + * + * @returns array + */ + public function tags(); + + /** + * Get the a list if items tagged with $tag. + * + * Throws an exception if the tag could not be found. + * + * @param string|integer $tag Tag id or name. + * @return array An array of object ids or false on error. + */ + public function idsForTag($tag); + + /** + * Checks whether a tag is already saved. + * + * @param string $name The name to check for. + * @return bool + */ + public function hasTag($name); + + /** + * Add a new tag. + * + * @param string $name A string with a name of the tag + * @return int the id of the added tag or false if it already exists. + */ + public function add($name); + + /** + * Rename tag. + * + * @param string $from The name of the existing tag + * @param string $to The new name of the tag. + * @return bool + */ + public function rename($from, $to); + + /** + * Add a list of new tags. + * + * @param string[] $names A string with a name or an array of strings containing + * the name(s) of the to add. + * @param bool $sync When true, save the tags + * @param int|null $id int Optional object id to add to this|these tag(s) + * @return bool Returns false on error. + */ + public function addMulti($names, $sync=false, $id = null); + + /** + * 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); + + /** + * Get favorites for an object type + * + * @return array An array of object ids. + */ + public function getFavorites(); + + /** + * Add an object to favorites + * + * @param int $objid The id of the object + * @return boolean + */ + public function addToFavorites($objid); + + /** + * Remove an object from favorites + * + * @param int $objid The id of the object + * @return boolean + */ + public function removeFromFavorites($objid); + + /** + * Creates a tag/object relation. + * + * @param int $objid The id of the object + * @param int|string $tag The id or name of the tag + * @return boolean Returns false on database error. + */ + public function tagAs($objid, $tag); + + /** + * Delete single tag/object relation from the db + * + * @param int $objid The id of the object + * @param int|string $tag The id or name of the tag + * @return boolean + */ + public function unTag($objid, $tag); + + /** + * Delete tags from the + * + * @param string[] $names An array of tags to delete + * @return bool Returns false on error + */ + public function delete($names); + +} \ No newline at end of file diff --git a/lib/server.php b/lib/server.php index 804af6b0ea..b09d380b0e 100644 --- a/lib/server.php +++ b/lib/server.php @@ -49,8 +49,11 @@ class Server extends SimpleContainer implements IServerContainer { $this->registerService('PreviewManager', function($c) { return new PreviewManager(); }); - $this->registerService('RootFolder', function($c) { - // TODO: get user from container as well + $this->registerService('TagManager', function($c){ + return new Tags(); + }); + $this->registerService('RootFolder', function($c){ + // TODO: get user and user manager from container as well $user = \OC_User::getUser(); /** @var $c SimpleContainer */ $userManager = $c->query('UserManager'); @@ -139,6 +142,15 @@ class Server extends SimpleContainer implements IServerContainer { return $this->query('PreviewManager'); } + /** + * Returns the tag manager which can get and set tags for different object types + * + * @return \OCP\ITags + */ + function getTagManager() { + return $this->query('TagManager'); + } + /** * Returns the root folder of ownCloud's data directory * diff --git a/lib/tags.php b/lib/tags.php new file mode 100644 index 0000000000..4aafff8e1b --- /dev/null +++ b/lib/tags.php @@ -0,0 +1,621 @@ + +* @copyright 2012 Bart Visscher bartv@thisnet.nl +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see . +* +*/ + +/** + * Class for easily tagging objects by their id + * + * A tag can be e.g. 'Family', 'Work', 'Chore', 'Special Occation' or + * anything else that is either parsed from a vobject or that the user chooses + * to add. + * Tag names are not case-sensitive, but will be saved with the case they + * are entered in. If a user already has a tag 'family' for a type, and + * tries to add a tag named 'Family' it will be silently ignored. + */ + +namespace OC; + +class Tags implements \OCP\ITags { + + /** + * Tags + */ + private $tags = array(); + + /** + * Used for storing objectid/categoryname pairs while rescanning. + */ + private static $relations = array(); + + private $type = null; + private $user = null; + + const TAG_TABLE = '*PREFIX*vcategory'; + const RELATION_TABLE = '*PREFIX*vcategory_to_object'; + + const TAG_FAVORITE = '_$!!$_'; + + /** + * Constructor. + * + * @param string $user The user whos data the object will operate on. + */ + public function __construct($user) { + + $this->user = $user; + + } + + /** + * Load tags from db. + * + * @param string $type The type identifier e.g. 'contact' or 'event'. + * @param array $defaultTags An array of default tags to be used if none are stored. + * @return \OCP\ITags + */ + public function loadTagsFor($type, $defaultTags=array()) { + $this->type = $type; + $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->addMulti($defaultTags, true); + } + \OCP\Util::writeLog('core', __METHOD__.', tags: ' . print_r($this->tags, true), + \OCP\Util::DEBUG); + + return $this; + } + + /** + * Check if any tags are saved for this type and user. + * + * @return boolean. + */ + public function isEmpty() { + $sql = 'SELECT COUNT(*) FROM `' . self::TAG_TABLE . '` ' + . 'WHERE `uid` = ? AND `type` = ?'; + 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); + return false; + } + return ($result->numRows() === 0); + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), + \OCP\Util::ERROR); + return false; + } + } + + /** + * Get the tags for a specific user. + * + * This returns an array with id/name maps: + * [ + * ['id' => 0, 'name' = 'First tag'], + * ['id' => 1, 'name' = 'Second tag'], + * ] + * + * @return array + */ + public function tags() { + if(!count($this->tags)) { + return array(); + } + + $tags = array_values($this->tags); + uasort($tags, 'strnatcasecmp'); + $tagMap = array(); + + foreach($tags as $tag) { + if($tag !== self::TAG_FAVORITE) { + $tagMap[] = array( + 'id' => $this->array_searchi($tag, $this->tags), + 'name' => $tag + ); + } + } + return $tagMap; + + } + + /** + * Get the a list if items tagged with $tag. + * + * Throws an exception if the tag could not be found. + * + * @param string|integer $tag Tag id or name. + * @return array An array of object ids or false on error. + */ + public function idsForTag($tag) { + $result = null; + if(is_numeric($tag)) { + $tagId = $tag; + } elseif(is_string($tag)) { + $tag = trim($tag); + $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` = ?'; + + 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; + } + + /** + * Checks whether a tag is already saved. + * + * @param string $name The name to check for. + * @return bool + */ + public function hasTag($name) { + return $this->in_arrayi($name, $this->tags); + } + + /** + * Add a new tag. + * + * @param string $name A string with a name of the tag + * @return int the id of the added tag or false if it already exists. + */ + public function add($name) { + $name = trim($name); + + if($this->hasTag($name)) { + \OCP\Util::writeLog('core', __METHOD__.', name: ' . $name. ' exists already', \OCP\Util::DEBUG); + return false; + } + try { + \OCP\DB::insertIfNotExist(self::TAG_TABLE, + array( + 'uid' => $this->user, + 'type' => $this->type, + 'category' => $name, + )); + } 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 name of the existing tag + * @param string $to The new name of the tag. + * @return bool + */ + public function rename($from, $to) { + $from = trim($from); + $to = trim($to); + $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; + } + + $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; + } + + /** + * Add a list of new tags. + * + * @param string[] $names A string with a name or an array of strings containing + * the name(s) of the to add. + * @param bool $sync When true, save the tags + * @param int|null $id int Optional object id to add to this|these tag(s) + * @return bool Returns false on error. + */ + public function addMulti($names, $sync=false, $id = null) { + if(!is_array($names)) { + $names = array($names); + } + $names = array_map('trim', $names); + $newones = array(); + foreach($names as $name) { + if(($this->in_arrayi( + $name, $this->tags) == false) && $name !== '') { + $newones[] = $name; + } + if(!is_null($id) ) { + // Insert $objectid, $categoryid pairs if not exist. + self::$relations[] = array('objid' => $id, 'tag' => $name); + } + } + $this->tags = array_merge($this->tags, $newones); + if($sync === true) { + $this->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->loadTagsFor($this->type); + // 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 + */ + 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; + } + + /** + * Get favorites for an object type + * + * @return array An array of object ids. + */ + public function getFavorites() { + try { + return $this->idsForTag(self::TAG_FAVORITE); + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__.', exception: ' . $e->getMessage(), + \OCP\Util::ERROR); + return array(); + } + } + + /** + * Add an object to favorites + * + * @param int $objid The id of the object + * @return boolean + */ + public function addToFavorites($objid) { + if(!$this->hasCategory(self::TAG_FAVORITE)) { + $this->add(self::TAG_FAVORITE, true); + } + return $this->tagAs($objid, self::TAG_FAVORITE, $this->type); + } + + /** + * Remove an object from favorites + * + * @param int $objid The id of the object + * @return boolean + */ + public function removeFromFavorites($objid) { + return $this->unTag($objid, self::TAG_FAVORITE, $this->type); + } + + /** + * Creates a tag/object relation. + * + * @param int $objid The id of the object + * @param int|string $tag The id or name of the tag + * @return boolean Returns false on database error. + */ + public function tagAs($objid, $tag) { + if(is_string($tag) && !is_numeric($tag)) { + $tag = trim($tag); + if(!$this->hasTag($tag)) { + $this->add($tag, true); + } + $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; + } + return true; + } + + /** + * Delete single tag/object relation from the db + * + * @param int $objid The id of the object + * @param int|string $tag The id or name of the tag + * @return boolean + */ + public function unTag($objid, $tag) { + if(is_string($tag) && !is_numeric($tag)) { + $tag = trim($tag); + $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); + return false; + } + return true; + } + + /** + * Delete tags from the + * + * @param string[] $names An array of tags to delete + * @return bool Returns false on error + */ + public function delete($names) { + if(!is_array($names)) { + $names = array($names); + } + + $names = array_map('trim', $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; + } + } + } + return true; + } + + // case-insensitive in_array + private function in_arrayi($needle, $haystack) { + if(!is_array($haystack)) { + return false; + } + return in_array(strtolower($needle), array_map('strtolower', $haystack)); + } + + // case-insensitive array_search + private function array_searchi($needle, $haystack) { + if(!is_array($haystack)) { + return false; + } + return array_search(strtolower($needle), array_map('strtolower', $haystack)); + } +} diff --git a/lib/vcategories.php b/lib/vcategories.php deleted file mode 100644 index a7e4c54be2..0000000000 --- a/lib/vcategories.php +++ /dev/null @@ -1,833 +0,0 @@ - -* @copyright 2012 Bart Visscher bartv@thisnet.nl -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see . -* -*/ - -OC_Hook::connect('OC_User', 'post_deleteUser', 'OC_VCategories', 'post_deleteUser'); - -/** - * Class for easy access to categories in VCARD, VEVENT, VTODO and VJOURNAL. - * A Category can be e.g. 'Family', 'Work', 'Chore', 'Special Occation' or - * anything else that is either parsed from a vobject or that the user chooses - * to add. - * Category names are not case-sensitive, but will be saved with the case they - * are entered in. If a user already has a category 'family' for a type, and - * tries to add a category named 'Family' it will be silently ignored. - */ -class OC_VCategories { - - /** - * Categories - */ - private $categories = array(); - - /** - * Used for storing objectid/categoryname pairs while rescanning. - */ - private static $relations = array(); - - private $type = null; - private $user = null; - - const CATEGORY_TABLE = '*PREFIX*vcategory'; - const RELATION_TABLE = '*PREFIX*vcategory_to_object'; - - const CATEGORY_FAVORITE = '_$!!$_'; - - const FORMAT_LIST = 0; - const FORMAT_MAP = 1; - - /** - * @brief Constructor. - * @param $type The type identifier e.g. 'contact' or 'event'. - * @param $user The user whos data the object will operate on. This - * parameter should normally be omitted but to make an app able to - * update categories for all users it is made possible to provide it. - * @param $defcategories An array of default categories to be used if none is stored. - */ - public function __construct($type, $user=null, $defcategories=array()) { - $this->type = $type; - $this->user = is_null($user) ? OC_User::getUser() : $user; - - $this->loadCategories(); - OCP\Util::writeLog('core', __METHOD__ . ', categories: ' - . print_r($this->categories, true), - OCP\Util::DEBUG - ); - - if($defcategories && count($this->categories) === 0) { - $this->addMulti($defcategories, true); - } - } - - /** - * @brief Load categories from db. - */ - private function loadCategories() { - $this->categories = array(); - $result = null; - $sql = 'SELECT `id`, `category` FROM `' . self::CATEGORY_TABLE . '` ' - . 'WHERE `uid` = ? AND `type` = ? ORDER BY `category`'; - try { - $stmt = OCP\DB::prepare($sql); - $result = $stmt->execute(array($this->user, $this->type)); - if (OC_DB::isError($result)) { - OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR); - } - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - OCP\Util::ERROR); - } - - if(!is_null($result)) { - while( $row = $result->fetchRow()) { - // The keys are prefixed because array_search wouldn't work otherwise :-/ - $this->categories[$row['id']] = $row['category']; - } - } - OCP\Util::writeLog('core', __METHOD__.', categories: ' . print_r($this->categories, true), - OCP\Util::DEBUG); - } - - - /** - * @brief Check if any categories are saved for this type and user. - * @returns boolean. - * @param $type The type identifier e.g. 'contact' or 'event'. - * @param $user The user whos categories will be checked. If not set current user will be used. - */ - public static function isEmpty($type, $user = null) { - $user = is_null($user) ? OC_User::getUser() : $user; - $sql = 'SELECT COUNT(*) FROM `' . self::CATEGORY_TABLE . '` ' - . 'WHERE `uid` = ? AND `type` = ?'; - try { - $stmt = OCP\DB::prepare($sql); - $result = $stmt->execute(array($user, $type)); - if (OC_DB::isError($result)) { - OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR); - return false; - } - return ($result->numRows() === 0); - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - OCP\Util::ERROR); - return false; - } - } - - /** - * @brief Get the categories for a specific user. - * @param - * @returns array containing the categories as strings. - */ - public function categories($format = null) { - if(!$this->categories) { - return array(); - } - $categories = array_values($this->categories); - uasort($categories, 'strnatcasecmp'); - if($format == self::FORMAT_MAP) { - $catmap = array(); - foreach($categories as $category) { - if($category !== self::CATEGORY_FAVORITE) { - $catmap[] = array( - 'id' => $this->array_searchi($category, $this->categories), - 'name' => $category - ); - } - } - return $catmap; - } - - // Don't add favorites to normal categories. - $favpos = array_search(self::CATEGORY_FAVORITE, $categories); - if($favpos !== false) { - return array_splice($categories, $favpos); - } else { - return $categories; - } - } - - /** - * Get the a list if items belonging to $category. - * - * Throws an exception if the category could not be found. - * - * @param string|integer $category Category id or name. - * @returns array An array of object ids or false on error. - */ - public function idsForCategory($category) { - $result = null; - if(is_numeric($category)) { - $catid = $category; - } elseif(is_string($category)) { - $category = trim($category); - $catid = $this->array_searchi($category, $this->categories); - } - OCP\Util::writeLog('core', __METHOD__.', category: '.$catid.' '.$category, OCP\Util::DEBUG); - if($catid === false) { - $l10n = OC_L10N::get('core'); - throw new Exception( - $l10n->t('Could not find category "%s"', $category) - ); - } - - $ids = array(); - $sql = 'SELECT `objid` FROM `' . self::RELATION_TABLE - . '` WHERE `categoryid` = ?'; - - try { - $stmt = OCP\DB::prepare($sql); - $result = $stmt->execute(array($catid)); - if (OC_DB::isError($result)) { - OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::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; - } - - /** - * Get the a list if items belonging to $category. - * - * Throws an exception if the category could not be found. - * - * @param string|integer $category Category id or name. - * @param array $tableinfo Array in the form {'tablename' => table, 'fields' => ['field1', 'field2']} - * @param int $limit - * @param int $offset - * - * This generic method queries a table assuming that the id - * field is called 'id' and the table name provided is in - * the form '*PREFIX*table_name'. - * - * If the category name cannot be resolved an exception is thrown. - * - * TODO: Maybe add the getting permissions for objects? - * - * @returns array containing the resulting items or false on error. - */ - public function itemsForCategory($category, $tableinfo, $limit = null, $offset = null) { - $result = null; - if(is_numeric($category)) { - $catid = $category; - } elseif(is_string($category)) { - $category = trim($category); - $catid = $this->array_searchi($category, $this->categories); - } - OCP\Util::writeLog('core', __METHOD__.', category: '.$catid.' '.$category, OCP\Util::DEBUG); - if($catid === false) { - $l10n = OC_L10N::get('core'); - throw new Exception( - $l10n->t('Could not find category "%s"', $category) - ); - } - $fields = ''; - foreach($tableinfo['fields'] as $field) { - $fields .= '`' . $tableinfo['tablename'] . '`.`' . $field . '`,'; - } - $fields = substr($fields, 0, -1); - - $items = array(); - $sql = 'SELECT `' . self::RELATION_TABLE . '`.`categoryid`, ' . $fields - . ' FROM `' . $tableinfo['tablename'] . '` JOIN `' - . self::RELATION_TABLE . '` ON `' . $tableinfo['tablename'] - . '`.`id` = `' . self::RELATION_TABLE . '`.`objid` WHERE `' - . self::RELATION_TABLE . '`.`categoryid` = ?'; - - try { - $stmt = OCP\DB::prepare($sql, $limit, $offset); - $result = $stmt->execute(array($catid)); - if (OC_DB::isError($result)) { - OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::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()) { - $items[] = $row; - } - } - //OCP\Util::writeLog('core', __METHOD__.', count: ' . count($items), OCP\Util::DEBUG); - //OCP\Util::writeLog('core', __METHOD__.', sql: ' . $sql, OCP\Util::DEBUG); - - return $items; - } - - /** - * @brief Checks whether a category is already saved. - * @param $name The name to check for. - * @returns bool - */ - public function hasCategory($name) { - return $this->in_arrayi($name, $this->categories); - } - - /** - * @brief Add a new category. - * @param $name A string with a name of the category - * @returns int the id of the added category or false if it already exists. - */ - public function add($name) { - $name = trim($name); - OCP\Util::writeLog('core', __METHOD__.', name: ' . $name, OCP\Util::DEBUG); - if($this->hasCategory($name)) { - OCP\Util::writeLog('core', __METHOD__.', name: ' . $name. ' exists already', OCP\Util::DEBUG); - return false; - } - try { - OCP\DB::insertIfNotExist(self::CATEGORY_TABLE, - array( - 'uid' => $this->user, - 'type' => $this->type, - 'category' => $name, - )); - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - OCP\Util::ERROR); - return false; - } - $id = OCP\DB::insertid(self::CATEGORY_TABLE); - OCP\Util::writeLog('core', __METHOD__.', id: ' . $id, OCP\Util::DEBUG); - $this->categories[$id] = $name; - return $id; - } - - /** - * @brief Rename category. - * @param string $from The name of the existing category - * @param string $to The new name of the category. - * @returns bool - */ - public function rename($from, $to) { - $from = trim($from); - $to = trim($to); - $id = $this->array_searchi($from, $this->categories); - if($id === false) { - OCP\Util::writeLog('core', __METHOD__.', category: ' . $from. ' does not exist', OCP\Util::DEBUG); - return false; - } - - $sql = 'UPDATE `' . self::CATEGORY_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 (OC_DB::isError($result)) { - OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR); - return false; - } - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - OCP\Util::ERROR); - return false; - } - $this->categories[$id] = $to; - return true; - } - - /** - * @brief Add a new category. - * @param $names A string with a name or an array of strings containing - * the name(s) of the categor(y|ies) to add. - * @param $sync bool When true, save the categories - * @param $id int Optional object id to add to this|these categor(y|ies) - * @returns bool Returns false on error. - */ - public function addMulti($names, $sync=false, $id = null) { - if(!is_array($names)) { - $names = array($names); - } - $names = array_map('trim', $names); - $newones = array(); - foreach($names as $name) { - if(($this->in_arrayi( - $name, $this->categories) == false) && $name != '') { - $newones[] = $name; - } - if(!is_null($id) ) { - // Insert $objectid, $categoryid pairs if not exist. - self::$relations[] = array('objid' => $id, 'category' => $name); - } - } - $this->categories = array_merge($this->categories, $newones); - if($sync === true) { - $this->save(); - } - - return true; - } - - /** - * @brief Extracts categories from a vobject and add the ones not already present. - * @param $vobject The instance of OC_VObject to load the categories from. - */ - public function loadFromVObject($id, $vobject, $sync=false) { - $this->addMulti($vobject->getAsArray('CATEGORIES'), $sync, $id); - } - - /** - * @brief Reset saved categories and rescan supplied vobjects for categories. - * @param $objects An array of vobjects (as text). - * To get the object array, do something like: - * // For Addressbook: - * $categories = new OC_VCategories('contacts'); - * $stmt = OC_DB::prepare( 'SELECT `carddata` FROM `*PREFIX*contacts_cards`' ); - * $result = $stmt->execute(); - * $objects = array(); - * if(!is_null($result)) { - * while( $row = $result->fetchRow()){ - * $objects[] = array($row['id'], $row['carddata']); - * } - * } - * $categories->rescan($objects); - */ - public function rescan($objects, $sync=true, $reset=true) { - - if($reset === true) { - $result = null; - // Find all objectid/categoryid pairs. - try { - $stmt = OCP\DB::prepare('SELECT `id` FROM `' . self::CATEGORY_TABLE . '` ' - . 'WHERE `uid` = ? AND `type` = ?'); - $result = $stmt->execute(array($this->user, $this->type)); - if (OC_DB::isError($result)) { - OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR); - return false; - } - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - OCP\Util::ERROR); - } - - // And delete them. - if(!is_null($result)) { - $stmt = OCP\DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` ' - . 'WHERE `categoryid` = ? AND `type`= ?'); - while( $row = $result->fetchRow()) { - $stmt->execute(array($row['id'], $this->type)); - } - } - try { - $stmt = OCP\DB::prepare('DELETE FROM `' . self::CATEGORY_TABLE . '` ' - . 'WHERE `uid` = ? AND `type` = ?'); - $result = $stmt->execute(array($this->user, $this->type)); - if (OC_DB::isError($result)) { - OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR); - return; - } - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__ . ', exception: ' - . $e->getMessage(), OCP\Util::ERROR); - return; - } - $this->categories = array(); - } - // Parse all the VObjects - foreach($objects as $object) { - $vobject = OC_VObject::parse($object[1]); - if(!is_null($vobject)) { - // Load the categories - $this->loadFromVObject($object[0], $vobject, $sync); - } else { - OC_Log::write('core', __METHOD__ . ', unable to parse. ID: ' . ', ' - . substr($object, 0, 100) . '(...)', OC_Log::DEBUG); - } - } - $this->save(); - } - - /** - * @brief Save the list with categories - */ - private function save() { - if(is_array($this->categories)) { - foreach($this->categories as $category) { - try { - OCP\DB::insertIfNotExist(self::CATEGORY_TABLE, - array( - 'uid' => $this->user, - 'type' => $this->type, - 'category' => $category, - )); - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - OCP\Util::ERROR); - } - } - // reload categories to get the proper ids. - $this->loadCategories(); - // Loop through temporarily cached objectid/categoryname pairs - // and save relations. - $categories = $this->categories; - // For some reason this is needed or array_search(i) will return 0..? - ksort($categories); - foreach(self::$relations as $relation) { - $catid = $this->array_searchi($relation['category'], $categories); - OC_Log::write('core', __METHOD__ . 'catid, ' . $relation['category'] . ' ' . $catid, OC_Log::DEBUG); - if($catid) { - try { - OCP\DB::insertIfNotExist(self::RELATION_TABLE, - array( - 'objid' => $relation['objid'], - 'categoryid' => $catid, - 'type' => $this->type, - )); - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - OCP\Util::ERROR); - } - } - } - self::$relations = array(); // reset - } else { - OC_Log::write('core', __METHOD__.', $this->categories is not an array! ' - . print_r($this->categories, true), OC_Log::ERROR); - } - } - - /** - * @brief Delete categories and category/object relations for a user. - * For hooking up on post_deleteUser - * @param string $uid The user id for which entries should be purged. - */ - public static function post_deleteUser($arguments) { - // Find all objectid/categoryid pairs. - $result = null; - try { - $stmt = OCP\DB::prepare('SELECT `id` FROM `' . self::CATEGORY_TABLE . '` ' - . 'WHERE `uid` = ?'); - $result = $stmt->execute(array($arguments['uid'])); - if (OC_DB::isError($result)) { - OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::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::CATEGORY_TABLE . '` ' - . 'WHERE `uid` = ?'); - $result = $stmt->execute(array($arguments['uid'])); - if (OC_DB::isError($result)) { - OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR); - } - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__ . ', exception: ' - . $e->getMessage(), OCP\Util::ERROR); - } - } - - /** - * @brief Delete category/object relations from the db - * @param array $ids The ids of the objects - * @param string $type The type of object (event/contact/task/journal). - * Defaults to the type set in the instance - * @returns boolean Returns false on error. - */ - public function purgeObjects(array $ids, $type = null) { - $type = is_null($type) ? $this->type : $type; - 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[] = $type; - $stmt = OCP\DB::prepare($query); - $result = $stmt->execute($updates); - if (OC_DB::isError($result)) { - OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR); - return false; - } - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__.', exception: ' . $e->getMessage(), - OCP\Util::ERROR); - return false; - } - return true; - } - - /** - * Get favorites for an object type - * - * @param string $type The type of object (event/contact/task/journal). - * Defaults to the type set in the instance - * @returns array An array of object ids. - */ - public function getFavorites($type = null) { - $type = is_null($type) ? $this->type : $type; - - try { - return $this->idsForCategory(self::CATEGORY_FAVORITE); - } catch(Exception $e) { - // No favorites - return array(); - } - } - - /** - * Add an object to favorites - * - * @param int $objid The id of the object - * @param string $type The type of object (event/contact/task/journal). - * Defaults to the type set in the instance - * @returns boolean - */ - public function addToFavorites($objid, $type = null) { - $type = is_null($type) ? $this->type : $type; - if(!$this->hasCategory(self::CATEGORY_FAVORITE)) { - $this->add(self::CATEGORY_FAVORITE, true); - } - return $this->addToCategory($objid, self::CATEGORY_FAVORITE, $type); - } - - /** - * Remove an object from favorites - * - * @param int $objid The id of the object - * @param string $type The type of object (event/contact/task/journal). - * Defaults to the type set in the instance - * @returns boolean - */ - public function removeFromFavorites($objid, $type = null) { - $type = is_null($type) ? $this->type : $type; - return $this->removeFromCategory($objid, self::CATEGORY_FAVORITE, $type); - } - - /** - * @brief Creates a category/object relation. - * @param int $objid The id of the object - * @param int|string $category The id or name of the category - * @param string $type The type of object (event/contact/task/journal). - * Defaults to the type set in the instance - * @returns boolean Returns false on database error. - */ - public function addToCategory($objid, $category, $type = null) { - $type = is_null($type) ? $this->type : $type; - if(is_string($category) && !is_numeric($category)) { - $category = trim($category); - if(!$this->hasCategory($category)) { - $this->add($category, true); - } - $categoryid = $this->array_searchi($category, $this->categories); - } else { - $categoryid = $category; - } - try { - OCP\DB::insertIfNotExist(self::RELATION_TABLE, - array( - 'objid' => $objid, - 'categoryid' => $categoryid, - 'type' => $type, - )); - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - OCP\Util::ERROR); - return false; - } - return true; - } - - /** - * @brief Delete single category/object relation from the db - * @param int $objid The id of the object - * @param int|string $category The id or name of the category - * @param string $type The type of object (event/contact/task/journal). - * Defaults to the type set in the instance - * @returns boolean - */ - public function removeFromCategory($objid, $category, $type = null) { - $type = is_null($type) ? $this->type : $type; - if(is_string($category) && !is_numeric($category)) { - $category = trim($category); - $categoryid = $this->array_searchi($category, $this->categories); - } else { - $categoryid = $category; - } - - try { - $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` ' - . 'WHERE `objid` = ? AND `categoryid` = ? AND `type` = ?'; - OCP\Util::writeLog('core', __METHOD__.', sql: ' . $objid . ' ' . $categoryid . ' ' . $type, - OCP\Util::DEBUG); - $stmt = OCP\DB::prepare($sql); - $stmt->execute(array($objid, $categoryid, $type)); - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - OCP\Util::ERROR); - return false; - } - return true; - } - - /** - * @brief Delete categories from the db and from all the vobject supplied - * @param $names An array of categories to delete - * @param $objects An array of arrays with [id,vobject] (as text) pairs suitable for updating the apps object table. - */ - public function delete($names, array &$objects=null) { - if(!is_array($names)) { - $names = array($names); - } - - $names = array_map('trim', $names); - - OC_Log::write('core', __METHOD__ . ', before: ' - . print_r($this->categories, true), OC_Log::DEBUG); - foreach($names as $name) { - $id = null; - OC_Log::write('core', __METHOD__.', '.$name, OC_Log::DEBUG); - if($this->hasCategory($name)) { - $id = $this->array_searchi($name, $this->categories); - unset($this->categories[$id]); - } - try { - $stmt = OCP\DB::prepare('DELETE FROM `' . self::CATEGORY_TABLE . '` WHERE ' - . '`uid` = ? AND `type` = ? AND `category` = ?'); - $result = $stmt->execute(array($this->user, $this->type, $name)); - if (OC_DB::isError($result)) { - OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR); - } - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__ . ', exception: ' - . $e->getMessage(), OCP\Util::ERROR); - } - 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 (OC_DB::isError($result)) { - OC_Log::write('core', - __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), - OC_Log::ERROR); - } - } catch(Exception $e) { - OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(), - OCP\Util::ERROR); - return false; - } - } - } - OC_Log::write('core', __METHOD__.', after: ' - . print_r($this->categories, true), OC_Log::DEBUG); - if(!is_null($objects)) { - foreach($objects as $key=>&$value) { - $vobject = OC_VObject::parse($value[1]); - if(!is_null($vobject)) { - $object = null; - $componentname = ''; - if (isset($vobject->VEVENT)) { - $object = $vobject->VEVENT; - $componentname = 'VEVENT'; - } else - if (isset($vobject->VTODO)) { - $object = $vobject->VTODO; - $componentname = 'VTODO'; - } else - if (isset($vobject->VJOURNAL)) { - $object = $vobject->VJOURNAL; - $componentname = 'VJOURNAL'; - } else { - $object = $vobject; - } - $categories = $object->getAsArray('CATEGORIES'); - foreach($names as $name) { - $idx = $this->array_searchi($name, $categories); - if($idx !== false) { - OC_Log::write('core', __METHOD__ - .', unsetting: ' - . $categories[$this->array_searchi($name, $categories)], - OC_Log::DEBUG); - unset($categories[$this->array_searchi($name, $categories)]); - } - } - - $object->setString('CATEGORIES', implode(',', $categories)); - if($vobject !== $object) { - $vobject[$componentname] = $object; - } - $value[1] = $vobject->serialize(); - $objects[$key] = $value; - } else { - OC_Log::write('core', __METHOD__ - .', unable to parse. ID: ' . $value[0] . ', ' - . substr($value[1], 0, 50) . '(...)', OC_Log::DEBUG); - } - } - } - } - - // case-insensitive in_array - private function in_arrayi($needle, $haystack) { - if(!is_array($haystack)) { - return false; - } - return in_array(strtolower($needle), array_map('strtolower', $haystack)); - } - - // case-insensitive array_search - private function array_searchi($needle, $haystack) { - if(!is_array($haystack)) { - return false; - } - return array_search(strtolower($needle), array_map('strtolower', $haystack)); - } -} diff --git a/tests/lib/tags.php b/tests/lib/tags.php new file mode 100644 index 0000000000..06baebc0af --- /dev/null +++ b/tests/lib/tags.php @@ -0,0 +1,133 @@ +. +* +*/ + +class Test_Tags extends PHPUnit_Framework_TestCase { + + protected $objectType; + protected $user; + protected $backupGlobals = FALSE; + + public function setUp() { + + OC_User::clearBackends(); + OC_User::useBackend('dummy'); + $this->user = uniqid('user_'); + $this->objectType = uniqid('type_'); + OC_User::createUser($this->user, 'pass'); + OC_User::setUserId($this->user); + + } + + public function tearDown() { + //$query = OC_DB::prepare('DELETE FROM `*PREFIX*vcategories` WHERE `item_type` = ?'); + //$query->execute(array('test')); + } + + public function testInstantiateWithDefaults() { + $defaultTags = array('Friends', 'Family', 'Work', 'Other'); + + $tagMgr = new OC\Tags($this->user); + $tagMgr->loadTagsFor($this->objectType, $defaultTags); + + $this->assertEquals(4, count($tagMgr->tags())); + } + + public function testAddTags() { + $tags = array('Friends', 'Family', 'Work', 'Other'); + + $tagMgr = new OC\Tags($this->user); + $tagMgr->loadTagsFor($this->objectType); + + foreach($tags as $tag) { + $result = $tagMgr->add($tag); + $this->assertTrue((bool)$result); + } + + $this->assertFalse($tagMgr->add('Family')); + $this->assertFalse($tagMgr->add('fAMILY')); + + $this->assertEquals(4, count($tagMgr->tags())); + } + + public function testdeleteTags() { + $defaultTags = array('Friends', 'Family', 'Work', 'Other'); + $tagMgr = new OC\Tags($this->user); + $tagMgr->loadTagsFor($this->objectType, $defaultTags); + + $this->assertEquals(4, count($tagMgr->tags())); + + $tagMgr->delete('family'); + $this->assertEquals(3, count($tagMgr->tags())); + + $tagMgr->delete(array('Friends', 'Work', 'Other')); + $this->assertEquals(0, count($tagMgr->tags())); + + } + + public function testRenameTag() { + $defaultTags = array('Friends', 'Family', 'Wrok', 'Other'); + $tagMgr = new OC\Tags($this->user); + $tagMgr->loadTagsFor($this->objectType, $defaultTags); + + $this->assertTrue($tagMgr->rename('Wrok', 'Work')); + $this->assertTrue($tagMgr->hasTag('Work')); + $this->assertFalse($tagMgr->hastag('Wrok')); + $this->assertFalse($tagMgr->rename('Wrok', 'Work')); + + } + + public function testTagAs() { + $objids = array(1, 2, 3, 4, 5, 6, 7, 8, 9); + + $tagMgr = new OC\Tags($this->user); + $tagMgr->loadTagsFor($this->objectType); + + foreach($objids as $id) { + $tagMgr->tagAs($id, 'Family'); + } + + $this->assertEquals(1, count($tagMgr->tags())); + $this->assertEquals(9, count($tagMgr->idsForTag('Family'))); + } + + /** + * @depends testTagAs + */ + public function testUnTag() { + $objIds = array(1, 2, 3, 4, 5, 6, 7, 8, 9); + + // Is this "legal"? + $this->testTagAs(); + $tagMgr = new OC\Tags($this->user); + $tagMgr->loadTagsFor($this->objectType); + + foreach($objIds as $id) { + $this->assertTrue(in_array($id, $tagMgr->idsForTag('Family'))); + $tagMgr->unTag($id, 'Family'); + $this->assertFalse(in_array($id, $tagMgr->idsForTag('Family'))); + } + + $this->assertEquals(1, count($tagMgr->tags())); + $this->assertEquals(0, count($tagMgr->idsForTag('Family'))); + } + +} diff --git a/tests/lib/vcategories.php b/tests/lib/vcategories.php deleted file mode 100644 index df5f600f20..0000000000 --- a/tests/lib/vcategories.php +++ /dev/null @@ -1,128 +0,0 @@ -. -* -*/ - -//require_once("../lib/template.php"); - -class Test_VCategories extends PHPUnit_Framework_TestCase { - - protected $objectType; - protected $user; - protected $backupGlobals = FALSE; - - public function setUp() { - - OC_User::clearBackends(); - OC_User::useBackend('dummy'); - $this->user = uniqid('user_'); - $this->objectType = uniqid('type_'); - OC_User::createUser($this->user, 'pass'); - OC_User::setUserId($this->user); - - } - - public function tearDown() { - //$query = OC_DB::prepare('DELETE FROM `*PREFIX*vcategories` WHERE `item_type` = ?'); - //$query->execute(array('test')); - } - - public function testInstantiateWithDefaults() { - $defcategories = array('Friends', 'Family', 'Work', 'Other'); - - $catmgr = new OC_VCategories($this->objectType, $this->user, $defcategories); - - $this->assertEquals(4, count($catmgr->categories())); - } - - public function testAddCategories() { - $categories = array('Friends', 'Family', 'Work', 'Other'); - - $catmgr = new OC_VCategories($this->objectType, $this->user); - - foreach($categories as $category) { - $result = $catmgr->add($category); - $this->assertTrue((bool)$result); - } - - $this->assertFalse($catmgr->add('Family')); - $this->assertFalse($catmgr->add('fAMILY')); - - $this->assertEquals(4, count($catmgr->categories())); - } - - public function testdeleteCategories() { - $defcategories = array('Friends', 'Family', 'Work', 'Other'); - $catmgr = new OC_VCategories($this->objectType, $this->user, $defcategories); - $this->assertEquals(4, count($catmgr->categories())); - - $catmgr->delete('family'); - $this->assertEquals(3, count($catmgr->categories())); - - $catmgr->delete(array('Friends', 'Work', 'Other')); - $this->assertEquals(0, count($catmgr->categories())); - - } - - public function testrenameCategory() { - $defcategories = array('Friends', 'Family', 'Wrok', 'Other'); - $catmgr = new OC_VCategories($this->objectType, $this->user, $defcategories); - - $this->assertTrue($catmgr->rename('Wrok', 'Work')); - $this->assertTrue($catmgr->hasCategory('Work')); - $this->assertFalse($catmgr->hasCategory('Wrok')); - $this->assertFalse($catmgr->rename('Wrok', 'Work')); - - } - - public function testAddToCategory() { - $objids = array(1, 2, 3, 4, 5, 6, 7, 8, 9); - - $catmgr = new OC_VCategories($this->objectType, $this->user); - - foreach($objids as $id) { - $catmgr->addToCategory($id, 'Family'); - } - - $this->assertEquals(1, count($catmgr->categories())); - $this->assertEquals(9, count($catmgr->idsForCategory('Family'))); - } - - /** - * @depends testAddToCategory - */ - public function testRemoveFromCategory() { - $objids = array(1, 2, 3, 4, 5, 6, 7, 8, 9); - - // Is this "legal"? - $this->testAddToCategory(); - $catmgr = new OC_VCategories($this->objectType, $this->user); - - foreach($objids as $id) { - $this->assertTrue(in_array($id, $catmgr->idsForCategory('Family'))); - $catmgr->removeFromCategory($id, 'Family'); - $this->assertFalse(in_array($id, $catmgr->idsForCategory('Family'))); - } - - $this->assertEquals(1, count($catmgr->categories())); - $this->assertEquals(0, count($catmgr->idsForCategory('Family'))); - } - -}