Completed
Push — master ( 2a7dd0...470ed5 )
by
unknown
02:46
created

Data::get()   D

Complexity

Conditions 16
Paths 110

Size

Total Lines 99
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 18.2673

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 99
ccs 46
cts 58
cp 0.7931
rs 4.7112
cc 16
eloc 64
nc 110
nop 9
crap 18.2673

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @author Frank Karlitschek <[email protected]>
4
 * @author Joas Schilling <[email protected]>
5
 * @author Thomas Müller <[email protected]>
6
 *
7
 * @copyright Copyright (c) 2016, ownCloud, Inc.
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\Activity;
25
26
use OCA\Activity\Exception\InvalidFilterException;
27
use OCP\Activity\IEvent;
28
use OCP\Activity\IExtension;
29
use OCP\Activity\IManager;
30
use OCP\DB\QueryBuilder\IQueryBuilder;
31
use OCP\IDBConnection;
32
use OCP\IL10N;
33
use OCP\IUser;
34
use OCP\IUserSession;
35
36
/**
37
 * @brief Class for managing the data in the activities
38
 */
39
class Data {
40
	/** @var IManager */
41
	protected $activityManager;
42
43
	/** @var IDBConnection */
44
	protected $connection;
45
46
	/** @var IUserSession */
47
	protected $userSession;
48
49
	/**
50
	 * @param IManager $activityManager
51
	 * @param IDBConnection $connection
52
	 * @param IUserSession $userSession
53
	 */
54 65
	public function __construct(IManager $activityManager, IDBConnection $connection, IUserSession $userSession) {
55 65
		$this->activityManager = $activityManager;
56 65
		$this->connection = $connection;
57 65
		$this->userSession = $userSession;
58 65
	}
59
60
	protected $notificationTypes = array();
61
62
	/**
63
	 * @param IL10N $l
64
	 * @return array Array "stringID of the type" => "translated string description for the setting"
65
	 * 				or Array "stringID of the type" => [
66
	 * 					'desc' => "translated string description for the setting"
67
	 * 					'methods' => [\OCP\Activity\IExtension::METHOD_*],
68
	 * 				]
69
	 */
70 7
	public function getNotificationTypes(IL10N $l) {
71 7
		if (isset($this->notificationTypes[$l->getLanguageCode()])) {
72 1
			return $this->notificationTypes[$l->getLanguageCode()];
73
		}
74
75
		// Allow apps to add new notification types
76 7
		$notificationTypes = $this->activityManager->getNotificationTypes($l->getLanguageCode());
77 7
		$this->notificationTypes[$l->getLanguageCode()] = $notificationTypes;
78 7
		return $notificationTypes;
79
	}
80
81
	/**
82
	 * Send an event into the activity stream
83
	 *
84
	 * @param IEvent $event
85
	 * @return bool
86
	 */
87 4
	public function send(IEvent $event) {
88 4
		if ($event->getAffectedUser() === '' || $event->getAffectedUser() === null) {
89 2
			return false;
90
		}
91
92
		// store in DB
93 2
		$queryBuilder = $this->connection->getQueryBuilder();
94 2
		$queryBuilder->insert('activity')
95 2
			->values([
96 2
				'app' => $queryBuilder->createParameter('app'),
97 2
				'subject' => $queryBuilder->createParameter('subject'),
98 2
				'subjectparams' => $queryBuilder->createParameter('subjectparams'),
99 2
				'message' => $queryBuilder->createParameter('message'),
100 2
				'messageparams' => $queryBuilder->createParameter('messageparams'),
101 2
				'file' => $queryBuilder->createParameter('object_name'),
102 2
				'link' => $queryBuilder->createParameter('link'),
103 2
				'user' => $queryBuilder->createParameter('user'),
104 2
				'affecteduser' => $queryBuilder->createParameter('affecteduser'),
105 2
				'timestamp' => $queryBuilder->createParameter('timestamp'),
106 2
				'priority' => $queryBuilder->createParameter('priority'),
107 2
				'type' => $queryBuilder->createParameter('type'),
108 2
				'object_type' => $queryBuilder->createParameter('object_type'),
109 2
				'object_id' => $queryBuilder->createParameter('object_id'),
110
			])
111 2
			->setParameters([
112 2
				'app' => $event->getApp(),
113 2
				'type' => $event->getType(),
114 2
				'affecteduser' => $event->getAffectedUser(),
115 2
				'user' => $event->getAuthor(),
116 2
				'timestamp' => (int) $event->getTimestamp(),
117 2
				'subject' => $event->getSubject(),
118 2
				'subjectparams' => json_encode($event->getSubjectParameters()),
119 2
				'message' => $event->getMessage(),
120 2
				'messageparams' => json_encode($event->getMessageParameters()),
121 2
				'priority' => IExtension::PRIORITY_MEDIUM,
122 2
				'object_type' => $event->getObjectType(),
123 2
				'object_id' => (int) $event->getObjectId(),
124 2
				'object_name' => $event->getObjectName(),
125 2
				'link' => $event->getLink(),
126
			])
127 2
			->execute();
128
129 2
		return true;
130
	}
131
132
	/**
133
	 * Send an event as email
134
	 *
135
	 * @param IEvent $event
136
	 * @param int    $latestSendTime Activity $timestamp + batch setting of $affectedUser
137
	 * @return bool
138
	 */
139 4
	public function storeMail(IEvent $event, $latestSendTime) {
140 4
		if ($event->getAffectedUser() === '' || $event->getAffectedUser() === null) {
141 2
			return false;
142
		}
143
144
		// store in DB
145 2
		$queryBuilder = $this->connection->getQueryBuilder();
146 2
		$queryBuilder->insert('activity_mq')
147 2
			->values([
148 2
				'amq_appid' => $queryBuilder->createParameter('app'),
149 2
				'amq_subject' => $queryBuilder->createParameter('subject'),
150 2
				'amq_subjectparams' => $queryBuilder->createParameter('subjectparams'),
151 2
				'amq_affecteduser' => $queryBuilder->createParameter('affecteduser'),
152 2
				'amq_timestamp' => $queryBuilder->createParameter('timestamp'),
153 2
				'amq_type' => $queryBuilder->createParameter('type'),
154 2
				'amq_latest_send' => $queryBuilder->createParameter('latest_send'),
155
			])
156 2
			->setParameters([
157 2
				'app' => $event->getApp(),
158 2
				'subject' => $event->getSubject(),
159 2
				'subjectparams' => json_encode($event->getSubjectParameters()),
160 2
				'affecteduser' => $event->getAffectedUser(),
161 2
				'timestamp' => (int) $event->getTimestamp(),
162 2
				'type' => $event->getType(),
163 2
				'latest_send' => $latestSendTime,
164
			])
165 2
			->execute();
166
167 2
		return true;
168
	}
169
170
	/**
171
	 * Read a list of events from the activity stream
172
	 *
173
	 * @param GroupHelper $groupHelper Allows activities to be grouped
174
	 * @param UserSettings $userSettings Gets the settings of the user
175
	 * @param string $user User for whom we display the stream
176
	 *
177
	 * @param int $since The integer ID of the last activity that has been seen.
178
	 * @param int $limit How many activities should be returned
179
	 * @param string $sort Should activities be given ascending or descending
180
	 *
181
	 * @param string $filter Filter the activities
182
	 * @param string $objectType Allows to filter the activities to a given object. May only appear together with $objectId
183
	 * @param int $objectId Allows to filter the activities to a given object. May only appear together with $objectType
184
	 *
185
	 * @return array
186
	 *
187
	 * @throws \OutOfBoundsException if the user (Code: 1) or the since (Code: 2) is invalid
188
	 * @throws \BadMethodCallException if the user has selected to display no types for this filter (Code: 3)
189
	 */
190 15
	public function get(GroupHelper $groupHelper, UserSettings $userSettings, $user, $since, $limit, $sort, $filter, $objectType = '', $objectId = 0) {
191
		// get current user
192 15
		if ($user === '') {
193 1
			throw new \OutOfBoundsException('Invalid user', 1);
194
		}
195 14
		$groupHelper->setUser($user);
196
197 14
		$enabledNotifications = $userSettings->getNotificationTypes($user, 'stream');
198 14
		$enabledNotifications = $this->activityManager->filterNotificationTypes($enabledNotifications, $filter);
199 14
		$enabledNotifications = array_unique($enabledNotifications);
200
201
		// We don't want to display any activities
202 14
		if (empty($enabledNotifications)) {
203 1
			throw new \BadMethodCallException('No settings enabled', 3);
204
		}
205
206 13
		$query = $this->connection->getQueryBuilder();
207 13
		$query->select('*')
208 13
			->from('activity');
209
210 13
		$query->where($query->expr()->eq('affecteduser', $query->createNamedParameter($user)))
211 13
			->andWhere($query->expr()->in('type', $query->createNamedParameter($enabledNotifications, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)));
212 13
		if ($filter === 'self') {
213 2
			$query->andWhere($query->expr()->eq('user', $query->createNamedParameter($user)));
214
215 11
		} else if ($filter === 'by') {
216 2
			$query->andWhere($query->expr()->neq('user', $query->createNamedParameter($user)));
217
218 9
		} else if ($filter === 'all' && !$userSettings->getUserSetting($user, 'setting', 'self')) {
219
			$query->andWhere($query->expr()->orX(
220
				$query->expr()->neq('user', $query->createNamedParameter($user)),
221
				$query->expr()->notIn('type', $query->createNamedParameter([
222
					'file_created',
223
					'file_changed',
224
					'file_deleted',
225
					'file_restored',
226
				], IQueryBuilder::PARAM_STR_ARRAY))
227
			));
228
229 9
		} else if ($filter === 'filter') {
230 2
			if (!$userSettings->getUserSetting($user, 'setting', 'self')) {
231 2
				$query->andWhere($query->expr()->orX(
232 2
					$query->expr()->neq('user', $query->createNamedParameter($user)),
233 2
					$query->expr()->notIn('type', $query->createNamedParameter([
234 2
						'file_created',
235
						'file_changed',
236
						'file_deleted',
237
						'file_restored',
238 2
					], IQueryBuilder::PARAM_STR_ARRAY))
239
				));
240
			}
241
242 2
			$query->andWhere($query->expr()->eq('object_type', $query->createNamedParameter($objectType)));
243 2
			$query->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)));
