Completed
Push — stable9 ( 3c64b7...7d6fa4 )
by Joas
9s
created

Data::get()   C

Complexity

Conditions 16
Paths 92

Size

Total Lines 80
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 41
CRAP Score 16.7933

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 80
ccs 41
cts 48
cp 0.8542
rs 5.0401
cc 16
eloc 48
nc 92
nop 9
crap 16.7933

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 67
	public function __construct(IManager $activityManager, IDBConnection $connection, IUserSession $userSession) {
55 67
		$this->activityManager = $activityManager;
56 67
		$this->connection = $connection;
57 67
		$this->userSession = $userSession;
58 67
	}
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 8
	public function getNotificationTypes(IL10N $l) {
71 8
		if (isset($this->notificationTypes[$l->getLanguageCode()])) {
72 1
			return $this->notificationTypes[$l->getLanguageCode()];
73
		}
74
75
		// Allow apps to add new notification types
76 8
		$notificationTypes = $this->activityManager->getNotificationTypes($l->getLanguageCode());
77 8
		$this->notificationTypes[$l->getLanguageCode()] = $notificationTypes;
78 8
		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' || $filter === 'all' && !$userSettings->getUserSetting($user, 'setting', 'self')) {
216 2
			$query->andWhere($query->expr()->neq('user', $query->createNamedParameter($user)));
217
218 9
		} else if ($filter === 'filter') {
219 2
			if (!$userSettings->getUserSetting($user, 'setting', 'self')) {
220 2
				$query->andWhere($query->expr()->neq('user', $query->createNamedParameter($user)));
221
			}
222
223 2
			$query->andWhere($query->expr()->eq('object_type', $query->createNamedParameter($objectType)));
224 2
			$query->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)));
225
		}
226
227 13
		list($condition, $params) = $this->activityManager->getQueryForFilter($filter);
228 13
		if (!is_null($condition)) {
229
			// Strip away ' and '
230
			$condition = substr($condition, 5);
231
232
			if (is_array($params)) {
233
				// Replace ? placeholders with named parameters
234
				foreach ($params as $param) {
235
					$pos = strpos($condition, '?');
236
					if ($pos !== false) {
237
						$condition = substr($condition, 0, $pos) . $query->createNamedParameter($param) . substr($condition, $pos + 1);
238
					}
239
				}
240
			}
241
242
			$query->andWhere($query->createFunction($condition));
243
		}
244
245
		/**
246
		 * Order and specify the offset
247
		 */
248 13
		$sqlSort = ($sort === 'asc') ? 'ASC' : 'DESC';
249 13
		$headers = $this->setOffsetFromSince($query, $user, $since, $sqlSort);
250 13
		$query->orderBy('timestamp', $sqlSort)
251 13
			->addOrderBy('activity_id', $sqlSort);
252
253 13
		$query->setMaxResults($limit + 1);
254
255 13
		$result = $query->execute();
256 13
		$hasMore = false;
257 13
		while ($row = $result->fetch()) {
258 12
			if ($limit === 0) {
259 10
				$hasMore = true;
260 10
				break;
261
			}
262 12
			$headers['X-Activity-Last-Given'] = (int) $row['activity_id'];
263 12
			$groupHelper->addActivity($row);
264 12
			$limit--;
265
		}
266 13
		$result->closeCursor();
267
268 13
		return ['data' => $groupHelper->getActivities(), 'has_more' => $hasMore, 'headers' => $headers];
269
	}
270
271
	/**
272
	 * @param IQueryBuilder $query
273
	 * @param string $user
274
	 * @param int $since
275
	 * @param string $sort
276
	 *
277
	 * @return array Headers that should be set on the response
278
	 *
279
	 * @throws \OutOfBoundsException If $since is not owned by $user
280
	 */
