vcategories.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Thomas Tanghus
  6. * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
  7. * @copyright 2012 Bart Visscher bartv@thisnet.nl
  8. *
  9. * This library is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  11. * License as published by the Free Software Foundation; either
  12. * version 3 of the License, or any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public
  20. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. */
  23. OC_Hook::connect('OC_User', 'post_deleteUser', 'OC_VCategories', 'post_deleteUser');
  24. /**
  25. * Class for easy access to categories in VCARD, VEVENT, VTODO and VJOURNAL.
  26. * A Category can be e.g. 'Family', 'Work', 'Chore', 'Special Occation' or
  27. * anything else that is either parsed from a vobject or that the user chooses
  28. * to add.
  29. * Category names are not case-sensitive, but will be saved with the case they
  30. * are entered in. If a user already has a category 'family' for a type, and
  31. * tries to add a category named 'Family' it will be silently ignored.
  32. */
  33. class OC_VCategories {
  34. /**
  35. * Categories
  36. */
  37. private $categories = array();
  38. /**
  39. * Used for storing objectid/categoryname pairs while rescanning.
  40. */
  41. private static $relations = array();
  42. private $type = null;
  43. private $user = null;
  44. const CATEGORY_TABLE = '*PREFIX*vcategory';
  45. const RELATION_TABLE = '*PREFIX*vcategory_to_object';
  46. const CATEGORY_FAVORITE = '_$!<Favorite>!$_';
  47. const FORMAT_LIST = 0;
  48. const FORMAT_MAP = 1;
  49. /**
  50. * @brief Constructor.
  51. * @param $type The type identifier e.g. 'contact' or 'event'.
  52. * @param $user The user whos data the object will operate on. This
  53. * parameter should normally be omitted but to make an app able to
  54. * update categories for all users it is made possible to provide it.
  55. * @param $defcategories An array of default categories to be used if none is stored.
  56. */
  57. public function __construct($type, $user=null, $defcategories=array()) {
  58. $this->type = $type;
  59. $this->user = is_null($user) ? OC_User::getUser() : $user;
  60. $this->loadCategories();
  61. OCP\Util::writeLog('core', __METHOD__ . ', categories: '
  62. . print_r($this->categories, true),
  63. OCP\Util::DEBUG
  64. );
  65. if($defcategories && count($this->categories) === 0) {
  66. $this->addMulti($defcategories, true);
  67. }
  68. }
  69. /**
  70. * @brief Load categories from db.
  71. */
  72. private function loadCategories() {
  73. $this->categories = array();
  74. $result = null;
  75. $sql = 'SELECT `id`, `category` FROM `' . self::CATEGORY_TABLE . '` '
  76. . 'WHERE `uid` = ? AND `type` = ? ORDER BY `category`';
  77. try {
  78. $stmt = OCP\DB::prepare($sql);
  79. $result = $stmt->execute(array($this->user, $this->type));
  80. if (OC_DB::isError($result)) {
  81. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  82. }
  83. } catch(Exception $e) {
  84. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  85. OCP\Util::ERROR);
  86. }
  87. if(!is_null($result)) {
  88. while( $row = $result->fetchRow()) {
  89. // The keys are prefixed because array_search wouldn't work otherwise :-/
  90. $this->categories[$row['id']] = $row['category'];
  91. }
  92. }
  93. OCP\Util::writeLog('core', __METHOD__.', categories: ' . print_r($this->categories, true),
  94. OCP\Util::DEBUG);
  95. }
  96. /**
  97. * @brief Check if any categories are saved for this type and user.
  98. * @returns boolean.
  99. * @param $type The type identifier e.g. 'contact' or 'event'.
  100. * @param $user The user whos categories will be checked. If not set current user will be used.
  101. */
  102. public static function isEmpty($type, $user = null) {
  103. $user = is_null($user) ? OC_User::getUser() : $user;
  104. $sql = 'SELECT COUNT(*) FROM `' . self::CATEGORY_TABLE . '` '
  105. . 'WHERE `uid` = ? AND `type` = ?';
  106. try {
  107. $stmt = OCP\DB::prepare($sql);
  108. $result = $stmt->execute(array($user, $type));
  109. if (OC_DB::isError($result)) {
  110. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  111. return false;
  112. }
  113. return ($result->numRows() == 0);
  114. } catch(Exception $e) {
  115. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  116. OCP\Util::ERROR);
  117. return false;
  118. }
  119. }
  120. /**
  121. * @brief Get the categories for a specific user.
  122. * @param
  123. * @returns array containing the categories as strings.
  124. */
  125. public function categories($format = null) {
  126. if(!$this->categories) {
  127. return array();
  128. }
  129. $categories = array_values($this->categories);
  130. uasort($categories, 'strnatcasecmp');
  131. if($format == self::FORMAT_MAP) {
  132. $catmap = array();
  133. foreach($categories as $category) {
  134. if($category !== self::CATEGORY_FAVORITE) {
  135. $catmap[] = array(
  136. 'id' => $this->array_searchi($category, $this->categories),
  137. 'name' => $category
  138. );
  139. }
  140. }
  141. return $catmap;
  142. }
  143. // Don't add favorites to normal categories.
  144. $favpos = array_search(self::CATEGORY_FAVORITE, $categories);
  145. if($favpos !== false) {
  146. return array_splice($categories, $favpos);
  147. } else {
  148. return $categories;
  149. }
  150. }
  151. /**
  152. * Get the a list if items belonging to $category.
  153. *
  154. * Throws an exception if the category could not be found.
  155. *
  156. * @param string|integer $category Category id or name.
  157. * @returns array An array of object ids or false on error.
  158. */
  159. public function idsForCategory($category) {
  160. $result = null;
  161. if(is_numeric($category)) {
  162. $catid = $category;
  163. } elseif(is_string($category)) {
  164. $catid = $this->array_searchi($category, $this->categories);
  165. }
  166. OCP\Util::writeLog('core', __METHOD__.', category: '.$catid.' '.$category, OCP\Util::DEBUG);
  167. if($catid === false) {
  168. $l10n = OC_L10N::get('core');
  169. throw new Exception(
  170. $l10n->t('Could not find category "%s"', $category)
  171. );
  172. }
  173. $ids = array();
  174. $sql = 'SELECT `objid` FROM `' . self::RELATION_TABLE
  175. . '` WHERE `categoryid` = ?';
  176. try {
  177. $stmt = OCP\DB::prepare($sql);
  178. $result = $stmt->execute(array($catid));
  179. if (OC_DB::isError($result)) {
  180. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  181. return false;
  182. }
  183. } catch(Exception $e) {
  184. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  185. OCP\Util::ERROR);
  186. return false;
  187. }
  188. if(!is_null($result)) {
  189. while( $row = $result->fetchRow()) {
  190. $ids[] = (int)$row['objid'];
  191. }
  192. }
  193. return $ids;
  194. }
  195. /**
  196. * Get the a list if items belonging to $category.
  197. *
  198. * Throws an exception if the category could not be found.
  199. *
  200. * @param string|integer $category Category id or name.
  201. * @param array $tableinfo Array in the form {'tablename' => table, 'fields' => ['field1', 'field2']}
  202. * @param int $limit
  203. * @param int $offset
  204. *
  205. * This generic method queries a table assuming that the id
  206. * field is called 'id' and the table name provided is in
  207. * the form '*PREFIX*table_name'.
  208. *
  209. * If the category name cannot be resolved an exception is thrown.
  210. *
  211. * TODO: Maybe add the getting permissions for objects?
  212. *
  213. * @returns array containing the resulting items or false on error.
  214. */
  215. public function itemsForCategory($category, $tableinfo, $limit = null, $offset = null) {
  216. $result = null;
  217. if(is_numeric($category)) {
  218. $catid = $category;
  219. } elseif(is_string($category)) {
  220. $catid = $this->array_searchi($category, $this->categories);
  221. }
  222. OCP\Util::writeLog('core', __METHOD__.', category: '.$catid.' '.$category, OCP\Util::DEBUG);
  223. if($catid === false) {
  224. $l10n = OC_L10N::get('core');
  225. throw new Exception(
  226. $l10n->t('Could not find category "%s"', $category)
  227. );
  228. }
  229. $fields = '';
  230. foreach($tableinfo['fields'] as $field) {
  231. $fields .= '`' . $tableinfo['tablename'] . '`.`' . $field . '`,';
  232. }
  233. $fields = substr($fields, 0, -1);
  234. $items = array();
  235. $sql = 'SELECT `' . self::RELATION_TABLE . '`.`categoryid`, ' . $fields
  236. . ' FROM `' . $tableinfo['tablename'] . '` JOIN `'
  237. . self::RELATION_TABLE . '` ON `' . $tableinfo['tablename']
  238. . '`.`id` = `' . self::RELATION_TABLE . '`.`objid` WHERE `'
  239. . self::RELATION_TABLE . '`.`categoryid` = ?';
  240. try {
  241. $stmt = OCP\DB::prepare($sql, $limit, $offset);
  242. $result = $stmt->execute(array($catid));
  243. if (OC_DB::isError($result)) {
  244. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  245. return false;
  246. }
  247. } catch(Exception $e) {
  248. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  249. OCP\Util::ERROR);
  250. return false;
  251. }
  252. if(!is_null($result)) {
  253. while( $row = $result->fetchRow()) {
  254. $items[] = $row;
  255. }
  256. }
  257. //OCP\Util::writeLog('core', __METHOD__.', count: ' . count($items), OCP\Util::DEBUG);
  258. //OCP\Util::writeLog('core', __METHOD__.', sql: ' . $sql, OCP\Util::DEBUG);
  259. return $items;
  260. }
  261. /**
  262. * @brief Checks whether a category is already saved.
  263. * @param $name The name to check for.
  264. * @returns bool
  265. */
  266. public function hasCategory($name) {
  267. return $this->in_arrayi($name, $this->categories);
  268. }
  269. /**
  270. * @brief Add a new category.
  271. * @param $name A string with a name of the category
  272. * @returns int the id of the added category or false if it already exists.
  273. */
  274. public function add($name) {
  275. OCP\Util::writeLog('core', __METHOD__.', name: ' . $name, OCP\Util::DEBUG);
  276. if($this->hasCategory($name)) {
  277. OCP\Util::writeLog('core', __METHOD__.', name: ' . $name. ' exists already', OCP\Util::DEBUG);
  278. return false;
  279. }
  280. OCP\DB::insertIfNotExist(self::CATEGORY_TABLE,
  281. array(
  282. 'uid' => $this->user,
  283. 'type' => $this->type,
  284. 'category' => $name,
  285. ));
  286. $id = OCP\DB::insertid(self::CATEGORY_TABLE);
  287. OCP\Util::writeLog('core', __METHOD__.', id: ' . $id, OCP\Util::DEBUG);
  288. $this->categories[$id] = $name;
  289. return $id;
  290. }
  291. /**
  292. * @brief Add a new category.
  293. * @param $names A string with a name or an array of strings containing
  294. * the name(s) of the categor(y|ies) to add.
  295. * @param $sync bool When true, save the categories
  296. * @param $id int Optional object id to add to this|these categor(y|ies)
  297. * @returns bool Returns false on error.
  298. */
  299. public function addMulti($names, $sync=false, $id = null) {
  300. if(!is_array($names)) {
  301. $names = array($names);
  302. }
  303. $names = array_map('trim', $names);
  304. $newones = array();
  305. foreach($names as $name) {
  306. if(($this->in_arrayi(
  307. $name, $this->categories) == false) && $name != '') {
  308. $newones[] = $name;
  309. }
  310. if(!is_null($id) ) {
  311. // Insert $objectid, $categoryid pairs if not exist.
  312. self::$relations[] = array('objid' => $id, 'category' => $name);
  313. }
  314. }
  315. if(count($newones) > 0) {
  316. $this->categories = array_merge($this->categories, $newones);
  317. if($sync === true) {
  318. $this->save();
  319. }
  320. }
  321. return true;
  322. }
  323. /**
  324. * @brief Extracts categories from a vobject and add the ones not already present.
  325. * @param $vobject The instance of OC_VObject to load the categories from.
  326. */
  327. public function loadFromVObject($id, $vobject, $sync=false) {
  328. $this->addMulti($vobject->getAsArray('CATEGORIES'), $sync, $id);
  329. }
  330. /**
  331. * @brief Reset saved categories and rescan supplied vobjects for categories.
  332. * @param $objects An array of vobjects (as text).
  333. * To get the object array, do something like:
  334. * // For Addressbook:
  335. * $categories = new OC_VCategories('contacts');
  336. * $stmt = OC_DB::prepare( 'SELECT `carddata` FROM `*PREFIX*contacts_cards`' );
  337. * $result = $stmt->execute();
  338. * $objects = array();
  339. * if(!is_null($result)) {
  340. * while( $row = $result->fetchRow()){
  341. * $objects[] = array($row['id'], $row['carddata']);
  342. * }
  343. * }
  344. * $categories->rescan($objects);
  345. */
  346. public function rescan($objects, $sync=true, $reset=true) {
  347. if($reset === true) {
  348. $result = null;
  349. // Find all objectid/categoryid pairs.
  350. try {
  351. $stmt = OCP\DB::prepare('SELECT `id` FROM `' . self::CATEGORY_TABLE . '` '
  352. . 'WHERE `uid` = ? AND `type` = ?');
  353. $result = $stmt->execute(array($this->user, $this->type));
  354. if (OC_DB::isError($result)) {
  355. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  356. return false;
  357. }
  358. } catch(Exception $e) {
  359. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  360. OCP\Util::ERROR);
  361. }
  362. // And delete them.
  363. if(!is_null($result)) {
  364. $stmt = OCP\DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` '
  365. . 'WHERE `categoryid` = ? AND `type`= ?');
  366. while( $row = $result->fetchRow()) {
  367. $stmt->execute(array($row['id'], $this->type));
  368. }
  369. }
  370. try {
  371. $stmt = OCP\DB::prepare('DELETE FROM `' . self::CATEGORY_TABLE . '` '
  372. . 'WHERE `uid` = ? AND `type` = ?');
  373. $result = $stmt->execute(array($this->user, $this->type));
  374. if (OC_DB::isError($result)) {
  375. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  376. return;
  377. }
  378. } catch(Exception $e) {
  379. OCP\Util::writeLog('core', __METHOD__ . ', exception: '
  380. . $e->getMessage(), OCP\Util::ERROR);
  381. return;
  382. }
  383. $this->categories = array();
  384. }
  385. // Parse all the VObjects
  386. foreach($objects as $object) {
  387. $vobject = OC_VObject::parse($object[1]);
  388. if(!is_null($vobject)) {
  389. // Load the categories
  390. $this->loadFromVObject($object[0], $vobject, $sync);
  391. } else {
  392. OC_Log::write('core', __METHOD__ . ', unable to parse. ID: ' . ', '
  393. . substr($object, 0, 100) . '(...)', OC_Log::DEBUG);
  394. }
  395. }
  396. $this->save();
  397. }
  398. /**
  399. * @brief Save the list with categories
  400. */
  401. private function save() {
  402. if(is_array($this->categories)) {
  403. foreach($this->categories as $category) {
  404. OCP\DB::insertIfNotExist(self::CATEGORY_TABLE,
  405. array(
  406. 'uid' => $this->user,
  407. 'type' => $this->type,
  408. 'category' => $category,
  409. ));
  410. }
  411. // reload categories to get the proper ids.
  412. $this->loadCategories();
  413. // Loop through temporarily cached objectid/categoryname pairs
  414. // and save relations.
  415. $categories = $this->categories;
  416. // For some reason this is needed or array_search(i) will return 0..?
  417. ksort($categories);
  418. foreach(self::$relations as $relation) {
  419. $catid = $this->array_searchi($relation['category'], $categories);
  420. OC_Log::write('core', __METHOD__ . 'catid, ' . $relation['category'] . ' ' . $catid, OC_Log::DEBUG);
  421. if($catid) {
  422. OCP\DB::insertIfNotExist(self::RELATION_TABLE,
  423. array(
  424. 'objid' => $relation['objid'],
  425. 'categoryid' => $catid,
  426. 'type' => $this->type,
  427. ));
  428. }
  429. }
  430. self::$relations = array(); // reset
  431. } else {
  432. OC_Log::write('core', __METHOD__.', $this->categories is not an array! '
  433. . print_r($this->categories, true), OC_Log::ERROR);
  434. }
  435. }
  436. /**
  437. * @brief Delete categories and category/object relations for a user.
  438. * For hooking up on post_deleteUser
  439. * @param string $uid The user id for which entries should be purged.
  440. */
  441. public static function post_deleteUser($arguments) {
  442. // Find all objectid/categoryid pairs.
  443. $result = null;
  444. try {
  445. $stmt = OCP\DB::prepare('SELECT `id` FROM `' . self::CATEGORY_TABLE . '` '
  446. . 'WHERE `uid` = ?');
  447. $result = $stmt->execute(array($arguments['uid']));
  448. if (OC_DB::isError($result)) {
  449. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  450. }
  451. } catch(Exception $e) {
  452. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  453. OCP\Util::ERROR);
  454. }
  455. if(!is_null($result)) {
  456. try {
  457. $stmt = OCP\DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` '
  458. . 'WHERE `categoryid` = ?');
  459. while( $row = $result->fetchRow()) {
  460. try {
  461. $stmt->execute(array($row['id']));
  462. } catch(Exception $e) {
  463. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  464. OCP\Util::ERROR);
  465. }
  466. }
  467. } catch(Exception $e) {
  468. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  469. OCP\Util::ERROR);
  470. }
  471. }
  472. try {
  473. $stmt = OCP\DB::prepare('DELETE FROM `' . self::CATEGORY_TABLE . '` '
  474. . 'WHERE `uid` = ? AND');
  475. $result = $stmt->execute(array($arguments['uid']));
  476. if (OC_DB::isError($result)) {
  477. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  478. }
  479. } catch(Exception $e) {
  480. OCP\Util::writeLog('core', __METHOD__ . ', exception: '
  481. . $e->getMessage(), OCP\Util::ERROR);
  482. }
  483. }
  484. /**
  485. * @brief Delete category/object relations from the db
  486. * @param int $id The id of the object
  487. * @param string $type The type of object (event/contact/task/journal).
  488. * Defaults to the type set in the instance
  489. * @returns boolean Returns false on error.
  490. */
  491. public function purgeObject($id, $type = null) {
  492. $type = is_null($type) ? $this->type : $type;
  493. try {
  494. $stmt = OCP\DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` '
  495. . 'WHERE `objid` = ? AND `type`= ?');
  496. $result = $stmt->execute(array($id, $type));
  497. if (OC_DB::isError($result)) {
  498. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  499. return false;
  500. }
  501. } catch(Exception $e) {
  502. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  503. OCP\Util::ERROR);
  504. return false;
  505. }
  506. return true;
  507. }
  508. /**
  509. * Get favorites for an object type
  510. *
  511. * @param string $type The type of object (event/contact/task/journal).
  512. * Defaults to the type set in the instance
  513. * @returns array An array of object ids.
  514. */
  515. public function getFavorites($type = null) {
  516. $type = is_null($type) ? $this->type : $type;
  517. try {
  518. return $this->idsForCategory(self::CATEGORY_FAVORITE);
  519. } catch(Exception $e) {
  520. // No favorites
  521. return array();
  522. }
  523. }
  524. /**
  525. * Add an object to favorites
  526. *
  527. * @param int $objid The id of the object
  528. * @param string $type The type of object (event/contact/task/journal).
  529. * Defaults to the type set in the instance
  530. * @returns boolean
  531. */
  532. public function addToFavorites($objid, $type = null) {
  533. $type = is_null($type) ? $this->type : $type;
  534. if(!$this->hasCategory(self::CATEGORY_FAVORITE)) {
  535. $this->add(self::CATEGORY_FAVORITE, true);
  536. }
  537. return $this->addToCategory($objid, self::CATEGORY_FAVORITE, $type);
  538. }
  539. /**
  540. * Remove an object from favorites
  541. *
  542. * @param int $objid The id of the object
  543. * @param string $type The type of object (event/contact/task/journal).
  544. * Defaults to the type set in the instance
  545. * @returns boolean
  546. */
  547. public function removeFromFavorites($objid, $type = null) {
  548. $type = is_null($type) ? $this->type : $type;
  549. return $this->removeFromCategory($objid, self::CATEGORY_FAVORITE, $type);
  550. }
  551. /**
  552. * @brief Creates a category/object relation.
  553. * @param int $objid The id of the object
  554. * @param int|string $category The id or name of the category
  555. * @param string $type The type of object (event/contact/task/journal).
  556. * Defaults to the type set in the instance
  557. * @returns boolean Returns false on database error.
  558. */
  559. public function addToCategory($objid, $category, $type = null) {
  560. $type = is_null($type) ? $this->type : $type;
  561. if(is_string($category) && !is_numeric($category)) {
  562. if(!$this->hasCategory($category)) {
  563. $this->add($category, true);
  564. }
  565. $categoryid = $this->array_searchi($category, $this->categories);
  566. } else {
  567. $categoryid = $category;
  568. }
  569. try {
  570. OCP\DB::insertIfNotExist(self::RELATION_TABLE,
  571. array(
  572. 'objid' => $objid,
  573. 'categoryid' => $categoryid,
  574. 'type' => $type,
  575. ));
  576. } catch(Exception $e) {
  577. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  578. OCP\Util::ERROR);
  579. return false;
  580. }
  581. return true;
  582. }
  583. /**
  584. * @brief Delete single category/object relation from the db
  585. * @param int $objid The id of the object
  586. * @param int|string $category The id or name of the category
  587. * @param string $type The type of object (event/contact/task/journal).
  588. * Defaults to the type set in the instance
  589. * @returns boolean
  590. */
  591. public function removeFromCategory($objid, $category, $type = null) {
  592. $type = is_null($type) ? $this->type : $type;
  593. $categoryid = (is_string($category) && !is_numeric($category))
  594. ? $this->array_searchi($category, $this->categories)
  595. : $category;
  596. try {
  597. $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
  598. . 'WHERE `objid` = ? AND `categoryid` = ? AND `type` = ?';
  599. OCP\Util::writeLog('core', __METHOD__.', sql: ' . $objid . ' ' . $categoryid . ' ' . $type,
  600. OCP\Util::DEBUG);
  601. $stmt = OCP\DB::prepare($sql);
  602. $stmt->execute(array($objid, $categoryid, $type));
  603. } catch(Exception $e) {
  604. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  605. OCP\Util::ERROR);
  606. return false;
  607. }
  608. return true;
  609. }
  610. /**
  611. * @brief Delete categories from the db and from all the vobject supplied
  612. * @param $names An array of categories to delete
  613. * @param $objects An array of arrays with [id,vobject] (as text) pairs suitable for updating the apps object table.
  614. */
  615. public function delete($names, array &$objects=null) {
  616. if(!is_array($names)) {
  617. $names = array($names);
  618. }
  619. OC_Log::write('core', __METHOD__ . ', before: '
  620. . print_r($this->categories, true), OC_Log::DEBUG);
  621. foreach($names as $name) {
  622. $id = null;
  623. OC_Log::write('core', __METHOD__.', '.$name, OC_Log::DEBUG);
  624. if($this->hasCategory($name)) {
  625. $id = $this->array_searchi($name, $this->categories);
  626. unset($this->categories[$id]);
  627. }
  628. try {
  629. $stmt = OCP\DB::prepare('DELETE FROM `' . self::CATEGORY_TABLE . '` WHERE '
  630. . '`uid` = ? AND `type` = ? AND `category` = ?');
  631. $result = $stmt->execute(array($this->user, $this->type, $name));
  632. if (OC_DB::isError($result)) {
  633. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  634. }
  635. } catch(Exception $e) {
  636. OCP\Util::writeLog('core', __METHOD__ . ', exception: '
  637. . $e->getMessage(), OCP\Util::ERROR);
  638. }
  639. if(!is_null($id) && $id !== false) {
  640. try {
  641. $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
  642. . 'WHERE `categoryid` = ?';
  643. $stmt = OCP\DB::prepare($sql);
  644. $result = $stmt->execute(array($id));
  645. if (OC_DB::isError($result)) {
  646. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  647. }
  648. } catch(Exception $e) {
  649. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  650. OCP\Util::ERROR);
  651. return false;
  652. }
  653. }
  654. }
  655. OC_Log::write('core', __METHOD__.', after: '
  656. . print_r($this->categories, true), OC_Log::DEBUG);
  657. if(!is_null($objects)) {
  658. foreach($objects as $key=>&$value) {
  659. $vobject = OC_VObject::parse($value[1]);
  660. if(!is_null($vobject)) {
  661. $object = null;
  662. $componentname = '';
  663. if (isset($vobject->VEVENT)) {
  664. $object = $vobject->VEVENT;
  665. $componentname = 'VEVENT';
  666. } else
  667. if (isset($vobject->VTODO)) {
  668. $object = $vobject->VTODO;
  669. $componentname = 'VTODO';
  670. } else
  671. if (isset($vobject->VJOURNAL)) {
  672. $object = $vobject->VJOURNAL;
  673. $componentname = 'VJOURNAL';
  674. } else {
  675. $object = $vobject;
  676. }
  677. $categories = $object->getAsArray('CATEGORIES');
  678. foreach($names as $name) {
  679. $idx = $this->array_searchi($name, $categories);
  680. if($idx !== false) {
  681. OC_Log::write('core', __METHOD__
  682. .', unsetting: '
  683. . $categories[$this->array_searchi($name, $categories)],
  684. OC_Log::DEBUG);
  685. unset($categories[$this->array_searchi($name, $categories)]);
  686. }
  687. }
  688. $object->setString('CATEGORIES', implode(',', $categories));
  689. if($vobject !== $object) {
  690. $vobject[$componentname] = $object;
  691. }
  692. $value[1] = $vobject->serialize();
  693. $objects[$key] = $value;
  694. } else {
  695. OC_Log::write('core', __METHOD__
  696. .', unable to parse. ID: ' . $value[0] . ', '
  697. . substr($value[1], 0, 50) . '(...)', OC_Log::DEBUG);
  698. }
  699. }
  700. }
  701. }
  702. // case-insensitive in_array
  703. private function in_arrayi($needle, $haystack) {
  704. if(!is_array($haystack)) {
  705. return false;
  706. }
  707. return in_array(strtolower($needle), array_map('strtolower', $haystack));
  708. }
  709. // case-insensitive array_search
  710. private function array_searchi($needle, $haystack) {
  711. if(!is_array($haystack)) {
  712. return false;
  713. }
  714. return array_search(strtolower($needle), array_map('strtolower', $haystack));
  715. }
  716. }