Completed
Pull Request — master (#610)
by Tom
124:16 queued 122:05
created

Data::get()   D

Complexity

Conditions 16
Paths 110

Size

Total Lines 99
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 53
CRAP Score 21.2661

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 99
ccs 53
cts 73
cp 0.726
rs 4.7112
cc 16
eloc 64
nc 110
nop 9
crap 21.2661

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 66
	public function __construct(IManager $activityManager, IDBConnection $connection, IUserSession $userSession) {
55 66
		$this->activityManager = $activityManager;
56 66
		$this->connection = $connection;
57 66
		$this->userSession = $userSession;
58 66
	}
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 3
	public function getNotificationTypes(IL10N $l) {
71 3
		if (isset($this->notificationTypes[$l->getLanguageCode()])) {
72 1
			return $this->notificationTypes[$l->getLanguageCode()];
73
		}
74
75
		// Allow apps to add new notification types
76 3
		$notificationTypes = $this->activityManager->getNotificationTypes($l->getLanguageCode());
77 3
		$this->notificationTypes[$l->getLanguageCode()] = $notificationTypes;
78 3
		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 3
			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 2
			])
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 2
			])
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 2
			])
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 2
			])
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 11
	public function get(GroupHelper $groupHelper, UserSettings $userSettings, $user, $since, $limit, $sort, $filter, $objectType = '', $objectId = 0) {
191
		// get current user
192 11
		if ($user === '') {
193 1
			throw new \OutOfBoundsException('Invalid user', 1);
194
		}
195 10
		$groupHelper->setUser($user);
196
197 10
		$enabledNotifications = $userSettings->getNotificationTypes($user, 'stream');
198 10
		$enabledNotifications = $this->activityManager->filterNotificationTypes($enabledNotifications, $filter);
199 10
		$enabledNotifications = array_unique($enabledNotifications);
200
201
		// We don't want to display any activities
202 10
		if (empty($enabledNotifications)) {
203 1
			throw new \BadMethodCallException('No settings enabled', 3);
204
		}
205
206 9
		$query = $this->connection->getQueryBuilder();
207 9
		$query->select('*')
208 9
			->from('activity');
209
210 9
		$query->where($query->expr()->eq('affecteduser', $query->createNamedParameter($user)))
211 9
			->andWhere($query->expr()->in('type', $query->createNamedParameter($enabledNotifications, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)));
212 9
		if ($filter === 'self') {
213 2
			$query->andWhere($query->expr()->eq('user', $query->createNamedParameter($user)));
214
215 9
		} else if ($filter === 'by') {
216 2
			$query->andWhere($query->expr()->neq('user', $query->createNamedParameter($user)));
217
218 7
		} 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 5
		} 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 2
						'file_changed',
236 2
						'file_deleted',
237 2
						'file_restored',
238 2
					], IQueryBuilder::PARAM_STR_ARRAY))
239 2
				));
240 2
			}
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 2
		}
245
246 9
		list($condition, $params) = $this->activityManager->getQueryForFilter($filter);
247 9
		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 9
		$sqlSort = ($sort === 'asc') ? 'ASC' : 'DESC';
268 9
		$headers = $this->setOffsetFromSince($query, $user, $since, $sqlSort);
269 9
		$query->orderBy('timestamp', $sqlSort)
270 9
			->addOrderBy('activity_id', $sqlSort);
271
272 9
		$query->setMaxResults($limit + 1);
273
274 9
		$result = $query->execute();
275 9
		$hasMore = false;
276 9
		while ($row = $result->fetch()) {
277 8
			if ($limit === 0) {
278 8
				$hasMore = true;
279 8
				break;
280
			}
281 8
			$headers['X-Activity-Last-Given'] = (int) $row['activity_id'];
282 8
			$groupHelper->addActivity($row);
283 8
			$limit--;
284 8
		}
285 9
		$result->closeCursor();
286
287 9
		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 14
	protected function setOffsetFromSince(IQueryBuilder $query, $user, $since, $sort) {
301 14
		if ($since) {
302 4
			$queryBuilder = $this->connection->getQueryBuilder();
303 4
			$queryBuilder->select(['affecteduser', 'timestamp'])
304 4
				->from('activity')
305 4
				->where($queryBuilder->expr()->eq('activity_id', $queryBuilder->createNamedParameter((int) $since)));
306 4
			$result = $queryBuilder->execute();
307 4
			$activity = $result->fetch();
308 4
			$result->closeCursor();
309
310 4
			if ($activity) {
311 3
				if ($activity['affecteduser'] !== $user) {
312 1
					throw new \OutOfBoundsException('Invalid since', 2);
313
				}
314 2
				$timestamp = (int) $activity['timestamp'];
315
316 2
				if ($sort === 'DESC') {
317 1
					$query->andWhere($query->expr()->lte('timestamp', $query->createNamedParameter($timestamp)));
318 1
					$query->andWhere($query->expr()->lt('activity_id', $query->createNamedParameter($since)));
319 1
				} 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 2
				return [];
324
			}
325 1
		}
326
327
		/**
328
		 * Couldn't find the since, so find the oldest one and set the header
329
		 */
330 11
		$fetchQuery = $this->connection->getQueryBuilder();
331 11
		$fetchQuery->select('activity_id')
332 11
			->from('activity')
333 11
			->where($fetchQuery->expr()->eq('affecteduser', $fetchQuery->createNamedParameter($user)))
334 11
			->orderBy('timestamp', $sort)
335 11
			->setMaxResults(1);
336 11
		$result = $fetchQuery->execute();
337 11
		$activity = $result->fetch();
338 11
		$result->closeCursor();
339
340 11
		if ($activity !== false) {
341
			return [
342 9
				'X-Activity-First-Known' => (int) $activity['activity_id'],
343 9
			];
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 5
			case 'self':
363 5
			case 'all':
364 5
			case 'filter':
365 3
				return $filterValue;
366 2
			default:
367 2
				if ($this->activityManager->isFilterValid($filterValue)) {
368 1
					return $filterValue;
369
				}
370 1
				return 'all';
371 2
		}
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 2
		));
387
	}
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 13
	public function deleteActivities($conditions) {
398 13
		$sqlWhere = '';
399 13
		$sqlParameters = $sqlWhereList = array();
400 13
		foreach ($conditions as $column => $comparison) {
401 13
			$sqlWhereList[] = " `$column` " . ((is_array($comparison) && isset($comparison[1])) ? $comparison[1] : '=') . ' ? ';
402 13
			$sqlParameters[] = (is_array($comparison)) ? $comparison[0] : $comparison;
403 13
		}
404
405 13
		if (!empty($sqlWhereList)) {
406 13
			$sqlWhere = ' WHERE ' . implode(' AND ', $sqlWhereList);
407 13
		}
408
409 13
		$max = 100000;
410 13
		$query = $this->connection->prepare(
411 13
			'DELETE FROM `*PREFIX*activity`' . $sqlWhere . " LIMIT " . $max);
412
		do {
413 13
			$query->execute($sqlParameters);
414 1
			$deleted = $query->rowCount();
415 1
		} while($deleted === $max);
416 1
	}
417
}
418