CustomPropertiesBackend.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Robin Appelman <robin@icewind.nl>
  6. * @author Thomas Müller <thomas.mueller@tmit.eu>
  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 OCA\DAV\Files;
  24. use OCP\IDBConnection;
  25. use OCP\IUser;
  26. use Sabre\DAV\PropertyStorage\Backend\BackendInterface;
  27. use Sabre\DAV\PropFind;
  28. use Sabre\DAV\PropPatch;
  29. use Sabre\DAV\Tree;
  30. class CustomPropertiesBackend implements BackendInterface {
  31. /**
  32. * Ignored properties
  33. *
  34. * @var array
  35. */
  36. private $ignoredProperties = array(
  37. '{DAV:}getcontentlength',
  38. '{DAV:}getcontenttype',
  39. '{DAV:}getetag',
  40. '{DAV:}quota-used-bytes',
  41. '{DAV:}quota-available-bytes',
  42. '{http://owncloud.org/ns}permissions',
  43. '{http://owncloud.org/ns}downloadURL',
  44. '{http://owncloud.org/ns}dDC',
  45. '{http://owncloud.org/ns}size',
  46. );
  47. /**
  48. * @var Tree
  49. */
  50. private $tree;
  51. /**
  52. * @var IDBConnection
  53. */
  54. private $connection;
  55. /**
  56. * @var string
  57. */
  58. private $user;
  59. /**
  60. * Properties cache
  61. *
  62. * @var array
  63. */
  64. private $cache = [];
  65. /**
  66. * @param Tree $tree node tree
  67. * @param IDBConnection $connection database connection
  68. * @param IUser $user owner of the tree and properties
  69. */
  70. public function __construct(
  71. Tree $tree,
  72. IDBConnection $connection,
  73. IUser $user) {
  74. $this->tree = $tree;
  75. $this->connection = $connection;
  76. $this->user = $user->getUID();
  77. }
  78. /**
  79. * Fetches properties for a path.
  80. *
  81. * @param string $path
  82. * @param PropFind $propFind
  83. * @return void
  84. */
  85. public function propFind($path, PropFind $propFind) {
  86. $requestedProps = $propFind->get404Properties();
  87. // these might appear
  88. $requestedProps = array_diff(
  89. $requestedProps,
  90. $this->ignoredProperties
  91. );
  92. if (empty($requestedProps)) {
  93. return;
  94. }
  95. $props = $this->getProperties($path, $requestedProps);
  96. foreach ($props as $propName => $propValue) {
  97. $propFind->set($propName, $propValue);
  98. }
  99. }
  100. /**
  101. * Updates properties for a path
  102. *
  103. * @param string $path
  104. * @param PropPatch $propPatch
  105. *
  106. * @return void
  107. */
  108. public function propPatch($path, PropPatch $propPatch) {
  109. $propPatch->handleRemaining(function($changedProps) use ($path) {
  110. return $this->updateProperties($path, $changedProps);
  111. });
  112. }
  113. /**
  114. * This method is called after a node is deleted.
  115. *
  116. * @param string $path path of node for which to delete properties
  117. */
  118. public function delete($path) {
  119. $statement = $this->connection->prepare(
  120. 'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
  121. );
  122. $statement->execute(array($this->user, $path));
  123. $statement->closeCursor();
  124. unset($this->cache[$path]);
  125. }
  126. /**
  127. * This method is called after a successful MOVE
  128. *
  129. * @param string $source
  130. * @param string $destination
  131. *
  132. * @return void
  133. */
  134. public function move($source, $destination) {
  135. $statement = $this->connection->prepare(
  136. 'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
  137. ' WHERE `userid` = ? AND `propertypath` = ?'
  138. );
  139. $statement->execute(array($destination, $this->user, $source));
  140. $statement->closeCursor();
  141. }
  142. /**
  143. * Returns a list of properties for this nodes.;
  144. * @param string $path
  145. * @param array $requestedProperties requested properties or empty array for "all"
  146. * @return array
  147. * @note The properties list is a list of propertynames the client
  148. * requested, encoded as xmlnamespace#tagName, for example:
  149. * http://www.example.org/namespace#author If the array is empty, all
  150. * properties should be returned
  151. */
  152. private function getProperties($path, array $requestedProperties) {
  153. if (isset($this->cache[$path])) {
  154. return $this->cache[$path];
  155. }
  156. // TODO: chunking if more than 1000 properties
  157. $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?';
  158. $whereValues = array($this->user, $path);
  159. $whereTypes = array(null, null);
  160. if (!empty($requestedProperties)) {
  161. // request only a subset
  162. $sql .= ' AND `propertyname` in (?)';
  163. $whereValues[] = $requestedProperties;
  164. $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
  165. }
  166. $result = $this->connection->executeQuery(
  167. $sql,
  168. $whereValues,
  169. $whereTypes
  170. );
  171. $props = [];
  172. while ($row = $result->fetch()) {
  173. $props[$row['propertyname']] = $row['propertyvalue'];
  174. }
  175. $result->closeCursor();
  176. $this->cache[$path] = $props;
  177. return $props;
  178. }
  179. /**
  180. * Update properties
  181. *
  182. * @param string $path node for which to update properties
  183. * @param array $properties array of properties to update
  184. *
  185. * @return bool
  186. */
  187. private function updateProperties($path, $properties) {
  188. $deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
  189. ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
  190. $insertStatement = 'INSERT INTO `*PREFIX*properties`' .
  191. ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
  192. $updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
  193. ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
  194. // TODO: use "insert or update" strategy ?
  195. $existing = $this->getProperties($path, array());
  196. $this->connection->beginTransaction();
  197. foreach ($properties as $propertyName => $propertyValue) {
  198. // If it was null, we need to delete the property
  199. if (is_null($propertyValue)) {
  200. if (array_key_exists($propertyName, $existing)) {
  201. $this->connection->executeUpdate($deleteStatement,
  202. array(
  203. $this->user,
  204. $path,
  205. $propertyName
  206. )
  207. );
  208. }
  209. } else {
  210. if (!array_key_exists($propertyName, $existing)) {
  211. $this->connection->executeUpdate($insertStatement,
  212. array(
  213. $this->user,
  214. $path,
  215. $propertyName,
  216. $propertyValue
  217. )
  218. );
  219. } else {
  220. $this->connection->executeUpdate($updateStatement,
  221. array(
  222. $propertyValue,
  223. $this->user,
  224. $path,
  225. $propertyName
  226. )
  227. );
  228. }
  229. }
  230. }
  231. $this->connection->commit();
  232. unset($this->cache[$path]);
  233. return true;
  234. }
  235. }