Completed
Pull Request — stable9 (#58)
by Joas
02:24
created

Data   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 356
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 96.09%

Importance

Changes 0
Metric Value
wmc 45
lcom 1
cbo 2
dl 0
loc 356
ccs 172
cts 179
cp 0.9609
rs 8.3673
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getNotificationTypes() 0 10 2
B send() 0 44 3
B storeMail() 0 30 3
B validateFilter() 0 18 7
A expire() 0 8 1
B deleteActivities() 0 16 6
C get() 0 80 16
B setOffsetFromSince() 0 48 6

How to fix   Complexity   

Complex Class

Complex classes like Data often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Data, and based on these observations, apply Extract Interface, too.

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