This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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 Doctrine\DBAL\Platforms\MySqlPlatform; |
||
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 = []; |
||
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 | 7 | public function getNotificationTypes(IL10N $l) { |
|
72 | 7 | if (isset($this->notificationTypes[$l->getLanguageCode()])) { |
|
73 | 1 | return $this->notificationTypes[$l->getLanguageCode()]; |
|
74 | } |
||
75 | |||
76 | // Allow apps to add new notification types |
||
77 | 7 | $notificationTypes = $this->activityManager->getNotificationTypes($l->getLanguageCode()); |
|
78 | 7 | $this->notificationTypes[$l->getLanguageCode()] = $notificationTypes; |
|
79 | 7 | 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 | 11 | } elseif ($filter === 'by') { |
|
216 | 2 | $query->andWhere($query->expr()->neq('user', $query->createNamedParameter($user))); |
|
217 | 9 | View Code Duplication | } elseif ($filter === 'all' && !$userSettings->getUserSetting($user, 'setting', 'self')) { |
0 ignored issues
–
show
|
|||
218 | $query->andWhere($query->expr()->orX( |
||
219 | $query->expr()->neq('user', $query->createNamedParameter($user)), |
||
220 | $query->expr()->notIn('type', $query->createNamedParameter([ |
||
221 | 'file_created', |
||
222 | 'file_changed', |
||
223 | 'file_deleted', |
||
224 | 'file_restored', |
||
225 | ], IQueryBuilder::PARAM_STR_ARRAY)) |
||
226 | )); |
||
227 | 9 | } elseif ($filter === 'filter') { |
|
228 | 2 | View Code Duplication | if (!$userSettings->getUserSetting($user, 'setting', 'self')) { |
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
229 | 2 | $query->andWhere($query->expr()->orX( |
|
230 | 2 | $query->expr()->neq('user', $query->createNamedParameter($user)), |
|
231 | 2 | $query->expr()->notIn('type', $query->createNamedParameter([ |
|
232 | 2 | 'file_created', |
|
233 | 'file_changed', |
||
234 | 'file_deleted', |
||
235 | 'file_restored', |
||
236 | 2 | ], IQueryBuilder::PARAM_STR_ARRAY)) |
|
237 | )); |
||
238 | } |
||
239 | |||
240 | 2 | $query->andWhere($query->expr()->eq('object_type', $query->createNamedParameter($objectType))); |
|
241 | 2 | $query->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId))); |
|
242 | } |
||
243 | |||
244 | 13 | list($condition, $params) = $this->activityManager->getQueryForFilter($filter); |
|
245 | 13 | if ($condition !== null) { |
|
246 | // Strip away ' and ' |
||
247 | $condition = \substr($condition, 5); |
||
248 | |||
249 | if (\is_array($params)) { |
||
250 | // Replace ? placeholders with named parameters |
||
251 | foreach ($params as $param) { |
||
252 | $pos = \strpos($condition, '?'); |
||
253 | if ($pos !== false) { |
||
254 | $condition = \substr($condition, 0, $pos) . $query->createNamedParameter($param) . \substr($condition, $pos + 1); |
||
255 | } |
||
256 | } |
||
257 | } |
||
258 | |||
259 | $query->andWhere($query->createFunction($condition)); |
||
260 | } |
||
261 | |||
262 | /** |
||
263 | * Order and specify the offset |
||
264 | */ |
||
265 | 13 | $sqlSort = ($sort === 'asc') ? 'ASC' : 'DESC'; |
|
266 | 13 | $headers = $this->setOffsetFromSince($query, $user, $since, $sqlSort); |
|
267 | 13 | $query->orderBy('timestamp', $sqlSort) |
|
268 | 13 | ->addOrderBy('activity_id', $sqlSort); |
|
269 | |||
270 | 13 | $query->setMaxResults($limit + 1); |
|
271 | |||
272 | 13 | $result = $query->execute(); |
|
273 | 13 | $hasMore = false; |
|
274 | 13 | while ($row = $result->fetch()) { |
|
275 | 12 | if ($limit === 0) { |
|
276 | 10 | $hasMore = true; |
|
277 | 10 | break; |
|
278 | } |
||
279 | 12 | $headers['X-Activity-Last-Given'] = (int) $row['activity_id']; |
|
280 | 12 | $groupHelper->addActivity($row); |
|
281 | 12 | $limit--; |
|
282 | } |
||
283 | 13 | $result->closeCursor(); |
|
284 | |||
285 | 13 | return ['data' => $groupHelper->getActivities(), 'has_more' => $hasMore, 'headers' => $headers]; |
|
286 | } |
||
287 | |||
288 | /** |
||
289 | * @param IQueryBuilder $query |
||
290 | * @param string $user |
||
291 | * @param int $since |
||
292 | * @param string $sort |
||
293 | * |
||
294 | * @return array Headers that should be set on the response |
||
295 | * |
||
296 | * @throws \OutOfBoundsException If $since is not owned by $user |
||
297 | */ |
||
298 | 18 | protected function setOffsetFromSince(IQueryBuilder $query, $user, $since, $sort) { |
|
299 | 18 | if ($since) { |
|
300 | 5 | $queryBuilder = $this->connection->getQueryBuilder(); |
|
301 | 5 | $queryBuilder->select(['affecteduser', 'timestamp']) |
|
302 | 5 | ->from('activity') |
|
303 | 5 | ->where($queryBuilder->expr()->eq('activity_id', $queryBuilder->createNamedParameter((int) $since))); |
|
304 | 5 | $result = $queryBuilder->execute(); |
|
305 | 5 | $activity = $result->fetch(); |
|
306 | 5 | $result->closeCursor(); |
|
307 | |||
308 | 5 | if ($activity) { |
|
309 | 4 | if ($activity['affecteduser'] !== $user) { |
|
310 | 1 | throw new \OutOfBoundsException('Invalid since', 2); |
|
311 | } |
||
312 | 3 | $timestamp = (int) $activity['timestamp']; |
|
313 | |||
314 | 3 | if ($sort === 'DESC') { |
|
315 | 2 | $query->andWhere($query->expr()->lte('timestamp', $query->createNamedParameter($timestamp))); |
|
316 | 2 | $query->andWhere($query->expr()->lt('activity_id', $query->createNamedParameter($since))); |
|
317 | } else { |
||
318 | 1 | $query->andWhere($query->expr()->gte('timestamp', $query->createNamedParameter($timestamp))); |
|
319 | 1 | $query->andWhere($query->expr()->gt('activity_id', $query->createNamedParameter($since))); |
|
320 | } |
||
321 | 3 | return []; |
|
322 | } |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * Couldn't find the since, so find the oldest one and set the header |
||
327 | */ |
||
328 | 14 | $fetchQuery = $this->connection->getQueryBuilder(); |
|
329 | 14 | $fetchQuery->select('activity_id') |
|
330 | 14 | ->from('activity') |
|
331 | 14 | ->where($fetchQuery->expr()->eq('affecteduser', $fetchQuery->createNamedParameter($user))) |
|
332 | 14 | ->orderBy('timestamp', $sort) |
|
333 | 14 | ->setMaxResults(1); |
|
334 | 14 | $result = $fetchQuery->execute(); |
|
335 | 14 | $activity = $result->fetch(); |
|
336 | 14 | $result->closeCursor(); |
|
337 | |||
338 | 14 | if ($activity !== false) { |
|
339 | return [ |
||
340 | 12 | 'X-Activity-First-Known' => (int) $activity['activity_id'], |
|
341 | ]; |
||
342 | } |
||
343 | |||
344 | 2 | return []; |
|
345 | } |
||
346 | |||
347 | /** |
||
348 | * Verify that the filter is valid |
||
349 | * |
||
350 | * @param string $filterValue |
||
351 | * @return string |
||
352 | */ |
||
353 | 6 | public function validateFilter($filterValue) { |
|
354 | 6 | if (!isset($filterValue)) { |
|
355 | 1 | return 'all'; |
|
356 | } |
||
357 | |||
358 | switch ($filterValue) { |
||
359 | 5 | case 'by': |
|
360 | 4 | case 'self': |
|
361 | 3 | case 'all': |
|
362 | 2 | case 'filter': |
|
363 | 3 | return $filterValue; |
|
364 | default: |
||
365 | 2 | if ($this->activityManager->isFilterValid($filterValue)) { |
|
366 | 1 | return $filterValue; |
|
367 | } |
||
368 | 1 | return 'all'; |
|
369 | } |
||
370 | } |
||
371 | |||
372 | /** |
||
373 | * Delete old events |
||
374 | * |
||
375 | * @param int $expireDays Minimum 1 day |
||
376 | * @return null |
||
377 | */ |
||
378 | 2 | public function expire($expireDays = 365) { |
|
379 | 2 | $ttl = (60 * 60 * 24 * \max(1, $expireDays)); |
|
380 | |||
381 | 2 | $timelimit = \time() - $ttl; |
|
382 | 2 | $this->deleteActivities([ |
|
383 | 2 | 'timestamp' => [$timelimit, '<'], |
|
384 | ]); |
||
385 | 2 | } |
|
386 | |||
387 | /** |
||
388 | * Delete activities that match certain conditions |
||
389 | * |
||
390 | * @param array $conditions Array with conditions that have to be met |
||
391 | * 'field' => 'value' => `field` = 'value' |
||
392 | * 'field' => array('value', 'operator') => `field` operator 'value' |
||
393 | * @return null |
||
394 | */ |
||
395 | 14 | public function deleteActivities($conditions) { |
|
396 | 14 | $sqlWhere = ''; |
|
397 | 14 | $sqlParameters = $sqlWhereList = []; |
|
398 | 14 | foreach ($conditions as $column => $comparison) { |
|
399 | 14 | $sqlWhereList[] = " `$column` " . ((\is_array($comparison) && isset($comparison[1])) ? $comparison[1] : '=') . ' ? '; |
|
400 | 14 | $sqlParameters[] = (\is_array($comparison)) ? $comparison[0] : $comparison; |
|
401 | } |
||
402 | |||
403 | 14 | if (!empty($sqlWhereList)) { |
|
404 | 14 | $sqlWhere = ' WHERE ' . \implode(' AND ', $sqlWhereList); |
|
405 | } |
||
406 | |||
407 | // Add galera safe delete chunking if using mysql |
||
408 | // Stops us hitting wsrep_max_ws_rows when large row counts are deleted |
||
409 | 14 | if ($this->connection->getDatabasePlatform() instanceof MySqlPlatform) { |
|
0 ignored issues
–
show
The class
Doctrine\DBAL\Platforms\MySqlPlatform does not exist. Did you forget a USE statement, or did you not list all dependencies?
This error could be the result of: 1. Missing dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in if ($x instanceof DoesNotExist) {
// Do something.
}
If you have not tested against this specific condition, such errors might go unnoticed. ![]() |
|||
410 | // Then use chunked delete |
||
411 | 1 | $max = 100000; |
|
412 | 1 | $query = $this->connection->prepare( |
|
413 | 1 | 'DELETE FROM `*PREFIX*activity`' . $sqlWhere . " LIMIT " . $max); |
|
414 | do { |
||
415 | 1 | $query->execute($sqlParameters); |
|
416 | 1 | $deleted = $query->rowCount(); |
|
417 | 1 | } while ($deleted === $max); |
|
418 | } else { |
||
419 | // Dont use chunked delete - let the DB handle the large row count natively |
||
420 | 14 | $query = $this->connection->prepare( |
|
421 | 14 | 'DELETE FROM `*PREFIX*activity`' . $sqlWhere); |
|
422 | 14 | $query->execute($sqlParameters); |
|
423 | } |
||
424 | 14 | } |
|
425 | } |
||
426 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.