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.

SystemTagManager.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  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 Jörn Friedrich Dreyer <jfd@butonic.de>
  8. * @author Roeland Jago Douma <roeland@famdouma.nl>
  9. * @author Vincent Petry <pvince81@owncloud.com>
  10. *
  11. * @license AGPL-3.0
  12. *
  13. * This code is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU Affero General Public License, version 3,
  15. * as published by the Free Software Foundation.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU Affero General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU Affero General Public License, version 3,
  23. * along with this program. If not, see <http://www.gnu.org/licenses/>
  24. *
  25. */
  26. namespace OC\SystemTag;
  27. use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
  28. use OCP\DB\QueryBuilder\IQueryBuilder;
  29. use OCP\IDBConnection;
  30. use OCP\SystemTag\ISystemTagManager;
  31. use OCP\SystemTag\ManagerEvent;
  32. use OCP\SystemTag\TagAlreadyExistsException;
  33. use OCP\SystemTag\TagNotFoundException;
  34. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  35. use OCP\IGroupManager;
  36. use OCP\SystemTag\ISystemTag;
  37. use OCP\IUser;
  38. /**
  39. * Manager class for system tags
  40. */
  41. class SystemTagManager implements ISystemTagManager {
  42. const TAG_TABLE = 'systemtag';
  43. const TAG_GROUP_TABLE = 'systemtag_group';
  44. /** @var IDBConnection */
  45. protected $connection;
  46. /** @var EventDispatcherInterface */
  47. protected $dispatcher;
  48. /** @var IGroupManager */
  49. protected $groupManager;
  50. /**
  51. * Prepared query for selecting tags directly
  52. *
  53. * @var \OCP\DB\QueryBuilder\IQueryBuilder
  54. */
  55. private $selectTagQuery;
  56. /**
  57. * Constructor.
  58. *
  59. * @param IDBConnection $connection database connection
  60. * @param IGroupManager $groupManager
  61. * @param EventDispatcherInterface $dispatcher
  62. */
  63. public function __construct(
  64. IDBConnection $connection,
  65. IGroupManager $groupManager,
  66. EventDispatcherInterface $dispatcher
  67. ) {
  68. $this->connection = $connection;
  69. $this->groupManager = $groupManager;
  70. $this->dispatcher = $dispatcher;
  71. $query = $this->connection->getQueryBuilder();
  72. $this->selectTagQuery = $query->select('*')
  73. ->from(self::TAG_TABLE)
  74. ->where($query->expr()->eq('name', $query->createParameter('name')))
  75. ->andWhere($query->expr()->eq('visibility', $query->createParameter('visibility')))
  76. ->andWhere($query->expr()->eq('editable', $query->createParameter('editable')));
  77. }
  78. /**
  79. * {@inheritdoc}
  80. */
  81. public function getTagsByIds($tagIds): array {
  82. if (!\is_array($tagIds)) {
  83. $tagIds = [$tagIds];
  84. }
  85. $tags = [];
  86. // note: not all databases will fail if it's a string or starts with a number
  87. foreach ($tagIds as $tagId) {
  88. if (!is_numeric($tagId)) {
  89. throw new \InvalidArgumentException('Tag id must be integer');
  90. }
  91. }
  92. $query = $this->connection->getQueryBuilder();
  93. $query->select('*')
  94. ->from(self::TAG_TABLE)
  95. ->where($query->expr()->in('id', $query->createParameter('tagids')))
  96. ->addOrderBy('name', 'ASC')
  97. ->addOrderBy('visibility', 'ASC')
  98. ->addOrderBy('editable', 'ASC')
  99. ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY);
  100. $result = $query->execute();
  101. while ($row = $result->fetch()) {
  102. $tags[$row['id']] = $this->createSystemTagFromRow($row);
  103. }
  104. $result->closeCursor();
  105. if (\count($tags) !== \count($tagIds)) {
  106. throw new TagNotFoundException(
  107. 'Tag id(s) not found', 0, null, array_diff($tagIds, array_keys($tags))
  108. );
  109. }
  110. return $tags;
  111. }
  112. /**
  113. * {@inheritdoc}
  114. */
  115. public function getAllTags($visibilityFilter = null, $nameSearchPattern = null): array {
  116. $tags = [];
  117. $query = $this->connection->getQueryBuilder();
  118. $query->select('*')
  119. ->from(self::TAG_TABLE);
  120. if (!\is_null($visibilityFilter)) {
  121. $query->andWhere($query->expr()->eq('visibility', $query->createNamedParameter((int)$visibilityFilter)));
  122. }
  123. if (!empty($nameSearchPattern)) {
  124. $query->andWhere(
  125. $query->expr()->like(
  126. 'name',
  127. $query->createNamedParameter('%' . $this->connection->escapeLikeParameter($nameSearchPattern). '%')
  128. )
  129. );
  130. }
  131. $query
  132. ->addOrderBy('name', 'ASC')
  133. ->addOrderBy('visibility', 'ASC')
  134. ->addOrderBy('editable', 'ASC');
  135. $result = $query->execute();
  136. while ($row = $result->fetch()) {
  137. $tags[$row['id']] = $this->createSystemTagFromRow($row);
  138. }
  139. $result->closeCursor();
  140. return $tags;
  141. }
  142. /**
  143. * {@inheritdoc}
  144. */
  145. public function getTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag {
  146. $result = $this->selectTagQuery
  147. ->setParameter('name', $tagName)
  148. ->setParameter('visibility', $userVisible ? 1 : 0)
  149. ->setParameter('editable', $userAssignable ? 1 : 0)
  150. ->execute();
  151. $row = $result->fetch();
  152. $result->closeCursor();
  153. if (!$row) {
  154. throw new TagNotFoundException(
  155. 'Tag ("' . $tagName . '", '. $userVisible . ', ' . $userAssignable . ') does not exist'
  156. );
  157. }
  158. return $this->createSystemTagFromRow($row);
  159. }
  160. /**
  161. * {@inheritdoc}
  162. */
  163. public function createTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag {
  164. $query = $this->connection->getQueryBuilder();
  165. $query->insert(self::TAG_TABLE)
  166. ->values([
  167. 'name' => $query->createNamedParameter($tagName),
  168. 'visibility' => $query->createNamedParameter($userVisible ? 1 : 0),
  169. 'editable' => $query->createNamedParameter($userAssignable ? 1 : 0),
  170. ]);
  171. try {
  172. $query->execute();
  173. } catch (UniqueConstraintViolationException $e) {
  174. throw new TagAlreadyExistsException(
  175. 'Tag ("' . $tagName . '", '. $userVisible . ', ' . $userAssignable . ') already exists',
  176. 0,
  177. $e
  178. );
  179. }
  180. $tagId = $query->getLastInsertId();
  181. $tag = new SystemTag(
  182. (string)$tagId,
  183. $tagName,
  184. $userVisible,
  185. $userAssignable
  186. );
  187. $this->dispatcher->dispatch(ManagerEvent::EVENT_CREATE, new ManagerEvent(
  188. ManagerEvent::EVENT_CREATE, $tag
  189. ));
  190. return $tag;
  191. }
  192. /**
  193. * {@inheritdoc}
  194. */
  195. public function updateTag(string $tagId, string $tagName, bool $userVisible, bool $userAssignable) {
  196. try {
  197. $tags = $this->getTagsByIds($tagId);
  198. } catch (TagNotFoundException $e) {
  199. throw new TagNotFoundException(
  200. 'Tag does not exist', 0, null, [$tagId]
  201. );
  202. }
  203. $beforeUpdate = array_shift($tags);
  204. $afterUpdate = new SystemTag(
  205. $tagId,
  206. $tagName,
  207. $userVisible,
  208. $userAssignable
  209. );
  210. $query = $this->connection->getQueryBuilder();
  211. $query->update(self::TAG_TABLE)
  212. ->set('name', $query->createParameter('name'))
  213. ->set('visibility', $query->createParameter('visibility'))
  214. ->set('editable', $query->createParameter('editable'))
  215. ->where($query->expr()->eq('id', $query->createParameter('tagid')))
  216. ->setParameter('name', $tagName)
  217. ->setParameter('visibility', $userVisible ? 1 : 0)
  218. ->setParameter('editable', $userAssignable ? 1 : 0)
  219. ->setParameter('tagid', $tagId);
  220. try {
  221. if ($query->execute() === 0) {
  222. throw new TagNotFoundException(
  223. 'Tag does not exist', 0, null, [$tagId]
  224. );
  225. }
  226. } catch (UniqueConstraintViolationException $e) {
  227. throw new TagAlreadyExistsException(
  228. 'Tag ("' . $tagName . '", '. $userVisible . ', ' . $userAssignable . ') already exists',
  229. 0,
  230. $e
  231. );
  232. }
  233. $this->dispatcher->dispatch(ManagerEvent::EVENT_UPDATE, new ManagerEvent(
  234. ManagerEvent::EVENT_UPDATE, $afterUpdate, $beforeUpdate
  235. ));
  236. }
  237. /**
  238. * {@inheritdoc}
  239. */
  240. public function deleteTags($tagIds) {
  241. if (!\is_array($tagIds)) {
  242. $tagIds = [$tagIds];
  243. }
  244. $tagNotFoundException = null;
  245. $tags = [];
  246. try {
  247. $tags = $this->getTagsByIds($tagIds);
  248. } catch (TagNotFoundException $e) {
  249. $tagNotFoundException = $e;
  250. // Get existing tag objects for the hooks later
  251. $existingTags = array_diff($tagIds, $tagNotFoundException->getMissingTags());
  252. if (!empty($existingTags)) {
  253. try {
  254. $tags = $this->getTagsByIds($existingTags);
  255. } catch (TagNotFoundException $e) {
  256. // Ignore further errors...
  257. }
  258. }
  259. }
  260. // delete relationships first
  261. $query = $this->connection->getQueryBuilder();
  262. $query->delete(SystemTagObjectMapper::RELATION_TABLE)
  263. ->where($query->expr()->in('systemtagid', $query->createParameter('tagids')))
  264. ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY)
  265. ->execute();
  266. $query = $this->connection->getQueryBuilder();
  267. $query->delete(self::TAG_TABLE)
  268. ->where($query->expr()->in('id', $query->createParameter('tagids')))
  269. ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY)
  270. ->execute();
  271. foreach ($tags as $tag) {
  272. $this->dispatcher->dispatch(ManagerEvent::EVENT_DELETE, new ManagerEvent(
  273. ManagerEvent::EVENT_DELETE, $tag
  274. ));
  275. }
  276. if ($tagNotFoundException !== null) {
  277. throw new TagNotFoundException(
  278. 'Tag id(s) not found', 0, $tagNotFoundException, $tagNotFoundException->getMissingTags()
  279. );
  280. }
  281. }
  282. /**
  283. * {@inheritdoc}
  284. */
  285. public function canUserAssignTag(ISystemTag $tag, IUser $user): bool {
  286. // early check to avoid unneeded group lookups
  287. if ($tag->isUserAssignable() && $tag->isUserVisible()) {
  288. return true;
  289. }
  290. if ($this->groupManager->isAdmin($user->getUID())) {
  291. return true;
  292. }
  293. if (!$tag->isUserVisible()) {
  294. return false;
  295. }
  296. $groupIds = $this->groupManager->getUserGroupIds($user);
  297. if (!empty($groupIds)) {
  298. $matchingGroups = array_intersect($groupIds, $this->getTagGroups($tag));
  299. if (!empty($matchingGroups)) {
  300. return true;
  301. }
  302. }
  303. return false;
  304. }
  305. /**
  306. * {@inheritdoc}
  307. */
  308. public function canUserSeeTag(ISystemTag $tag, IUser $user): bool {
  309. if ($tag->isUserVisible()) {
  310. return true;
  311. }
  312. if ($this->groupManager->isAdmin($user->getUID())) {
  313. return true;
  314. }
  315. return false;
  316. }
  317. private function createSystemTagFromRow($row) {
  318. return new SystemTag((string)$row['id'], $row['name'], (bool)$row['visibility'], (bool)$row['editable']);
  319. }
  320. /**
  321. * {@inheritdoc}
  322. */
  323. public function setTagGroups(ISystemTag $tag, array $groupIds) {
  324. // delete relationships first
  325. $this->connection->beginTransaction();
  326. try {
  327. $query = $this->connection->getQueryBuilder();
  328. $query->delete(self::TAG_GROUP_TABLE)
  329. ->where($query->expr()->eq('systemtagid', $query->createNamedParameter($tag->getId())))
  330. ->execute();
  331. // add each group id
  332. $query = $this->connection->getQueryBuilder();
  333. $query->insert(self::TAG_GROUP_TABLE)
  334. ->values([
  335. 'systemtagid' => $query->createNamedParameter($tag->getId()),
  336. 'gid' => $query->createParameter('gid'),
  337. ]);
  338. foreach ($groupIds as $groupId) {
  339. if ($groupId === '') {
  340. continue;
  341. }
  342. $query->setParameter('gid', $groupId);
  343. $query->execute();
  344. }
  345. $this->connection->commit();
  346. } catch (\Exception $e) {
  347. $this->connection->rollBack();
  348. throw $e;
  349. }
  350. }
  351. /**
  352. * {@inheritdoc}
  353. */
  354. public function getTagGroups(ISystemTag $tag): array {
  355. $groupIds = [];
  356. $query = $this->connection->getQueryBuilder();
  357. $query->select('gid')
  358. ->from(self::TAG_GROUP_TABLE)
  359. ->where($query->expr()->eq('systemtagid', $query->createNamedParameter($tag->getId())))
  360. ->orderBy('gid');
  361. $result = $query->execute();
  362. while ($row = $result->fetch()) {
  363. $groupIds[] = $row['gid'];
  364. }
  365. $result->closeCursor();
  366. return $groupIds;
  367. }
  368. }