vcategories.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  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. try {
  281. OCP\DB::insertIfNotExist(self::CATEGORY_TABLE,
  282. array(
  283. 'uid' => $this->user,
  284. 'type' => $this->type,
  285. 'category' => $name,
  286. ));
  287. } catch(Exception $e) {
  288. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  289. OCP\Util::ERROR);
  290. return false;
  291. }
  292. $id = OCP\DB::insertid(self::CATEGORY_TABLE);
  293. OCP\Util::writeLog('core', __METHOD__.', id: ' . $id, OCP\Util::DEBUG);
  294. $this->categories[$id] = $name;
  295. return $id;
  296. }
  297. /**
  298. * @brief Rename category.
  299. * @param string $from The name of the existing category
  300. * @param string $to The new name of the category.
  301. * @returns bool
  302. */
  303. public function rename($from, $to) {
  304. $id = $this->array_searchi($from, $this->categories);
  305. if($id === false) {
  306. OCP\Util::writeLog('core', __METHOD__.', category: ' . $from. ' does not exist', OCP\Util::DEBUG);
  307. return false;
  308. }
  309. $sql = 'UPDATE `' . self::CATEGORY_TABLE . '` SET `category` = ? '
  310. . 'WHERE `uid` = ? AND `type` = ? AND `id` = ?';
  311. try {
  312. $stmt = OCP\DB::prepare($sql);
  313. $result = $stmt->execute(array($to, $this->user, $this->type, $id));
  314. if (OC_DB::isError($result)) {
  315. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  316. return false;
  317. }
  318. } catch(Exception $e) {
  319. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  320. OCP\Util::ERROR);
  321. return false;
  322. }
  323. $this->categories[$id] = $to;
  324. return true;
  325. }
  326. /**
  327. * @brief Add a new category.
  328. * @param $names A string with a name or an array of strings containing
  329. * the name(s) of the categor(y|ies) to add.
  330. * @param $sync bool When true, save the categories
  331. * @param $id int Optional object id to add to this|these categor(y|ies)
  332. * @returns bool Returns false on error.
  333. */
  334. public function addMulti($names, $sync=false, $id = null) {
  335. if(!is_array($names)) {
  336. $names = array($names);
  337. }
  338. $names = array_map('trim', $names);
  339. $newones = array();
  340. foreach($names as $name) {
  341. if(($this->in_arrayi(
  342. $name, $this->categories) == false) && $name != '') {
  343. $newones[] = $name;
  344. }
  345. if(!is_null($id) ) {
  346. // Insert $objectid, $categoryid pairs if not exist.
  347. self::$relations[] = array('objid' => $id, 'category' => $name);
  348. }
  349. }
  350. $this->categories = array_merge($this->categories, $newones);
  351. if($sync === true) {
  352. $this->save();
  353. }
  354. return true;
  355. }
  356. /**
  357. * @brief Extracts categories from a vobject and add the ones not already present.
  358. * @param $vobject The instance of OC_VObject to load the categories from.
  359. */
  360. public function loadFromVObject($id, $vobject, $sync=false) {
  361. $this->addMulti($vobject->getAsArray('CATEGORIES'), $sync, $id);
  362. }
  363. /**
  364. * @brief Reset saved categories and rescan supplied vobjects for categories.
  365. * @param $objects An array of vobjects (as text).
  366. * To get the object array, do something like:
  367. * // For Addressbook:
  368. * $categories = new OC_VCategories('contacts');
  369. * $stmt = OC_DB::prepare( 'SELECT `carddata` FROM `*PREFIX*contacts_cards`' );
  370. * $result = $stmt->execute();
  371. * $objects = array();
  372. * if(!is_null($result)) {
  373. * while( $row = $result->fetchRow()){
  374. * $objects[] = array($row['id'], $row['carddata']);
  375. * }
  376. * }
  377. * $categories->rescan($objects);
  378. */
  379. public function rescan($objects, $sync=true, $reset=true) {
  380. if($reset === true) {
  381. $result = null;
  382. // Find all objectid/categoryid pairs.
  383. try {
  384. $stmt = OCP\DB::prepare('SELECT `id` FROM `' . self::CATEGORY_TABLE . '` '
  385. . 'WHERE `uid` = ? AND `type` = ?');
  386. $result = $stmt->execute(array($this->user, $this->type));
  387. if (OC_DB::isError($result)) {
  388. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  389. return false;
  390. }
  391. } catch(Exception $e) {
  392. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  393. OCP\Util::ERROR);
  394. }
  395. // And delete them.
  396. if(!is_null($result)) {
  397. $stmt = OCP\DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` '
  398. . 'WHERE `categoryid` = ? AND `type`= ?');
  399. while( $row = $result->fetchRow()) {
  400. $stmt->execute(array($row['id'], $this->type));
  401. }
  402. }
  403. try {
  404. $stmt = OCP\DB::prepare('DELETE FROM `' . self::CATEGORY_TABLE . '` '
  405. . 'WHERE `uid` = ? AND `type` = ?');
  406. $result = $stmt->execute(array($this->user, $this->type));
  407. if (OC_DB::isError($result)) {
  408. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  409. return;
  410. }
  411. } catch(Exception $e) {
  412. OCP\Util::writeLog('core', __METHOD__ . ', exception: '
  413. . $e->getMessage(), OCP\Util::ERROR);
  414. return;
  415. }
  416. $this->categories = array();
  417. }
  418. // Parse all the VObjects
  419. foreach($objects as $object) {
  420. $vobject = OC_VObject::parse($object[1]);
  421. if(!is_null($vobject)) {
  422. // Load the categories
  423. $this->loadFromVObject($object[0], $vobject, $sync);
  424. } else {
  425. OC_Log::write('core', __METHOD__ . ', unable to parse. ID: ' . ', '
  426. . substr($object, 0, 100) . '(...)', OC_Log::DEBUG);
  427. }
  428. }
  429. $this->save();
  430. }
  431. /**
  432. * @brief Save the list with categories
  433. */
  434. private function save() {
  435. if(is_array($this->categories)) {
  436. foreach($this->categories as $category) {
  437. try {
  438. OCP\DB::insertIfNotExist(self::CATEGORY_TABLE,
  439. array(
  440. 'uid' => $this->user,
  441. 'type' => $this->type,
  442. 'category' => $category,
  443. ));
  444. } catch(Exception $e) {
  445. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  446. OCP\Util::ERROR);
  447. }
  448. }
  449. // reload categories to get the proper ids.
  450. $this->loadCategories();
  451. // Loop through temporarily cached objectid/categoryname pairs
  452. // and save relations.
  453. $categories = $this->categories;
  454. // For some reason this is needed or array_search(i) will return 0..?
  455. ksort($categories);
  456. foreach(self::$relations as $relation) {
  457. $catid = $this->array_searchi($relation['category'], $categories);
  458. OC_Log::write('core', __METHOD__ . 'catid, ' . $relation['category'] . ' ' . $catid, OC_Log::DEBUG);
  459. if($catid) {
  460. try {
  461. OCP\DB::insertIfNotExist(self::RELATION_TABLE,
  462. array(
  463. 'objid' => $relation['objid'],
  464. 'categoryid' => $catid,
  465. 'type' => $this->type,
  466. ));
  467. } catch(Exception $e) {
  468. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  469. OCP\Util::ERROR);
  470. }
  471. }
  472. }
  473. self::$relations = array(); // reset
  474. } else {
  475. OC_Log::write('core', __METHOD__.', $this->categories is not an array! '
  476. . print_r($this->categories, true), OC_Log::ERROR);
  477. }
  478. }
  479. /**
  480. * @brief Delete categories and category/object relations for a user.
  481. * For hooking up on post_deleteUser
  482. * @param string $uid The user id for which entries should be purged.
  483. */
  484. public static function post_deleteUser($arguments) {
  485. // Find all objectid/categoryid pairs.
  486. $result = null;
  487. try {
  488. $stmt = OCP\DB::prepare('SELECT `id` FROM `' . self::CATEGORY_TABLE . '` '
  489. . 'WHERE `uid` = ?');
  490. $result = $stmt->execute(array($arguments['uid']));
  491. if (OC_DB::isError($result)) {
  492. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  493. }
  494. } catch(Exception $e) {
  495. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  496. OCP\Util::ERROR);
  497. }
  498. if(!is_null($result)) {
  499. try {
  500. $stmt = OCP\DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` '
  501. . 'WHERE `categoryid` = ?');
  502. while( $row = $result->fetchRow()) {
  503. try {
  504. $stmt->execute(array($row['id']));
  505. } catch(Exception $e) {
  506. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  507. OCP\Util::ERROR);
  508. }
  509. }
  510. } catch(Exception $e) {
  511. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  512. OCP\Util::ERROR);
  513. }
  514. }
  515. try {
  516. $stmt = OCP\DB::prepare('DELETE FROM `' . self::CATEGORY_TABLE . '` '
  517. . 'WHERE `uid` = ?');
  518. $result = $stmt->execute(array($arguments['uid']));
  519. if (OC_DB::isError($result)) {
  520. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  521. }
  522. } catch(Exception $e) {
  523. OCP\Util::writeLog('core', __METHOD__ . ', exception: '
  524. . $e->getMessage(), OCP\Util::ERROR);
  525. }
  526. }
  527. /**
  528. * @brief Delete category/object relations from the db
  529. * @param array $ids The ids of the objects
  530. * @param string $type The type of object (event/contact/task/journal).
  531. * Defaults to the type set in the instance
  532. * @returns boolean Returns false on error.
  533. */
  534. public function purgeObjects(array $ids, $type = null) {
  535. $type = is_null($type) ? $this->type : $type;
  536. if(count($ids) === 0) {
  537. // job done ;)
  538. return true;
  539. }
  540. $updates = $ids;
  541. try {
  542. $query = 'DELETE FROM `' . self::RELATION_TABLE . '` ';
  543. $query .= 'WHERE `objid` IN (' . str_repeat('?,', count($ids)-1) . '?) ';
  544. $query .= 'AND `type`= ?';
  545. $updates[] = $type;
  546. $stmt = OCP\DB::prepare($query);
  547. $result = $stmt->execute($updates);
  548. if (OC_DB::isError($result)) {
  549. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  550. return false;
  551. }
  552. } catch(Exception $e) {
  553. OCP\Util::writeLog('core', __METHOD__.', exception: ' . $e->getMessage(),
  554. OCP\Util::ERROR);
  555. return false;
  556. }
  557. return true;
  558. }
  559. /**
  560. * Get favorites for an object type
  561. *
  562. * @param string $type The type of object (event/contact/task/journal).
  563. * Defaults to the type set in the instance
  564. * @returns array An array of object ids.
  565. */
  566. public function getFavorites($type = null) {
  567. $type = is_null($type) ? $this->type : $type;
  568. try {
  569. return $this->idsForCategory(self::CATEGORY_FAVORITE);
  570. } catch(Exception $e) {
  571. // No favorites
  572. return array();
  573. }
  574. }
  575. /**
  576. * Add an object to favorites
  577. *
  578. * @param int $objid The id of the object
  579. * @param string $type The type of object (event/contact/task/journal).
  580. * Defaults to the type set in the instance
  581. * @returns boolean
  582. */
  583. public function addToFavorites($objid, $type = null) {
  584. $type = is_null($type) ? $this->type : $type;
  585. if(!$this->hasCategory(self::CATEGORY_FAVORITE)) {
  586. $this->add(self::CATEGORY_FAVORITE, true);
  587. }
  588. return $this->addToCategory($objid, self::CATEGORY_FAVORITE, $type);
  589. }
  590. /**
  591. * Remove an object from favorites
  592. *
  593. * @param int $objid The id of the object
  594. * @param string $type The type of object (event/contact/task/journal).
  595. * Defaults to the type set in the instance
  596. * @returns boolean
  597. */
  598. public function removeFromFavorites($objid, $type = null) {
  599. $type = is_null($type) ? $this->type : $type;
  600. return $this->removeFromCategory($objid, self::CATEGORY_FAVORITE, $type);
  601. }
  602. /**
  603. * @brief Creates a category/object relation.
  604. * @param int $objid The id of the object
  605. * @param int|string $category The id or name of the category
  606. * @param string $type The type of object (event/contact/task/journal).
  607. * Defaults to the type set in the instance
  608. * @returns boolean Returns false on database error.
  609. */
  610. public function addToCategory($objid, $category, $type = null) {
  611. $type = is_null($type) ? $this->type : $type;
  612. if(is_string($category) && !is_numeric($category)) {
  613. if(!$this->hasCategory($category)) {
  614. $this->add($category, true);
  615. }
  616. $categoryid = $this->array_searchi($category, $this->categories);
  617. } else {
  618. $categoryid = $category;
  619. }
  620. try {
  621. OCP\DB::insertIfNotExist(self::RELATION_TABLE,
  622. array(
  623. 'objid' => $objid,
  624. 'categoryid' => $categoryid,
  625. 'type' => $type,
  626. ));
  627. } catch(Exception $e) {
  628. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  629. OCP\Util::ERROR);
  630. return false;
  631. }
  632. return true;
  633. }
  634. /**
  635. * @brief Delete single category/object relation from the db
  636. * @param int $objid The id of the object
  637. * @param int|string $category The id or name of the category
  638. * @param string $type The type of object (event/contact/task/journal).
  639. * Defaults to the type set in the instance
  640. * @returns boolean
  641. */
  642. public function removeFromCategory($objid, $category, $type = null) {
  643. $type = is_null($type) ? $this->type : $type;
  644. $categoryid = (is_string($category) && !is_numeric($category))
  645. ? $this->array_searchi($category, $this->categories)
  646. : $category;
  647. try {
  648. $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
  649. . 'WHERE `objid` = ? AND `categoryid` = ? AND `type` = ?';
  650. OCP\Util::writeLog('core', __METHOD__.', sql: ' . $objid . ' ' . $categoryid . ' ' . $type,
  651. OCP\Util::DEBUG);
  652. $stmt = OCP\DB::prepare($sql);
  653. $stmt->execute(array($objid, $categoryid, $type));
  654. } catch(Exception $e) {
  655. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  656. OCP\Util::ERROR);
  657. return false;
  658. }
  659. return true;
  660. }
  661. /**
  662. * @brief Delete categories from the db and from all the vobject supplied
  663. * @param $names An array of categories to delete
  664. * @param $objects An array of arrays with [id,vobject] (as text) pairs suitable for updating the apps object table.
  665. */
  666. public function delete($names, array &$objects=null) {
  667. if(!is_array($names)) {
  668. $names = array($names);
  669. }
  670. OC_Log::write('core', __METHOD__ . ', before: '
  671. . print_r($this->categories, true), OC_Log::DEBUG);
  672. foreach($names as $name) {
  673. $id = null;
  674. OC_Log::write('core', __METHOD__.', '.$name, OC_Log::DEBUG);
  675. if($this->hasCategory($name)) {
  676. $id = $this->array_searchi($name, $this->categories);
  677. unset($this->categories[$id]);
  678. }
  679. try {
  680. $stmt = OCP\DB::prepare('DELETE FROM `' . self::CATEGORY_TABLE . '` WHERE '
  681. . '`uid` = ? AND `type` = ? AND `category` = ?');
  682. $result = $stmt->execute(array($this->user, $this->type, $name));
  683. if (OC_DB::isError($result)) {
  684. OC_Log::write('core', __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result), OC_Log::ERROR);
  685. }
  686. } catch(Exception $e) {
  687. OCP\Util::writeLog('core', __METHOD__ . ', exception: '
  688. . $e->getMessage(), OCP\Util::ERROR);
  689. }
  690. if(!is_null($id) && $id !== false) {
  691. try {
  692. $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
  693. . 'WHERE `categoryid` = ?';
  694. $stmt = OCP\DB::prepare($sql);
  695. $result = $stmt->execute(array($id));
  696. if (OC_DB::isError($result)) {
  697. OC_Log::write('core',
  698. __METHOD__. 'DB error: ' . OC_DB::getErrorMessage($result),
  699. OC_Log::ERROR);
  700. }
  701. } catch(Exception $e) {
  702. OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
  703. OCP\Util::ERROR);
  704. return false;
  705. }
  706. }
  707. }
  708. OC_Log::write('core', __METHOD__.', after: '
  709. . print_r($this->categories, true), OC_Log::DEBUG);
  710. if(!is_null($objects)) {
  711. foreach($objects as $key=>&$value) {
  712. $vobject = OC_VObject::parse($value[1]);
  713. if(!is_null($vobject)) {
  714. $object = null;
  715. $componentname = '';
  716. if (isset($vobject->VEVENT)) {
  717. $object = $vobject->VEVENT;
  718. $componentname = 'VEVENT';
  719. } else
  720. if (isset($vobject->VTODO)) {
  721. $object = $vobject->VTODO;
  722. $componentname = 'VTODO';
  723. } else
  724. if (isset($vobject->VJOURNAL)) {
  725. $object = $vobject->VJOURNAL;
  726. $componentname = 'VJOURNAL';
  727. } else {
  728. $object = $vobject;
  729. }
  730. $categories = $object->getAsArray('CATEGORIES');
  731. foreach($names as $name) {
  732. $idx = $this->array_searchi($name, $categories);
  733. if($idx !== false) {
  734. OC_Log::write('core', __METHOD__
  735. .', unsetting: '
  736. . $categories[$this->array_searchi($name, $categories)],
  737. OC_Log::DEBUG);
  738. unset($categories[$this->array_searchi($name, $categories)]);
  739. }
  740. }
  741. $object->setString('CATEGORIES', implode(',', $categories));
  742. if($vobject !== $object) {
  743. $vobject[$componentname] = $object;
  744. }
  745. $value[1] = $vobject->serialize();
  746. $objects[$key] = $value;
  747. } else {
  748. OC_Log::write('core', __METHOD__
  749. .', unable to parse. ID: ' . $value[0] . ', '
  750. . substr($value[1], 0, 50) . '(...)', OC_Log::DEBUG);
  751. }
  752. }
  753. }
  754. }
  755. // case-insensitive in_array
  756. private function in_arrayi($needle, $haystack) {
  757. if(!is_array($haystack)) {
  758. return false;
  759. }
  760. return in_array(strtolower($needle), array_map('strtolower', $haystack));
  761. }
  762. // case-insensitive array_search
  763. private function array_searchi($needle, $haystack) {
  764. if(!is_array($haystack)) {
  765. return false;
  766. }
  767. return array_search(strtolower($needle), array_map('strtolower', $haystack));
  768. }
  769. }