TagsPlugin.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. <?php
  2. /**
  3. * @author Thomas Müller <thomas.mueller@tmit.eu>
  4. * @author Vincent Petry <pvince81@owncloud.com>
  5. *
  6. * @copyright Copyright (c) 2016, ownCloud, Inc.
  7. * @license AGPL-3.0
  8. *
  9. * This code is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License, version 3,
  11. * as published by the Free Software Foundation.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License, version 3,
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>
  20. *
  21. */
  22. namespace OCA\DAV\Connector\Sabre;
  23. /**
  24. * ownCloud
  25. *
  26. * @author Vincent Petry
  27. * @copyright 2014 Vincent Petry <pvince81@owncloud.com>
  28. *
  29. * This library is free software; you can redistribute it and/or
  30. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  31. * License as published by the Free Software Foundation; either
  32. * version 3 of the License, or any later version.
  33. *
  34. * This library is distributed in the hope that it will be useful,
  35. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  36. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  37. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  38. *
  39. * You should have received a copy of the GNU Affero General Public
  40. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  41. *
  42. */
  43. use \Sabre\DAV\PropFind;
  44. use \Sabre\DAV\PropPatch;
  45. class TagsPlugin extends \Sabre\DAV\ServerPlugin
  46. {
  47. // namespace
  48. const NS_OWNCLOUD = 'http://owncloud.org/ns';
  49. const TAGS_PROPERTYNAME = '{http://owncloud.org/ns}tags';
  50. const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite';
  51. const TAG_FAVORITE = '_$!<Favorite>!$_';
  52. /**
  53. * Reference to main server object
  54. *
  55. * @var \Sabre\DAV\Server
  56. */
  57. private $server;
  58. /**
  59. * @var \OCP\ITagManager
  60. */
  61. private $tagManager;
  62. /**
  63. * @var \OCP\ITags
  64. */
  65. private $tagger;
  66. /**
  67. * Array of file id to tags array
  68. * The null value means the cache wasn't initialized.
  69. *
  70. * @var array
  71. */
  72. private $cachedTags;
  73. /**
  74. * @var \Sabre\DAV\Tree
  75. */
  76. private $tree;
  77. /**
  78. * @param \Sabre\DAV\Tree $tree tree
  79. * @param \OCP\ITagManager $tagManager tag manager
  80. */
  81. public function __construct(\Sabre\DAV\Tree $tree, \OCP\ITagManager $tagManager) {
  82. $this->tree = $tree;
  83. $this->tagManager = $tagManager;
  84. $this->tagger = null;
  85. $this->cachedTags = array();
  86. }
  87. /**
  88. * This initializes the plugin.
  89. *
  90. * This function is called by \Sabre\DAV\Server, after
  91. * addPlugin is called.
  92. *
  93. * This method should set up the required event subscriptions.
  94. *
  95. * @param \Sabre\DAV\Server $server
  96. * @return void
  97. */
  98. public function initialize(\Sabre\DAV\Server $server) {
  99. $server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc';
  100. $server->xml->elementMap[self::TAGS_PROPERTYNAME] = 'OCA\\DAV\\Connector\\Sabre\\TagList';
  101. $this->server = $server;
  102. $this->server->on('propFind', array($this, 'handleGetProperties'));
  103. $this->server->on('propPatch', array($this, 'handleUpdateProperties'));
  104. }
  105. /**
  106. * Returns the tagger
  107. *
  108. * @return \OCP\ITags tagger
  109. */
  110. private function getTagger() {
  111. if (!$this->tagger) {
  112. $this->tagger = $this->tagManager->load('files');
  113. }
  114. return $this->tagger;
  115. }
  116. /**
  117. * Returns tags and favorites.
  118. *
  119. * @param integer $fileId file id
  120. * @return array list($tags, $favorite) with $tags as tag array
  121. * and $favorite is a boolean whether the file was favorited
  122. */
  123. private function getTagsAndFav($fileId) {
  124. $isFav = false;
  125. $tags = $this->getTags($fileId);
  126. if ($tags) {
  127. $favPos = array_search(self::TAG_FAVORITE, $tags);
  128. if ($favPos !== false) {
  129. $isFav = true;
  130. unset($tags[$favPos]);
  131. }
  132. }
  133. return array($tags, $isFav);
  134. }
  135. /**
  136. * Returns tags for the given file id
  137. *
  138. * @param integer $fileId file id
  139. * @return array list of tags for that file
  140. */
  141. private function getTags($fileId) {
  142. if (isset($this->cachedTags[$fileId])) {
  143. return $this->cachedTags[$fileId];
  144. } else {
  145. $tags = $this->getTagger()->getTagsForObjects(array($fileId));
  146. if ($tags !== false) {
  147. if (empty($tags)) {
  148. return array();
  149. }
  150. return current($tags);
  151. }
  152. }
  153. return null;
  154. }
  155. /**
  156. * Updates the tags of the given file id
  157. *
  158. * @param int $fileId
  159. * @param array $tags array of tag strings
  160. */
  161. private function updateTags($fileId, $tags) {
  162. $tagger = $this->getTagger();
  163. $currentTags = $this->getTags($fileId);
  164. $newTags = array_diff($tags, $currentTags);
  165. foreach ($newTags as $tag) {
  166. if ($tag === self::TAG_FAVORITE) {
  167. continue;
  168. }
  169. $tagger->tagAs($fileId, $tag);
  170. }
  171. $deletedTags = array_diff($currentTags, $tags);
  172. foreach ($deletedTags as $tag) {
  173. if ($tag === self::TAG_FAVORITE) {
  174. continue;
  175. }
  176. $tagger->unTag($fileId, $tag);
  177. }
  178. }
  179. /**
  180. * Adds tags and favorites properties to the response,
  181. * if requested.
  182. *
  183. * @param PropFind $propFind
  184. * @param \Sabre\DAV\INode $node
  185. * @return void
  186. */
  187. public function handleGetProperties(
  188. PropFind $propFind,
  189. \Sabre\DAV\INode $node
  190. ) {
  191. if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
  192. return;
  193. }
  194. // need prefetch ?
  195. if ($node instanceof \OCA\DAV\Connector\Sabre\Directory
  196. && $propFind->getDepth() !== 0
  197. && (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME))
  198. || !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME))
  199. )) {
  200. // note: pre-fetching only supported for depth <= 1
  201. $folderContent = $node->getChildren();
  202. $fileIds[] = (int)$node->getId();
  203. foreach ($folderContent as $info) {
  204. $fileIds[] = (int)$info->getId();
  205. }
  206. $tags = $this->getTagger()->getTagsForObjects($fileIds);
  207. if ($tags === false) {
  208. // the tags API returns false on error...
  209. $tags = array();
  210. }
  211. $this->cachedTags = $this->cachedTags + $tags;
  212. $emptyFileIds = array_diff($fileIds, array_keys($tags));
  213. // also cache the ones that were not found
  214. foreach ($emptyFileIds as $fileId) {
  215. $this->cachedTags[$fileId] = [];
  216. }
  217. }
  218. $tags = null;
  219. $isFav = null;
  220. $propFind->handle(self::TAGS_PROPERTYNAME, function() use ($tags, &$isFav, $node) {
  221. list($tags, $isFav) = $this->getTagsAndFav($node->getId());
  222. return new TagList($tags);
  223. });
  224. $propFind->handle(self::FAVORITE_PROPERTYNAME, function() use ($isFav, $node) {
  225. if (is_null($isFav)) {
  226. list(, $isFav) = $this->getTagsAndFav($node->getId());
  227. }
  228. return $isFav;
  229. });
  230. }
  231. /**
  232. * Updates tags and favorites properties, if applicable.
  233. *
  234. * @param string $path
  235. * @param PropPatch $propPatch
  236. *
  237. * @return void
  238. */
  239. public function handleUpdateProperties($path, PropPatch $propPatch) {
  240. $propPatch->handle(self::TAGS_PROPERTYNAME, function($tagList) use ($path) {
  241. $node = $this->tree->getNodeForPath($path);
  242. if (is_null($node)) {
  243. return 404;
  244. }
  245. $this->updateTags($node->getId(), $tagList->getTags());
  246. return true;
  247. });
  248. $propPatch->handle(self::FAVORITE_PROPERTYNAME, function($favState) use ($path) {
  249. $node = $this->tree->getNodeForPath($path);
  250. if (is_null($node)) {
  251. return 404;
  252. }
  253. if ((int)$favState === 1 || $favState === 'true') {
  254. $this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE);
  255. } else {
  256. $this->getTagger()->unTag($node->getId(), self::TAG_FAVORITE);
  257. }
  258. if (is_null($favState)) {
  259. // confirm deletion
  260. return 204;
  261. }
  262. return 200;
  263. });
  264. }
  265. }