244
		}
245
246 13
		list($condition, $params) = $this->activityManager->getQueryForFilter($filter);
247 13
		if (!is_null($condition)) {
248
			// Strip away ' and '
249
			$condition = substr($condition, 5);
250
251
			if (is_array($params)) {
252
				// Replace ? placeholders with named parameters
253
				foreach ($params as $param) {
254
					$pos = strpos($condition, '?');
255
					if ($pos !== false) {
256
						$condition = substr($condition, 0, $pos) . $query->createNamedParameter($param) . substr($condition, $pos + 1);
257
					}
258
				}
259
			}
260
261
			$query->andWhere($query->createFunction($condition));
262
		}
263
264
		/**
265
		 * Order and specify the offset
266
		 */
267 13
		$sqlSort = ($sort === 'asc') ? 'ASC' : 'DESC';
268 13
		$headers = $this->setOffsetFromSince($query, $user, $since, $sqlSort);
269 13
		$query->orderBy('timestamp', $sqlSort)
270 13
			->addOrderBy('activity_id', $sqlSort);
271
272 13
		$query->setMaxResults($limit + 1);
273
274 13
		$result = $query->execute();
275 13
		$hasMore = false;
276 13
		while ($row = $result->fetch()) {
277 12
			if ($limit === 0) {
278 10
				$hasMore = true;
279 10
				break;
280
			}
281 12
			$headers['X-Activity-Last-Given'] = (int) $row['activity_id'];
282 12
			$groupHelper->addActivity($row);
283 12
			$limit--;
284
		}
