Completed
Push — master ( 55c94c...51bcb0 )
by Morris
13:33
created

CustomPropertiesBackend::move()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 8
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 2
dl 8
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2017, Georg Ehrke <[email protected]>
5
 *
6
 * @author Robin Appelman <[email protected]>
7
 * @author Thomas Müller <[email protected]>
8
 * @author Georg Ehrke <[email protected]>
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
26
namespace OCA\DAV\DAV;
27
28
use OCP\IDBConnection;
29
use OCP\IUser;
30
use Sabre\DAV\PropertyStorage\Backend\BackendInterface;
31
use Sabre\DAV\PropFind;
32
use Sabre\DAV\PropPatch;
33
use Sabre\DAV\Tree;
34
35
class CustomPropertiesBackend implements BackendInterface {
36
37
	/**
38
	 * Ignored properties
39
	 *
40
	 * @var array
41
	 */
42
	private $ignoredProperties = array(
43
		'{DAV:}getcontentlength',
44
		'{DAV:}getcontenttype',
45
		'{DAV:}getetag',
46
		'{DAV:}quota-used-bytes',
47
		'{DAV:}quota-available-bytes',
48
		'{http://owncloud.org/ns}permissions',
49
		'{http://owncloud.org/ns}downloadURL',
50
		'{http://owncloud.org/ns}dDC',
51
		'{http://owncloud.org/ns}size',
52
	);
53
54
	/**
55
	 * @var Tree
56
	 */
57
	private $tree;
58
59
	/**
60
	 * @var IDBConnection
61
	 */
62
	private $connection;
63
64
	/**
65
	 * @var string
66
	 */
67
	private $user;
68
69
	/**
70
	 * Properties cache
71
	 *
72
	 * @var array
73
	 */
74
	private $cache = [];
75
76
	/**
77
	 * @param Tree $tree node tree
78
	 * @param IDBConnection $connection database connection
79
	 * @param IUser $user owner of the tree and properties
80
	 */
81 View Code Duplication
	public function __construct(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
82
		Tree $tree,
83
		IDBConnection $connection,
84
		IUser $user) {
85
		$this->tree = $tree;
86
		$this->connection = $connection;
87
		$this->user = $user->getUID();
88
	}
89
90
	/**
91
	 * Fetches properties for a path.
92
	 *
93
	 * @param string $path
94
	 * @param PropFind $propFind
95
	 * @return void
96
	 */
97
	public function propFind($path, PropFind $propFind) {
98
99
		$requestedProps = $propFind->get404Properties();
100
101
		// these might appear
102
		$requestedProps = array_diff(
103
			$requestedProps,
104
			$this->ignoredProperties
105
		);
106
107
		// substr of calendars/ => path is inside the CalDAV component
108
		// two '/' => this a calendar (no calendar-home nor calendar object)
109
		if (substr($path, 0, 10) === 'calendars/' && substr_count($path, '/') === 2) {
110
			$allRequestedProps = $propFind->getRequestedProperties();
111
			$customPropertiesForShares = [
112
				'{DAV:}displayname',
113
				'{urn:ietf:params:xml:ns:caldav}calendar-description',
114
				'{urn:ietf:params:xml:ns:caldav}calendar-timezone',
115
				'{http://apple.com/ns/ical/}calendar-order',
116
				'{http://apple.com/ns/ical/}calendar-color',
117
				'{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp',
118
			];
119
120
			foreach ($customPropertiesForShares as $customPropertyForShares) {
121
				if (in_array($customPropertyForShares, $allRequestedProps)) {
122
					$requestedProps[] = $customPropertyForShares;
123
				}
124
			}
125
		}
126
127
		if (empty($requestedProps)) {
128
			return;
129
		}
130
131
		$props = $this->getProperties($path, $requestedProps);
132
		foreach ($props as $propName => $propValue) {
133
			$propFind->set($propName, $propValue);
134
		}
135
	}
136
137
	/**
138
	 * Updates properties for a path
139
	 *
140
	 * @param string $path
141
	 * @param PropPatch $propPatch
142
	 *
143
	 * @return void
144
	 */
145
	public function propPatch($path, PropPatch $propPatch) {
146
		$propPatch->handleRemaining(function($changedProps) use ($path) {
147
			return $this->updateProperties($path, $changedProps);
148
		});
149
	}
150
151
	/**
152
	 * This method is called after a node is deleted.
153
	 *
154
	 * @param string $path path of node for which to delete properties
155
	 */
156 View Code Duplication
	public function delete($path) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
157
		$statement = $this->connection->prepare(
158
			'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
159
		);
160
		$statement->execute(array($this->user, $path));
161
		$statement->closeCursor();
162
163
		unset($this->cache[$path]);
164
	}
