Completed
Push — master ( ad4761...22be7d )
by Sujith
11s
created

Data::storeMail()   B

Complexity

Conditions 3
Paths 2

Size

Total Lines 30
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 30
c 0
b 0
f 0
ccs 25
cts 25
cp 1
rs 8.8571
cc 3
eloc 23
nc 2
nop 2
crap 3
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 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 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 13
		} else if ($filter === 'by') {
216 2
			$query->andWhere($query->expr()->neq('user', $query->createNamedParameter($user)));
217
218 11
		} 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 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 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 12
		}
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 2
				} 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 1
		}
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 12
			];
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 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 12
		}
404
405 12
		if (!empty($sqlWhereList)) {
406 12
			$sqlWhere = ' WHERE ' . implode(' AND ', $sqlWhereList);
407 12
		}
408
409 12
		$query = $this->connection->prepare(
410 12
			'DELETE FROM `*PREFIX*activity`' . $sqlWhere);
411 12
		$query->execute($sqlParameters);
412 12
	}
413
}
414