Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

vcategories.php 25KB

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