165
166
	/**
167
	 * This method is called after a successful MOVE
168
	 *
169
	 * @param string $source
170
	 * @param string $destination
171
	 *
172
	 * @return void
173
	 */
174 View Code Duplication
	public function move($source, $destination) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
175
		$statement = $this->connection->prepare(
176
			'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
177
			' WHERE `userid` = ? AND `propertypath` = ?'
178
		);
179
		$statement->execute(array($destination, $this->user, $source));
180
		$statement->closeCursor();
181
	}
182
183
	/**
184
	 * Returns a list of properties for this nodes.;
185
	 * @param string $path
186
	 * @param array $requestedProperties requested properties or empty array for "all"
187
	 * @return array
188
	 * @note The properties list is a list of propertynames the client
189
	 * requested, encoded as xmlnamespace#tagName, for example:
190
	 * http://www.example.org/namespace#author If the array is empty, all
191
	 * properties should be returned
192
	 */
193 View Code Duplication
	private function getProperties($path, array $requestedProperties) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
194
		if (isset($this->cache[$path])) {
195
			return $this->cache[$path];
196
		}
197
198
		// TODO: chunking if more than 1000 properties
199
		$sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?';
200
201
		$whereValues = array($this->user, $path);
202
		$whereTypes = array(null, null);
203
204
		if (!empty($requestedProperties)) {
205
			// request only a subset
206
			$sql .= ' AND `propertyname` in (?)';
207
			$whereValues[] = $requestedProperties;
208
			$whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
209
		}
210
211
		$result = $this->connection->executeQuery(
212
			$sql,
213
			$whereValues,
0 ignored issues
show
Documentation introduced by
$whereValues is of type array<integer,string|array>, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
214
			$whereTypes
215
		);
216
217
		$props = [];
218
		while ($row = $result->fetch()) {
219
			$props[$row['propertyname']] = $row['propertyvalue'];
220
		}
221
222
		$result->closeCursor();
223
224
		$this->cache[$path] = $props;
225
		return $props;
226
	}
227
228
	/**
229
	 * Update properties
230
	 *
231
	 * @param string $path node for which to update properties
232
	 * @param array $properties array of properties to update
233
	 *
234
	 * @return bool
235
	 */
236 View Code Duplication
	private function updateProperties($path, $properties) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
237
238
		$deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
239
			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
240
241
		$insertStatement = 'INSERT INTO `*PREFIX*properties`' .
242
			' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
243
244
		$updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
245
			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
246
247
		// TODO: use "insert or update" strategy ?
248
		$existing = $this->getProperties($path, array());
249
		$this->connection->beginTransaction();
250
		foreach ($properties as $propertyName => $propertyValue) {
251
			// If it was null, we need to delete the property
252
			if (is_null($propertyValue)) {
253
				if (array_key_exists($propertyName, $existing)) {
254
					$this->connection->executeUpdate($deleteStatement,
255
						array(
256
							$this->user,
257
							$path,
258
							$propertyName
259
						)
260
					);
261
				}
262
			} else {
263
				if (!array_key_exists($propertyName, $existing)) {
264
					$this->connection->executeUpdate($insertStatement,
265
						array(
266
							$this->user,
267
							$path,
268
							$propertyName,
269
							$propertyValue
270
						)
271
					);
272
				} else {
273
					$this->connection->executeUpdate($updateStatement,
274
						array(
275
							$propertyValue,
276
							$this->user,
277
							$path,
278
							$propertyName
279
						)
280
					);
281
				}
282
			}
283
		}
284
285
		$this->connection->commit();
286
		unset($this->cache[$path]);
287
288
		return true;
289
	}
290
291
}
292