You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SystemTagObjectMapper.php 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2016, ownCloud, Inc.
  5. *
  6. * @author Joas Schilling <coding@schilljs.com>
  7. * @author Roeland Jago Douma <roeland@famdouma.nl>
  8. * @author Vincent Petry <pvince81@owncloud.com>
  9. *
  10. * @license AGPL-3.0
  11. *
  12. * This code is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU Affero General Public License, version 3,
  14. * as published by the Free Software Foundation.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU Affero General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Affero General Public License, version 3,
  22. * along with this program. If not, see <http://www.gnu.org/licenses/>
  23. *
  24. */
  25. namespace OC\SystemTag;
  26. use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
  27. use OCP\DB\QueryBuilder\IQueryBuilder;
  28. use OCP\IDBConnection;
  29. use OCP\SystemTag\ISystemTag;
  30. use OCP\SystemTag\ISystemTagManager;
  31. use OCP\SystemTag\ISystemTagObjectMapper;
  32. use OCP\SystemTag\MapperEvent;
  33. use OCP\SystemTag\TagNotFoundException;
  34. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  35. class SystemTagObjectMapper implements ISystemTagObjectMapper {
  36. const RELATION_TABLE = 'systemtag_object_mapping';
  37. /** @var ISystemTagManager */
  38. protected $tagManager;
  39. /** @var IDBConnection */
  40. protected $connection;
  41. /** @var EventDispatcherInterface */
  42. protected $dispatcher;
  43. /**
  44. * Constructor.
  45. *
  46. * @param IDBConnection $connection database connection
  47. * @param ISystemTagManager $tagManager system tag manager
  48. * @param EventDispatcherInterface $dispatcher
  49. */
  50. public function __construct(IDBConnection $connection, ISystemTagManager $tagManager, EventDispatcherInterface $dispatcher) {
  51. $this->connection = $connection;
  52. $this->tagManager = $tagManager;
  53. $this->dispatcher = $dispatcher;
  54. }
  55. /**
  56. * {@inheritdoc}
  57. */
  58. public function getTagIdsForObjects($objIds, string $objectType): array {
  59. if (!\is_array($objIds)) {
  60. $objIds = [$objIds];
  61. } else if (empty($objIds)) {
  62. return [];
  63. }
  64. $query = $this->connection->getQueryBuilder();
  65. $query->select(['systemtagid', 'objectid'])
  66. ->from(self::RELATION_TABLE)
  67. ->where($query->expr()->in('objectid', $query->createParameter('objectids')))
  68. ->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
  69. ->setParameter('objectids', $objIds, IQueryBuilder::PARAM_INT_ARRAY)
  70. ->setParameter('objecttype', $objectType)
  71. ->addOrderBy('objectid', 'ASC')
  72. ->addOrderBy('systemtagid', 'ASC');
  73. $mapping = [];
  74. foreach ($objIds as $objId) {
  75. $mapping[$objId] = [];
  76. }
  77. $result = $query->execute();
  78. while ($row = $result->fetch()) {
  79. $objectId = $row['objectid'];
  80. $mapping[$objectId][] = $row['systemtagid'];
  81. }
  82. $result->closeCursor();
  83. return $mapping;
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. public function getObjectIdsForTags($tagIds, string $objectType, int $limit = 0, string $offset = ''): array {
  89. if (!\is_array($tagIds)) {
  90. $tagIds = [$tagIds];
  91. }
  92. $this->assertTagsExist($tagIds);
  93. $query = $this->connection->getQueryBuilder();
  94. $query->selectDistinct('objectid')
  95. ->from(self::RELATION_TABLE)
  96. ->where($query->expr()->in('systemtagid', $query->createNamedParameter($tagIds, IQueryBuilder::PARAM_INT_ARRAY)))
  97. ->andWhere($query->expr()->eq('objecttype', $query->createNamedParameter($objectType)));
  98. if ($limit) {
  99. if (\count($tagIds) !== 1) {
  100. throw new \InvalidArgumentException('Limit is only allowed with a single tag');
  101. }
  102. $query->setMaxResults($limit)
  103. ->orderBy('objectid', 'ASC');
  104. if ($offset !== '') {
  105. $query->andWhere($query->expr()->gt('objectid', $query->createNamedParameter($offset)));
  106. }
  107. }
  108. $objectIds = [];
  109. $result = $query->execute();
  110. while ($row = $result->fetch()) {
  111. $objectIds[] = $row['objectid'];
  112. }
  113. return $objectIds;
  114. }
  115. /**
  116. * {@inheritdoc}
  117. */
  118. public function assignTags(string $objId, string $objectType, $tagIds) {
  119. if (!\is_array($tagIds)) {
  120. $tagIds = [$tagIds];
  121. }
  122. $this->assertTagsExist($tagIds);
  123. $query = $this->connection->getQueryBuilder();
  124. $query->insert(self::RELATION_TABLE)
  125. ->values([
  126. 'objectid' => $query->createNamedParameter($objId),
  127. 'objecttype' => $query->createNamedParameter($objectType),
  128. 'systemtagid' => $query->createParameter('tagid'),
  129. ]);
  130. foreach ($tagIds as $tagId) {
  131. try {
  132. $query->setParameter('tagid', $tagId);
  133. $query->execute();
  134. } catch (UniqueConstraintViolationException $e) {
  135. // ignore existing relations
  136. }
  137. }
  138. $this->dispatcher->dispatch(MapperEvent::EVENT_ASSIGN, new MapperEvent(
  139. MapperEvent::EVENT_ASSIGN,
  140. $objectType,
  141. $objId,
  142. $tagIds
  143. ));
  144. }
  145. /**
  146. * {@inheritdoc}
  147. */
  148. public function unassignTags(string $objId, string $objectType, $tagIds) {
  149. if (!\is_array($tagIds)) {
  150. $tagIds = [$tagIds];
  151. }
  152. $this->assertTagsExist($tagIds);
  153. $query = $this->connection->getQueryBuilder();
  154. $query->delete(self::RELATION_TABLE)
  155. ->where($query->expr()->eq('objectid', $query->createParameter('objectid')))
  156. ->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
  157. ->andWhere($query->expr()->in('systemtagid', $query->createParameter('tagids')))
  158. ->setParameter('objectid', $objId)
  159. ->setParameter('objecttype', $objectType)
  160. ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY)
  161. ->execute();
  162. $this->dispatcher->dispatch(MapperEvent::EVENT_UNASSIGN, new MapperEvent(
  163. MapperEvent::EVENT_UNASSIGN,
  164. $objectType,
  165. $objId,
  166. $tagIds
  167. ));
  168. }
  169. /**
  170. * {@inheritdoc}
  171. */
  172. public function haveTag($objIds, string $objectType, string $tagId, bool $all = true): bool {
  173. $this->assertTagsExist([$tagId]);
  174. if (!\is_array($objIds)) {
  175. $objIds = [$objIds];
  176. }
  177. $query = $this->connection->getQueryBuilder();
  178. if (!$all) {
  179. // If we only need one entry, we make the query lighter, by not
  180. // counting the elements
  181. $query->select('*')
  182. ->setMaxResults(1);
  183. } else {
  184. $query->select($query->func()->count($query->expr()->literal(1)));
  185. }
  186. $query->from(self::RELATION_TABLE)
  187. ->where($query->expr()->in('objectid', $query->createParameter('objectids')))
  188. ->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
  189. ->andWhere($query->expr()->eq('systemtagid', $query->createParameter('tagid')))
  190. ->setParameter('objectids', $objIds, IQueryBuilder::PARAM_STR_ARRAY)
  191. ->setParameter('tagid', $tagId)
  192. ->setParameter('objecttype', $objectType);
  193. $result = $query->execute();
  194. $row = $result->fetch(\PDO::FETCH_NUM);
  195. $result->closeCursor();
  196. if ($all) {
  197. return ((int)$row[0] === \count($objIds));
  198. }
  199. return (bool) $row;
  200. }
  201. /**
  202. * Asserts that all the given tag ids exist.
  203. *
  204. * @param string[] $tagIds tag ids to check
  205. *
  206. * @throws \OCP\SystemTag\TagNotFoundException if at least one tag did not exist
  207. */
  208. private function assertTagsExist($tagIds) {
  209. $tags = $this->tagManager->getTagsByIds($tagIds);
  210. if (\count($tags) !== \count($tagIds)) {
  211. // at least one tag missing, bail out
  212. $foundTagIds = array_map(
  213. function(ISystemTag $tag) {
  214. return $tag->getId();
  215. },
  216. $tags
  217. );
  218. $missingTagIds = array_diff($tagIds, $foundTagIds);
  219. throw new TagNotFoundException(
  220. 'Tags not found', 0, null, $missingTagIds
  221. );
  222. }
  223. }
  224. }