285 13
		$result->closeCursor();
286
287 13
		return ['data' => $groupHelper->getActivities(), 'has_more' => $hasMore, 'headers' => $headers];
288
	}
289
290
	/**
291
	 * @param IQueryBuilder $query
292
	 * @param string $user
293
	 * @param int $since
294
	 * @param string $sort
295
	 *
296
	 * @return array Headers that should be set on the response
297
	 *
298
	 * @throws \OutOfBoundsException If $since is not owned by $user
299
	 */
300 18
	protected function setOffsetFromSince(IQueryBuilder $query, $user, $since, $sort) {
301 18
		if ($since) {
302 5
			$queryBuilder = $this->connection->getQueryBuilder();
303 5
			$queryBuilder->select(['affecteduser', 'timestamp'])
304 5
				->from('activity')
305 5
				->where($queryBuilder->expr()->eq('activity_id', $queryBuilder->createNamedParameter((int) $since)));
306 5
			$result = $queryBuilder->execute();
307 5
			$activity = $result->fetch();
308 5
			$result->closeCursor();
309
310 5
			if ($activity) {
311 4
				if ($activity['affecteduser'] !== $user) {
312 1
					throw new \OutOfBoundsException('Invalid since', 2);
313
				}
314 3
				$timestamp = (int) $activity['timestamp'];
315
316 3
				if ($sort === 'DESC') {
317 2
					$query->andWhere($query->expr()->lte('timestamp', $query->createNamedParameter($timestamp)));
318 2
					$query->andWhere($query->expr()->lt('activity_id', $query->createNamedParameter($since)));
319
				} else {
320 1
					$query->andWhere($query->expr()->gte('timestamp', $query->createNamedParameter($timestamp)));
321 1
					$query->andWhere($query->expr()->gt('activity_id', $query->createNamedParameter($since)));
322
				}
323 3
				return [];
324
			}
325
		}
326
327
		/**
328
		 * Couldn't find the since, so find the oldest one and set the header
329
		 */
330 14
		$fetchQuery = $this->connection->getQueryBuilder();
331 14
		$fetchQuery->select('activity_id')
332 14
			->from('activity')
333 14
			->where($fetchQuery->expr()->eq('affecteduser', $fetchQuery->createNamedParameter($user)))
334 14
			->orderBy('timestamp', $sort)
335 14
			->setMaxResults(1);
336 14
		$result = $fetchQuery->execute();
337 14
		$activity = $result->fetch();
338 14
		$result->closeCursor();
339
340 14
		if ($activity !== false) {
341
			return [
342 12
				'X-Activity-First-Known' => (int) $activity['activity_id'],
343
			];
344
		}
345
346 2
		return [];
347
	}
