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')) { |
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')) { |
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
|
|||
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 |
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.