281 18
	protected function setOffsetFromSince(IQueryBuilder $query, $user, $since, $sort) {
282 18
		if ($since) {
283 5
			$queryBuilder = $this->connection->getQueryBuilder();
284 5
			$queryBuilder->select('*')
285 5
				->from('activity')
286 5
				->where($queryBuilder->expr()->eq('activity_id', $queryBuilder->createNamedParameter((int) $since)));
287 5
			$result = $queryBuilder->execute();
288 5
			$activity = $result->fetch();
289 5
			$result->closeCursor();
290
291 5
			if ($activity) {
292 4
				if ($activity['affecteduser'] !== $user) {
293 1
					throw new \OutOfBoundsException('Invalid since', 2);
294
				}
295 3
				$timestamp = (int) $activity['timestamp'];
296
297 3
				if ($sort === 'DESC') {
298 2
					$query->andWhere($query->expr()->lte('timestamp', $query->createNamedParameter($timestamp)));
299 2
					$query->andWhere($query->expr()->lt('activity_id', $query->createNamedParameter($since)));
300
				} else {
301 1
					$query->andWhere($query->expr()->gte('timestamp', $query->createNamedParameter($timestamp)));
302 1
					$query->andWhere($query->expr()->gt('activity_id', $query->createNamedParameter($since)));
303
				}
304 3
				return [];
305
			}
306
		}
307
308
		/**
309
		 * Couldn't find the since, so find the oldest one and set the header
310
		 */
311 14
		$fetchQuery = $this->connection->getQueryBuilder();
312 14
		$fetchQuery->select('activity_id')
313 14
			->from('activity')
314 14
			->where($fetchQuery->expr()->eq('affecteduser', $fetchQuery->createNamedParameter($user)))
315 14
			->orderBy('timestamp', $sort)
316 14
			->setMaxResults(1);
317 14
		$result = $fetchQuery->execute();
318 14
		$activity = $result->fetch();
319 14
		$result->closeCursor();
320
321 14
		if ($activity !== false) {
322
			return [
323 12
				'X-Activity-First-Known' => (int) $activity['activity_id'],
324
			];
325
		}
326
327 2
		return [];
328
	}
329
330
	/**
331
	 * Verify that the filter is valid
332
	 *
333
	 * @param string $filterValue
334
	 * @return string
335
	 */
336 6
	public function validateFilter($filterValue) {
337 6
		if (!isset($filterValue)) {
338 1
			return 'all';
339
		}
340
341
		switch ($filterValue) {
342 5
			case 'by':
343 4
			case 'self':
344 3
			case 'all':
345 2
			case 'filter':
346 3
				return $filterValue;
347
			default:
348 2
				if ($this->activityManager->isFilterValid($filterValue)) {
349 1
					return $filterValue;
350
				}
351 1
				return 'all';
352
		}
353
	}
354
355
	/**
356
	 * Delete old events
357
	 *
358
	 * @param int $expireDays Minimum 1 day
359
	 * @return null
360
	 */
361 2
	public function expire($expireDays = 365) {
362 2
		$ttl = (60 * 60 * 24 * max(1, $expireDays));
363
364 2
		$timelimit = time() - $ttl;
365 2
		$this->deleteActivities(array(
366 2
			'timestamp' => array($timelimit, '<'),
367
		));
368 2
	}
369
370
	/**
371
	 * Delete activities that match certain conditions
372
	 *
373
	 * @param array $conditions Array with conditions that have to be met
374
	 *                      'field' => 'value'  => `field` = 'value'
375
	 *    'field' => array('value', 'operator') => `field` operator 'value'
376
	 * @return null
377
	 */
378 12
	public function deleteActivities($conditions) {
379 12
		$sqlWhere = '';
380 12
		$sqlParameters = $sqlWhereList = array();
381 12
		foreach ($conditions as $column => $comparison) {
382 12
			$sqlWhereList[] = " `$column` " . ((is_array($comparison) && isset($comparison[1])) ? $comparison[1] : '=') . ' ? ';
383 12
			$sqlParameters[] = (is_array($comparison)) ? $comparison[0] : $comparison;
384
		}
385
386 12
		if (!empty($sqlWhereList)) {
387 12
			$sqlWhere = ' WHERE ' . implode(' AND ', $sqlWhereList);
388
		}
389
390 12
		$query = $this->connection->prepare(
391 12
			'DELETE FROM `*PREFIX*activity`' . $sqlWhere);
392 12
		$query->execute($sqlParameters);
393 12
	}
394
}
395