Completed
Push — master ( cde7f5...cd12e2 )
by Joas
14:47
created

CommentNode::composeMentionsPropertyValue()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 13
nc 1
nop 0
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Vincent Petry <[email protected]>
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
24
namespace OCA\DAV\Comments;
25
26
27
use OCP\Comments\IComment;
28
use OCP\Comments\ICommentsManager;
29
use OCP\Comments\MessageTooLongException;
30
use OCP\ILogger;
31
use OCP\IUserManager;
32
use OCP\IUserSession;
33
use Sabre\DAV\Exception\BadRequest;
34
use Sabre\DAV\Exception\Forbidden;
35
use Sabre\DAV\Exception\MethodNotAllowed;
36
use Sabre\DAV\PropPatch;
37
38
class CommentNode implements \Sabre\DAV\INode, \Sabre\DAV\IProperties {
39
	const NS_OWNCLOUD = 'http://owncloud.org/ns';
40
41
	const PROPERTY_NAME_UNREAD = '{http://owncloud.org/ns}isUnread';
42
	const PROPERTY_NAME_MESSAGE = '{http://owncloud.org/ns}message';
43
	const PROPERTY_NAME_ACTOR_DISPLAYNAME = '{http://owncloud.org/ns}actorDisplayName';
44
	const PROPERTY_NAME_MENTIONS = '{http://owncloud.org/ns}mentions';
45
	const PROPERTY_NAME_MENTION = '{http://owncloud.org/ns}mention';
46
	const PROPERTY_NAME_MENTION_TYPE = '{http://owncloud.org/ns}mentionType';
47
	const PROPERTY_NAME_MENTION_ID = '{http://owncloud.org/ns}mentionId';
48
	const PROPERTY_NAME_MENTION_DISPLAYNAME = '{http://owncloud.org/ns}mentionDisplayName';
49
50
	/** @var  IComment */
51
	public $comment;
52
53
	/** @var ICommentsManager */
54
	protected $commentsManager;
55
56
	/** @var  ILogger */
57
	protected $logger;
58
59
	/** @var array list of properties with key being their name and value their setter */
60
	protected $properties = [];
61
62
	/** @var IUserManager */
63
	protected $userManager;
64
65
	/** @var IUserSession */
66
	protected $userSession;
67
68
	/**
69
	 * CommentNode constructor.
70
	 *
71
	 * @param ICommentsManager $commentsManager
72
	 * @param IComment $comment
73
	 * @param IUserManager $userManager
74
	 * @param IUserSession $userSession
75
	 * @param ILogger $logger
76
	 */
77
	public function __construct(
78
		ICommentsManager $commentsManager,
79
		IComment $comment,
80
		IUserManager $userManager,
81
		IUserSession $userSession,
82
		ILogger $logger
83
	) {
84
		$this->commentsManager = $commentsManager;
85
		$this->comment = $comment;
86
		$this->logger = $logger;
87
88
		$methods = get_class_methods($this->comment);
89
		$methods = array_filter($methods, function($name){
90
			return strpos($name, 'get') === 0;
91
		});
92
		foreach($methods as $getter) {
93
			if($getter === 'getMentions') {
94
				continue;	// special treatment
95
			}
96
			$name = '{'.self::NS_OWNCLOUD.'}' . lcfirst(substr($getter, 3));
97
			$this->properties[$name] = $getter;
98
		}
99
		$this->userManager = $userManager;
100
		$this->userSession = $userSession;
101
	}
102
103
	/**
104
	 * returns a list of all possible property names
105
	 *
106
	 * @return array
107
	 */
108
	static public function getPropertyNames() {
109
		return [
110
			'{http://owncloud.org/ns}id',
111
			'{http://owncloud.org/ns}parentId',
112
			'{http://owncloud.org/ns}topmostParentId',
113
			'{http://owncloud.org/ns}childrenCount',
114
			'{http://owncloud.org/ns}verb',
115
			'{http://owncloud.org/ns}actorType',
116
			'{http://owncloud.org/ns}actorId',
117
			'{http://owncloud.org/ns}creationDateTime',
118
			'{http://owncloud.org/ns}latestChildDateTime',
119
			'{http://owncloud.org/ns}objectType',
120
			'{http://owncloud.org/ns}objectId',
121
			// re-used property names are defined as constants
122
			self::PROPERTY_NAME_MESSAGE,
123
			self::PROPERTY_NAME_ACTOR_DISPLAYNAME,
124
			self::PROPERTY_NAME_UNREAD,
125
			self::PROPERTY_NAME_MENTIONS,
126
			self::PROPERTY_NAME_MENTION,
127
			self::PROPERTY_NAME_MENTION_TYPE,
128
			self::PROPERTY_NAME_MENTION_ID,
129
			self::PROPERTY_NAME_MENTION_DISPLAYNAME,
130
		];
131
	}
132
133
	protected function checkWriteAccessOnComment() {
134
		$user = $this->userSession->getUser();
135
		if(    $this->comment->getActorType() !== 'users'
136
			|| is_null($user)
137
			|| $this->comment->getActorId() !== $user->getUID()
138
		) {
139
			throw new Forbidden('Only authors are allowed to edit their comment.');
140
		}
141
	}
142
143
	/**
144
	 * Deleted the current node
145
	 *
146
	 * @return void
147
	 */
148
	function delete() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
149
		$this->checkWriteAccessOnComment();
150
		$this->commentsManager->delete($this->comment->getId());
151
	}