348
349
	/**
350
	 * Verify that the filter is valid
351
	 *
352
	 * @param string $filterValue
353
	 * @return string
354
	 */
355 6
	public function validateFilter($filterValue) {
356 6
		if (!isset($filterValue)) {
357 1
			return 'all';
358
		}
359
360
		switch ($filterValue) {
361 5
			case 'by':
362 4
			case 'self':
363 3
			case 'all':
364 2
			case 'filter':
365 3
				return $filterValue;
366
			default:
367 2
				if ($this->activityManager->isFilterValid($filterValue)) {
368 1
					return $filterValue;
369
				}
370 1
				return 'all';
371
		}
372
	}
373
374
	/**
375
	 * Delete old events
376
	 *
377
	 * @param int $expireDays Minimum 1 day
378
	 * @return null
379
	 */
380 2
	public function expire($expireDays = 365) {
381 2
		$ttl = (60 * 60 * 24 * max(1, $expireDays));
382
383 2
		$timelimit = time() - $ttl;
384 2
		$this->deleteActivities(array(
385 2
			'timestamp' => array($timelimit, '<'),
386
		));
387 2
	}
388
389
	/**
390
	 * Delete activities that match certain conditions
391
	 *
392
	 * @param array $conditions Array with conditions that have to be met
393
	 *                      'field' => 'value'  => `field` = 'value'
394
	 *    'field' => array('value', 'operator') => `field` operator 'value'
395
	 * @return null
396
	 */
397 12
	public function deleteActivities($conditions) {
398 12
		$sqlWhere = '';
399 12
		$sqlParameters = $sqlWhereList = array();
400 12
		foreach ($conditions as $column => $comparison) {
401 12
			$sqlWhereList[] = " `$column` " . ((is_array($comparison) && isset($comparison[1])) ? $comparison[1] : '=') . ' ? ';
402 12
			$sqlParameters[] = (is_array($comparison)) ? $comparison[0] : $comparison;
403
		}
404
405 12
		if (!empty($sqlWhereList)) {
406 12
			$sqlWhere = ' WHERE ' . implode(' AND ', $sqlWhereList);
407
		}
408
409 12
		$query = $this->connection->prepare(
410 12
			'DELETE FROM `*PREFIX*activity`' . $sqlWhere);
411 12
		$query->execute($sqlParameters);
412 12
	}
413
}
414