SystemTagManager.php 11 KB

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