Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

8 роки тому
9 роки тому
8 роки тому
9 роки тому
8 роки тому
9 роки тому
9 роки тому
9 роки тому
8 роки тому
8 роки тому
10 роки тому
10 роки тому
10 роки тому
10 роки тому
10 роки тому
10 роки тому
10 роки тому
10 роки тому
10 роки тому
10 роки тому
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  6. * @author Bernhard Reiter <ockham@raz.or.at>
  7. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  8. * @author Daniel Kesselberg <mail@danielkesselberg.de>
  9. * @author Joas Schilling <coding@schilljs.com>
  10. * @author Morris Jobke <hey@morrisjobke.de>
  11. * @author Robin Appelman <robin@icewind.nl>
  12. * @author Roeland Jago Douma <roeland@famdouma.nl>
  13. * @author Thomas Tanghus <thomas@tanghus.net>
  14. * @author Vincent Petry <vincent@nextcloud.com>
  15. *
  16. * @license AGPL-3.0
  17. *
  18. * This code is free software: you can redistribute it and/or modify
  19. * it under the terms of the GNU Affero General Public License, version 3,
  20. * as published by the Free Software Foundation.
  21. *
  22. * This program is distributed in the hope that it will be useful,
  23. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. * GNU Affero General Public License for more details.
  26. *
  27. * You should have received a copy of the GNU Affero General Public License, version 3,
  28. * along with this program. If not, see <http://www.gnu.org/licenses/>
  29. *
  30. */
  31. namespace OC;
  32. use OC\Tagging\Tag;
  33. use OC\Tagging\TagMapper;
  34. use OCP\DB\Exception;
  35. use OCP\DB\QueryBuilder\IQueryBuilder;
  36. use OCP\IDBConnection;
  37. use OCP\ITags;
  38. use OCP\Share_Backend;
  39. use Psr\Log\LoggerInterface;
  40. class Tags implements ITags {
  41. /**
  42. * Used for storing objectid/categoryname pairs while rescanning.
  43. */
  44. private static array $relations = [];
  45. private string $type;
  46. private string $user;
  47. private IDBConnection $db;
  48. private LoggerInterface $logger;
  49. private array $tags = [];
  50. /**
  51. * Are we including tags for shared items?
  52. */
  53. private bool $includeShared = false;
  54. /**
  55. * The current user, plus any owners of the items shared with the current
  56. * user, if $this->includeShared === true.
  57. */
  58. private array $owners = [];
  59. /**
  60. * The Mapper we are using to communicate our Tag objects to the database.
  61. */
  62. private TagMapper $mapper;
  63. /**
  64. * The sharing backend for objects of $this->type. Required if
  65. * $this->includeShared === true to determine ownership of items.
  66. */
  67. private ?Share_Backend $backend = null;
  68. public const TAG_TABLE = 'vcategory';
  69. public const RELATION_TABLE = 'vcategory_to_object';
  70. /**
  71. * Constructor.
  72. *
  73. * @param TagMapper $mapper Instance of the TagMapper abstraction layer.
  74. * @param string $user The user whose data the object will operate on.
  75. * @param string $type The type of items for which tags will be loaded.
  76. * @param array $defaultTags Tags that should be created at construction.
  77. *
  78. * since 20.0.0 $includeShared isn't used anymore
  79. */
  80. public function __construct(TagMapper $mapper, string $user, string $type, LoggerInterface $logger, IDBConnection $connection, array $defaultTags = []) {
  81. $this->mapper = $mapper;
  82. $this->user = $user;
  83. $this->type = $type;
  84. $this->owners = [$this->user];
  85. $this->tags = $this->mapper->loadTags($this->owners, $this->type);
  86. $this->db = $connection;
  87. $this->logger = $logger;
  88. if (count($defaultTags) > 0 && count($this->tags) === 0) {
  89. $this->addMultiple($defaultTags, true);
  90. }
  91. }
  92. /**
  93. * Check if any tags are saved for this type and user.
  94. *
  95. * @return boolean
  96. */
  97. public function isEmpty(): bool {
  98. return count($this->tags) === 0;
  99. }
  100. /**
  101. * Returns an array mapping a given tag's properties to its values:
  102. * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
  103. *
  104. * @param string $id The ID of the tag that is going to be mapped
  105. * @return array|false
  106. */
  107. public function getTag(string $id) {
  108. $key = $this->getTagById($id);
  109. if ($key !== false) {
  110. return $this->tagMap($this->tags[$key]);
  111. }
  112. return false;
  113. }
  114. /**
  115. * Get the tags for a specific user.
  116. *
  117. * This returns an array with maps containing each tag's properties:
  118. * [
  119. * ['id' => 0, 'name' = 'First tag', 'owner' = 'User', 'type' => 'tagtype'],
  120. * ['id' => 1, 'name' = 'Shared tag', 'owner' = 'Other user', 'type' => 'tagtype'],
  121. * ]
  122. *
  123. * @return array<array-key, array{id: int, name: string}>
  124. */
  125. public function getTags(): array {
  126. if (!count($this->tags)) {
  127. return [];
  128. }
  129. usort($this->tags, function ($a, $b) {
  130. return strnatcasecmp($a->getName(), $b->getName());
  131. });
  132. $tagMap = [];
  133. foreach ($this->tags as $tag) {
  134. if ($tag->getName() !== ITags::TAG_FAVORITE) {
  135. $tagMap[] = $this->tagMap($tag);
  136. }
  137. }
  138. return $tagMap;
  139. }
  140. /**
  141. * Return only the tags owned by the given user, omitting any tags shared
  142. * by other users.
  143. *
  144. * @param string $user The user whose tags are to be checked.
  145. * @return array An array of Tag objects.
  146. */
  147. public function getTagsForUser(string $user): array {
  148. return array_filter($this->tags,
  149. function ($tag) use ($user) {
  150. return $tag->getOwner() === $user;
  151. }
  152. );
  153. }
  154. /**
  155. * Get the list of tags for the given ids.
  156. *
  157. * @param array $objIds array of object ids
  158. * @return array|false of tags id as key to array of tag names
  159. * or false if an error occurred
  160. */
  161. public function getTagsForObjects(array $objIds) {
  162. $entries = [];
  163. try {
  164. $chunks = array_chunk($objIds, 900, false);
  165. $qb = $this->db->getQueryBuilder();
  166. $qb->select('category', 'categoryid', 'objid')
  167. ->from(self::RELATION_TABLE, 'r')
  168. ->join('r', self::TAG_TABLE, 't', $qb->expr()->eq('r.categoryid', 't.id'))
  169. ->where($qb->expr()->eq('uid', $qb->createParameter('uid')))
  170. ->andWhere($qb->expr()->eq('r.type', $qb->createParameter('type')))
  171. ->andWhere($qb->expr()->in('objid', $qb->createParameter('chunk')));
  172. foreach ($chunks as $chunk) {
  173. $qb->setParameter('uid', $this->user, IQueryBuilder::PARAM_STR);
  174. $qb->setParameter('type', $this->type, IQueryBuilder::PARAM_STR);
  175. $qb->setParameter('chunk', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
  176. $result = $qb->executeQuery();
  177. while ($row = $result->fetch()) {
  178. $objId = (int)$row['objid'];
  179. if (!isset($entries[$objId])) {
  180. $entries[$objId] = [];
  181. }
  182. $entries[$objId][] = $row['category'];
  183. }
  184. $result->closeCursor();
  185. }
  186. } catch (\Exception $e) {
  187. $this->logger->error($e->getMessage(), [
  188. 'exception' => $e,
  189. 'app' => 'core',
  190. ]);
  191. return false;
  192. }
  193. return $entries;
  194. }
  195. /**
  196. * Get the a list if items tagged with $tag.
  197. *
  198. * Throws an exception if the tag could not be found.
  199. *
  200. * @param string $tag Tag id or name.
  201. * @return int[]|false An array of object ids or false on error.
  202. * @throws \Exception
  203. */
  204. public function getIdsForTag($tag) {
  205. $tagId = false;
  206. if (is_numeric($tag)) {
  207. $tagId = $tag;
  208. } elseif (is_string($tag)) {
  209. $tag = trim($tag);
  210. if ($tag === '') {
  211. $this->logger->debug(__METHOD__ . ' Cannot use empty tag names', ['app' => 'core']);
  212. return false;
  213. }
  214. $tagId = $this->getTagId($tag);
  215. }
  216. if ($tagId === false) {
  217. $l10n = \OCP\Util::getL10N('core');
  218. throw new \Exception(
  219. $l10n->t('Could not find category "%s"', [$tag])
  220. );
  221. }
  222. $ids = [];
  223. try {
  224. $qb = $this->db->getQueryBuilder();
  225. $qb->select('objid')
  226. ->from(self::RELATION_TABLE)
  227. ->where($qb->expr()->eq('categoryid', $qb->createNamedParameter($tagId, IQueryBuilder::PARAM_STR)));
  228. $result = $qb->executeQuery();
  229. } catch (Exception $e) {
  230. $this->logger->error($e->getMessage(), [
  231. 'app' => 'core',
  232. 'exception' => $e,
  233. ]);
  234. return false;
  235. }
  236. while ($row = $result->fetch()) {
  237. $ids[] = (int)$row['objid'];
  238. }
  239. $result->closeCursor();
  240. return $ids;
  241. }
  242. /**
  243. * Checks whether a tag is saved for the given user,
  244. * disregarding the ones shared with him or her.
  245. *
  246. * @param string $name The tag name to check for.
  247. * @param string $user The user whose tags are to be checked.
  248. */
  249. public function userHasTag(string $name, string $user): bool {
  250. $key = $this->array_searchi($name, $this->getTagsForUser($user));
  251. return ($key !== false) ? $this->tags[$key]->getId() : false;
  252. }
  253. /**
  254. * Checks whether a tag is saved for or shared with the current user.
  255. *
  256. * @param string $name The tag name to check for.
  257. */
  258. public function hasTag(string $name): bool {
  259. return $this->getTagId($name) !== false;
  260. }
  261. /**
  262. * Add a new tag.
  263. *
  264. * @param string $name A string with a name of the tag
  265. * @return false|int the id of the added tag or false on error.
  266. */
  267. public function add(string $name) {
  268. $name = trim($name);
  269. if ($name === '') {
  270. $this->logger->debug(__METHOD__ . ' Cannot add an empty tag', ['app' => 'core']);
  271. return false;
  272. }
  273. if ($this->userHasTag($name, $this->user)) {
  274. // TODO use unique db properties instead of an additional check
  275. $this->logger->debug(__METHOD__ . ' Tag with name already exists', ['app' => 'core']);
  276. return false;
  277. }
  278. try {
  279. $tag = new Tag($this->user, $this->type, $name);
  280. $tag = $this->mapper->insert($tag);
  281. $this->tags[] = $tag;
  282. } catch (\Exception $e) {
  283. $this->logger->error($e->getMessage(), [
  284. 'exception' => $e,
  285. 'app' => 'core',
  286. ]);
  287. return false;
  288. }
  289. $this->logger->debug(__METHOD__ . ' Added an tag with ' . $tag->getId(), ['app' => 'core']);
  290. return $tag->getId();
  291. }
  292. /**
  293. * Rename tag.
  294. *
  295. * @param string|integer $from The name or ID of the existing tag
  296. * @param string $to The new name of the tag.
  297. * @return bool
  298. */
  299. public function rename($from, string $to): bool {
  300. $from = trim($from);
  301. $to = trim($to);
  302. if ($to === '' || $from === '') {
  303. $this->logger->debug(__METHOD__ . 'Cannot use an empty tag names', ['app' => 'core']);
  304. return false;
  305. }
  306. if (is_numeric($from)) {
  307. $key = $this->getTagById($from);
  308. } else {
  309. $key = $this->getTagByName($from);
  310. }
  311. if ($key === false) {
  312. $this->logger->debug(__METHOD__ . 'Tag ' . $from . 'does not exist', ['app' => 'core']);
  313. return false;
  314. }
  315. $tag = $this->tags[$key];
  316. if ($this->userHasTag($to, $tag->getOwner())) {
  317. $this->logger->debug(__METHOD__ . 'A tag named' . $to . 'already exists for user' . $tag->getOwner(), ['app' => 'core']);
  318. return false;
  319. }
  320. try {
  321. $tag->setName($to);
  322. $this->tags[$key] = $this->mapper->update($tag);
  323. } catch (\Exception $e) {
  324. $this->logger->error($e->getMessage(), [
  325. 'exception' => $e,
  326. 'app' => 'core',
  327. ]);
  328. return false;
  329. }
  330. return true;
  331. }
  332. /**
  333. * Add a list of new tags.
  334. *
  335. * @param string|string[] $names A string with a name or an array of strings containing
  336. * the name(s) of the tag(s) to add.
  337. * @param bool $sync When true, save the tags
  338. * @param int|null $id int Optional object id to add to this|these tag(s)
  339. * @return bool Returns false on error.
  340. */
  341. public function addMultiple($names, bool $sync = false, ?int $id = null): bool {
  342. if (!is_array($names)) {
  343. $names = [$names];
  344. }
  345. $names = array_map('trim', $names);
  346. array_filter($names);
  347. $newones = [];
  348. foreach ($names as $name) {
  349. if (!$this->hasTag($name) && $name !== '') {
  350. $newones[] = new Tag($this->user, $this->type, $name);
  351. }
  352. if (!is_null($id)) {
  353. // Insert $objectid, $categoryid pairs if not exist.
  354. self::$relations[] = ['objid' => $id, 'tag' => $name];
  355. }
  356. }
  357. $this->tags = array_merge($this->tags, $newones);
  358. if ($sync === true) {
  359. $this->save();
  360. }
  361. return true;
  362. }
  363. /**
  364. * Save the list of tags and their object relations
  365. */
  366. protected function save(): void {
  367. foreach ($this->tags as $tag) {
  368. try {
  369. if (!$this->mapper->tagExists($tag)) {
  370. $this->mapper->insert($tag);
  371. }
  372. } catch (\Exception $e) {
  373. $this->logger->error($e->getMessage(), [
  374. 'exception' => $e,
  375. 'app' => 'core',
  376. ]);
  377. }
  378. }
  379. // reload tags to get the proper ids.
  380. $this->tags = $this->mapper->loadTags($this->owners, $this->type);
  381. $this->logger->debug(__METHOD__ . 'tags' . print_r($this->tags, true), ['app' => 'core']);
  382. // Loop through temporarily cached objectid/tagname pairs
  383. // and save relations.
  384. $tags = $this->tags;
  385. // For some reason this is needed or array_search(i) will return 0..?
  386. ksort($tags);
  387. foreach (self::$relations as $relation) {
  388. $tagId = $this->getTagId($relation['tag']);
  389. $this->logger->debug(__METHOD__ . 'catid ' . $relation['tag'] . ' ' . $tagId, ['app' => 'core']);
  390. if ($tagId) {
  391. $qb = $this->db->getQueryBuilder();
  392. $qb->insert(self::RELATION_TABLE)
  393. ->values([
  394. 'objid' => $qb->createNamedParameter($relation['objid'], IQueryBuilder::PARAM_INT),
  395. 'categoryid' => $qb->createNamedParameter($tagId, IQueryBuilder::PARAM_INT),
  396. 'type' => $qb->createNamedParameter($this->type),
  397. ]);
  398. try {
  399. $qb->executeStatement();
  400. } catch (Exception $e) {
  401. $this->logger->error($e->getMessage(), [
  402. 'exception' => $e,
  403. 'app' => 'core',
  404. ]);
  405. }
  406. }
  407. }
  408. self::$relations = []; // reset
  409. }
  410. /**
  411. * Delete tag/object relations from the db
  412. *
  413. * @param array $ids The ids of the objects
  414. * @return boolean Returns false on error.
  415. */
  416. public function purgeObjects(array $ids): bool {
  417. if (count($ids) === 0) {
  418. // job done ;)
  419. return true;
  420. }
  421. $updates = $ids;
  422. $qb = $this->db->getQueryBuilder();
  423. $qb->delete(self::RELATION_TABLE)
  424. ->where($qb->expr()->in('objid', $qb->createNamedParameter($ids)));
  425. try {
  426. $qb->executeStatement();
  427. } catch (Exception $e) {
  428. $this->logger->error($e->getMessage(), [
  429. 'app' => 'core',
  430. 'exception' => $e,
  431. ]);
  432. return false;
  433. }
  434. return true;
  435. }
  436. /**
  437. * Get favorites for an object type
  438. *
  439. * @return array|false An array of object ids.
  440. */
  441. public function getFavorites() {
  442. if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
  443. return [];
  444. }
  445. try {
  446. return $this->getIdsForTag(ITags::TAG_FAVORITE);
  447. } catch (\Exception $e) {
  448. \OCP\Server::get(LoggerInterface::class)->error(
  449. $e->getMessage(),
  450. [
  451. 'app' => 'core',
  452. 'exception' => $e,
  453. ]
  454. );
  455. return [];
  456. }
  457. }
  458. /**
  459. * Add an object to favorites
  460. *
  461. * @param int $objid The id of the object
  462. * @return boolean
  463. */
  464. public function addToFavorites($objid) {
  465. if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
  466. $this->add(ITags::TAG_FAVORITE);
  467. }
  468. return $this->tagAs($objid, ITags::TAG_FAVORITE);
  469. }
  470. /**
  471. * Remove an object from favorites
  472. *
  473. * @param int $objid The id of the object
  474. * @return boolean
  475. */
  476. public function removeFromFavorites($objid) {
  477. return $this->unTag($objid, ITags::TAG_FAVORITE);
  478. }
  479. /**
  480. * Creates a tag/object relation.
  481. *
  482. * @param int $objid The id of the object
  483. * @param string $tag The id or name of the tag
  484. * @return boolean Returns false on error.
  485. */
  486. public function tagAs($objid, $tag) {
  487. if (is_string($tag) && !is_numeric($tag)) {
  488. $tag = trim($tag);
  489. if ($tag === '') {
  490. $this->logger->debug(__METHOD__.', Cannot add an empty tag');
  491. return false;
  492. }
  493. if (!$this->hasTag($tag)) {
  494. $this->add($tag);
  495. }
  496. $tagId = $this->getTagId($tag);
  497. } else {
  498. $tagId = $tag;
  499. }
  500. $qb = $this->db->getQueryBuilder();
  501. $qb->insert(self::RELATION_TABLE)
  502. ->values([
  503. 'objid' => $qb->createNamedParameter($objid, IQueryBuilder::PARAM_INT),
  504. 'categoryid' => $qb->createNamedParameter($tagId, IQueryBuilder::PARAM_INT),
  505. 'type' => $qb->createNamedParameter($this->type, IQueryBuilder::PARAM_STR),
  506. ]);
  507. try {
  508. $qb->executeStatement();
  509. } catch (\Exception $e) {
  510. \OCP\Server::get(LoggerInterface::class)->error($e->getMessage(), [
  511. 'app' => 'core',
  512. 'exception' => $e,
  513. ]);
  514. return false;
  515. }
  516. return true;
  517. }
  518. /**
  519. * Delete single tag/object relation from the db
  520. *
  521. * @param int $objid The id of the object
  522. * @param string $tag The id or name of the tag
  523. * @return boolean
  524. */
  525. public function unTag($objid, $tag) {
  526. if (is_string($tag) && !is_numeric($tag)) {
  527. $tag = trim($tag);
  528. if ($tag === '') {
  529. $this->logger->debug(__METHOD__.', Tag name is empty');
  530. return false;
  531. }
  532. $tagId = $this->getTagId($tag);
  533. } else {
  534. $tagId = $tag;
  535. }
  536. try {
  537. $qb = $this->db->getQueryBuilder();
  538. $qb->delete(self::RELATION_TABLE)
  539. ->where($qb->expr()->andX(
  540. $qb->expr()->eq('objid', $qb->createNamedParameter($objid)),
  541. $qb->expr()->eq('categoryid', $qb->createNamedParameter($tagId)),
  542. $qb->expr()->eq('type', $qb->createNamedParameter($this->type)),
  543. ))->executeStatement();
  544. } catch (\Exception $e) {
  545. $this->logger->error($e->getMessage(), [
  546. 'app' => 'core',
  547. 'exception' => $e,
  548. ]);
  549. return false;
  550. }
  551. return true;
  552. }
  553. /**
  554. * Delete tags from the database.
  555. *
  556. * @param string[]|integer[] $names An array of tags (names or IDs) to delete
  557. * @return bool Returns false on error
  558. */
  559. public function delete($names) {
  560. if (!is_array($names)) {
  561. $names = [$names];
  562. }
  563. $names = array_map('trim', $names);
  564. array_filter($names);
  565. $this->logger->debug(__METHOD__ . ', before: ' . print_r($this->tags, true));
  566. foreach ($names as $name) {
  567. $id = null;
  568. if (is_numeric($name)) {
  569. $key = $this->getTagById($name);
  570. } else {
  571. $key = $this->getTagByName($name);
  572. }
  573. if ($key !== false) {
  574. $tag = $this->tags[$key];
  575. $id = $tag->getId();
  576. unset($this->tags[$key]);
  577. $this->mapper->delete($tag);
  578. } else {
  579. $this->logger->error(__METHOD__ . 'Cannot delete tag ' . $name . ': not found.');
  580. }
  581. if (!is_null($id) && $id !== false) {
  582. try {
  583. $qb = $this->db->getQueryBuilder();
  584. $qb->delete(self::RELATION_TABLE)
  585. ->where($qb->expr()->eq('categoryid', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
  586. ->executeStatement();
  587. } catch (\Exception $e) {
  588. $this->logger->error($e->getMessage(), [
  589. 'app' => 'core',
  590. 'exception' => $e,
  591. ]);
  592. return false;
  593. }
  594. }
  595. }
  596. return true;
  597. }
  598. // case-insensitive array_search
  599. protected function array_searchi($needle, $haystack, $mem = 'getName') {
  600. if (!is_array($haystack)) {
  601. return false;
  602. }
  603. return array_search(strtolower($needle), array_map(
  604. function ($tag) use ($mem) {
  605. return strtolower(call_user_func([$tag, $mem]));
  606. }, $haystack)
  607. );
  608. }
  609. /**
  610. * Get a tag's ID.
  611. *
  612. * @param string $name The tag name to look for.
  613. * @return string|bool The tag's id or false if no matching tag is found.
  614. */
  615. private function getTagId($name) {
  616. $key = $this->array_searchi($name, $this->tags);
  617. if ($key !== false) {
  618. return $this->tags[$key]->getId();
  619. }
  620. return false;
  621. }
  622. /**
  623. * Get a tag by its name.
  624. *
  625. * @param string $name The tag name.
  626. * @return integer|bool The tag object's offset within the $this->tags
  627. * array or false if it doesn't exist.
  628. */
  629. private function getTagByName($name) {
  630. return $this->array_searchi($name, $this->tags, 'getName');
  631. }
  632. /**
  633. * Get a tag by its ID.
  634. *
  635. * @param string $id The tag ID to look for.
  636. * @return integer|bool The tag object's offset within the $this->tags
  637. * array or false if it doesn't exist.
  638. */
  639. private function getTagById($id) {
  640. return $this->array_searchi($id, $this->tags, 'getId');
  641. }
  642. /**
  643. * Returns an array mapping a given tag's properties to its values:
  644. * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
  645. *
  646. * @param Tag $tag The tag that is going to be mapped
  647. * @return array
  648. */
  649. private function tagMap(Tag $tag) {
  650. return [
  651. 'id' => $tag->getId(),
  652. 'name' => $tag->getName(),
  653. 'owner' => $tag->getOwner(),
  654. 'type' => $tag->getType()
  655. ];
  656. }
  657. }