152
153
	/**
154
	 * Returns the name of the node.
155
	 *
156
	 * This is used to generate the url.
157
	 *
158
	 * @return string
159
	 */
160
	function getName() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
161
		return $this->comment->getId();
162
	}
163
164
	/**
165
	 * Renames the node
166
	 *
167
	 * @param string $name The new name
168
	 * @throws MethodNotAllowed
169
	 */
170
	function setName($name) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
171
		throw new MethodNotAllowed();
172
	}
173
174
	/**
175
	 * Returns the last modification time, as a unix timestamp
176
	 *
177
	 * @return int
178
	 */
179
	function getLastModified() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
180
		return null;
181
	}
182
183
	/**
184
	 * update the comment's message
185
	 *
186
	 * @param $propertyValue
187
	 * @return bool
188
	 * @throws BadRequest
189
	 * @throws \Exception
190
	 */
191
	public function updateComment($propertyValue) {
192
		$this->checkWriteAccessOnComment();
193
		try {
194
			$this->comment->setMessage($propertyValue);
195
			$this->commentsManager->save($this->comment);
196
			return true;
197
		} catch (\Exception $e) {
198
			$this->logger->logException($e, ['app' => 'dav/comments']);
199
			if($e instanceof MessageTooLongException) {
200
				$msg = 'Message exceeds allowed character limit of ';
201
				throw new BadRequest($msg . IComment::MAX_MESSAGE_LENGTH, 0, $e);
202
			}
203
			throw $e;
204
		}
205
	}
206
207
	/**
208
	 * Updates properties on this node.
209
	 *
210
	 * This method received a PropPatch object, which contains all the
211
	 * information about the update.
212
	 *
213
	 * To update specific properties, call the 'handle' method on this object.
214
	 * Read the PropPatch documentation for more information.
215
	 *
216
	 * @param PropPatch $propPatch
217
	 * @return void
218
	 */
219
	function propPatch(PropPatch $propPatch) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
220
		// other properties than 'message' are read only
221
		$propPatch->handle(self::PROPERTY_NAME_MESSAGE, [$this, 'updateComment']);
222
	}
223
224
	/**
225
	 * Returns a list of properties for this nodes.
226
	 *
227
	 * The properties list is a list of propertynames the client requested,
228
	 * encoded in clark-notation {xmlnamespace}tagname
229
	 *
230
	 * If the array is empty, it means 'all properties' were requested.
231
	 *
232
	 * Note that it's fine to liberally give properties back, instead of
233
	 * conforming to the list of requested properties.
234
	 * The Server class will filter out the extra.
235
	 *
236
	 * @param array $properties
237
	 * @return array
238
	 */
239
	function getProperties($properties) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
240
		$properties = array_keys($this->properties);
241
242
		$result = [];
243
		foreach($properties as $property) {
244
			$getter = $this->properties[$property];
245
			if(method_exists($this->comment, $getter)) {
246
				$result[$property] = $this->comment->$getter();
247
			}
248
		}
249
250
		if($this->comment->getActorType() === 'users') {
251
			$user = $this->userManager->get($this->comment->getActorId());
252
			$displayName = is_null($user) ? null : $user->getDisplayName();
253
			$result[self::PROPERTY_NAME_ACTOR_DISPLAYNAME] = $displayName;
254
		}
255
256
		$result[self::PROPERTY_NAME_MENTIONS] = $this->composeMentionsPropertyValue();
257
258
		$unread = null;
259
		$user =  $this->userSession->getUser();
260
		if(!is_null($user)) {
261
			$readUntil = $this->commentsManager->getReadMark(
262
				$this->comment->getObjectType(),
263
				$this->comment->getObjectId(),
264
				$user
265
			);
266
			if(is_null($readUntil)) {
267
				$unread = 'true';
268
			} else {
269
				$unread = $this->comment->getCreationDateTime() > $readUntil;
270
				// re-format for output
271
				$unread = $unread ? 'true' : 'false';
272
			}
273
		}
274
		$result[self::PROPERTY_NAME_UNREAD] = $unread;
275
276
		return $result;
277
	}
278
279
	/**
280
	 * transforms a mentions array as returned from IComment->getMentions to an
281
	 * array with DAV-compatible structure that can be assigned to the
282
	 * PROPERTY_NAME_MENTION property.
283
	 *
284
	 * @return array
285
	 */
286
	protected function composeMentionsPropertyValue() {
287
		return array_map(function($mention) {
288
			try {
289
				$displayName = $this->commentsManager->resolveDisplayName($mention['type'], $mention['id']);
290
			} catch (\OutOfBoundsException $e) {
291
				$this->logger->logException($e);
292
				// No displayname, upon client's discretion what to display.
293
				$displayName = '';
294
			}
295
296
			return [
297
				self::PROPERTY_NAME_MENTION => [
298
					self::PROPERTY_NAME_MENTION_TYPE        => $mention['type'],
299
					self::PROPERTY_NAME_MENTION_ID          => $mention['id'],
300
					self::PROPERTY_NAME_MENTION_DISPLAYNAME => $displayName,
301
				]
302
			];
303
		}, $this->comment->getMentions());
304
	}
305
}
306