Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
82.24% |
88 / 107 |
|
44.44% |
4 / 9 |
CRAP | |
0.00% |
0 / 1 |
UserLastActivityLogStorage | |
82.24% |
88 / 107 |
|
44.44% |
4 / 9 |
33.71 | |
0.00% |
0 / 1 |
log | |
92.86% |
13 / 14 |
|
0.00% |
0 / 1 |
2.00 | |||
find | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
6.03 | |||
count | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
4 | |||
addFilter | |
88.89% |
16 / 18 |
|
0.00% |
0 / 1 |
5.03 | |||
getColumnNames | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
getPersistence | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getQueryBuilder | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
install | |
92.86% |
26 / 28 |
|
0.00% |
0 / 1 |
3.00 | |||
catchEvent | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
42 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU General Public License |
6 | * as published by the Free Software Foundation; under version 2 |
7 | * of the License (non-upgradable). |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * |
18 | * Copyright (c) 2017 (original work) Open Assessment Technologies SA; |
19 | */ |
20 | |
21 | namespace oat\taoEventLog\model\userLastActivityLog\rds; |
22 | |
23 | use common_exception_Error; |
24 | use common_persistence_Persistence; |
25 | use common_persistence_SqlPersistence; |
26 | use common_report_Report; |
27 | use common_session_SessionManager; |
28 | use oat\oatbox\user\User; |
29 | use oat\taoEventLog\model\RdsLogIterator; |
30 | use oat\taoEventLog\model\userLastActivityLog\UserLastActivityLog; |
31 | use oat\oatbox\service\ConfigurableService; |
32 | use oat\oatbox\event\Event; |
33 | use Doctrine\DBAL\Schema\SchemaException; |
34 | use Doctrine\DBAL\Query\QueryBuilder; |
35 | use GuzzleHttp\Psr7\ServerRequest; |
36 | |
37 | /** |
38 | * Class UserLastActivityLogStorage |
39 | * @package oat\taoEventLog\model\userLastActivityLog\rds |
40 | * @author Aleh Hutnikau, <hutnikau@1pt.com> |
41 | */ |
42 | class UserLastActivityLogStorage extends ConfigurableService implements UserLastActivityLog |
43 | { |
44 | public const OPTION_PERSISTENCE = 'persistence_id'; |
45 | public const TABLE_NAME = 'user_last_activity_log'; |
46 | |
47 | public const COLUMN_USER_ID = self::USER_ID; |
48 | public const COLUMN_USER_ROLES = self::USER_ROLES; |
49 | public const COLUMN_ACTION = self::ACTION; |
50 | public const COLUMN_EVENT_TIME = self::EVENT_TIME; |
51 | public const COLUMN_DETAILS = self::DETAILS; |
52 | |
53 | /** Threshold in seconds */ |
54 | public const OPTION_ACTIVE_USER_THRESHOLD = 'active_user_threshold'; |
55 | |
56 | public const PHP_SESSION_LAST_ACTIVITY = 'tao_user_last_activity_timestamp'; |
57 | |
58 | /** |
59 | * @inheritdoc |
60 | */ |
61 | public function log(User $user, $action, array $details = []) |
62 | { |
63 | $userId = $user->getIdentifier(); |
64 | if ($userId === null) { |
65 | $userId = get_class($user); |
66 | } |
67 | |
68 | $data = [ |
69 | self::USER_ID => $userId, |
70 | self::USER_ROLES => ',' . implode(',', $user->getRoles()) . ',', |
71 | self::COLUMN_ACTION => strval($action), |
72 | self::COLUMN_EVENT_TIME => microtime(true), |
73 | self::COLUMN_DETAILS => json_encode($details), |
74 | ]; |
75 | $this->getPersistence()->exec( |
76 | 'DELETE FROM ' . self::TABLE_NAME . ' WHERE ' . self::USER_ID . ' = \'' . $userId . '\'' |
77 | ); |
78 | $this->getPersistence()->insert(self::TABLE_NAME, $data); |
79 | } |
80 | |
81 | /** |
82 | * @inheritdoc |
83 | */ |
84 | public function find(array $filters = [], array $options = []) |
85 | { |
86 | $queryBuilder = $this->getQueryBuilder(); |
87 | $queryBuilder->select('*'); |
88 | if (isset($options['limit'])) { |
89 | $queryBuilder->setMaxResults(intval($options['limit'])); |
90 | } |
91 | if (isset($options['offset'])) { |
92 | $queryBuilder->setFirstResult(intval($options['offset'])); |
93 | } |
94 | if (isset($options['group']) && in_array($options['group'], $this->getColumnNames())) { |
95 | $queryBuilder->groupBy($options['group']); |
96 | } |
97 | |
98 | foreach ($filters as $filter) { |
99 | $this->addFilter($queryBuilder, $filter); |
100 | } |
101 | return new RdsLogIterator($this->getPersistence(), $queryBuilder); |
102 | } |
103 | |
104 | /** |
105 | * @inheritdoc |
106 | */ |
107 | public function count(array $filters = [], array $options = []) |
108 | { |
109 | $queryBuilder = $this->getQueryBuilder(); |
110 | $queryBuilder->select('user_id'); |
111 | |
112 | foreach ($filters as $filter) { |
113 | $this->addFilter($queryBuilder, $filter); |
114 | } |
115 | if (isset($options['group']) && in_array($options['group'], $this->getColumnNames())) { |
116 | $queryBuilder->select($options['group']); |
117 | $queryBuilder->groupBy($options['group']); |
118 | } |
119 | |
120 | $stmt = $this->getPersistence()->query( |
121 | 'SELECT count(*) as count FROM (' . $queryBuilder->getSQL() . ') as group_q', |
122 | $queryBuilder->getParameters() |
123 | ); |
124 | $data = $stmt->fetch(\PDO::FETCH_ASSOC); |
125 | return intval($data['count']); |
126 | } |
127 | |
128 | /** |
129 | * @param QueryBuilder $queryBuilder |
130 | * @param array $filter |
131 | */ |
132 | private function addFilter(QueryBuilder $queryBuilder, array $filter) |
133 | { |
134 | $colName = strtolower($filter[0]); |
135 | $operation = strtolower($filter[1]); |
136 | $val = $filter[2]; |
137 | $val2 = isset($filter[3]) ? $filter[3] : null; |
138 | |
139 | if (!in_array($colName, $this->getColumnNames())) { |
140 | return; |
141 | } |
142 | |
143 | if (!in_array($operation, ['<', '>', '<>', '<=', '>=', '=', 'between', 'like'])) { |
144 | return; |
145 | } |
146 | $params = []; |
147 | if ($operation === 'between') { |
148 | $condition = "r.$colName between ? AND ?"; |
149 | $params[] = $val; |
150 | $params[] = $val2; |
151 | } else { |
152 | $condition = "r.$colName $operation ?"; |
153 | $params[] = $val; |
154 | } |
155 | |
156 | $queryBuilder->andWhere($condition); |
157 | |
158 | $params = array_merge($queryBuilder->getParameters(), $params); |
159 | $queryBuilder->setParameters($params); |
160 | } |
161 | |
162 | /** |
163 | * @return array |
164 | */ |
165 | private function getColumnNames() |
166 | { |
167 | return [ |
168 | self::USER_ID, |
169 | self::USER_ROLES, |
170 | self::COLUMN_ACTION, |
171 | self::COLUMN_EVENT_TIME, |
172 | self::COLUMN_DETAILS, |
173 | ]; |
174 | } |
175 | |
176 | /** |
177 | * @return common_persistence_SqlPersistence |
178 | * @throws |
179 | */ |
180 | private function getPersistence() |
181 | { |
182 | $persistenceManager = $this->getServiceManager()->get(\common_persistence_Manager::SERVICE_ID); |
183 | return $persistenceManager->getPersistenceById($this->getOption(self::OPTION_PERSISTENCE)); |
184 | } |
185 | |
186 | /** |
187 | * @return \Doctrine\DBAL\Query\QueryBuilder |
188 | * @throws |
189 | */ |
190 | private function getQueryBuilder() |
191 | { |
192 | return $this->getPersistence()->getPlatForm()->getQueryBuilder()->from(self::TABLE_NAME, 'r'); |
193 | } |
194 | |
195 | /** |
196 | * Initialize log storage |
197 | * |
198 | * @param common_persistence_Persistence $persistence |
199 | * @return common_report_Report |
200 | */ |
201 | public static function install($persistence) |
202 | { |
203 | $schemaManager = $persistence->getDriver()->getSchemaManager(); |
204 | $schema = $schemaManager->createSchema(); |
205 | $fromSchema = clone $schema; |
206 | |
207 | try { |
208 | $table = $schema->createTable(self::TABLE_NAME); |
209 | $table->addOption('engine', 'InnoDB'); |
210 | $table->addColumn(static::COLUMN_USER_ID, "string", ["length" => 255]); |
211 | $table->addColumn(static::COLUMN_USER_ROLES, "text", ["notnull" => true]); |
212 | $table->addColumn(static::COLUMN_ACTION, "string", ["notnull" => false, "length" => 4096]); |
213 | $table->addColumn( |
214 | static::COLUMN_EVENT_TIME, |
215 | 'decimal', |
216 | ['precision' => 14, 'scale' => 4, "notnull" => true] |
217 | ); |
218 | $table->addColumn(static::COLUMN_DETAILS, "text", ["notnull" => false]); |
219 | |
220 | $table->addIndex([static::COLUMN_USER_ID], 'IDX_' . static::TABLE_NAME . '_' . static::COLUMN_USER_ID); |
221 | $table->addIndex( |
222 | [static::COLUMN_EVENT_TIME], |
223 | 'IDX_' . static::TABLE_NAME . '_' . static::COLUMN_EVENT_TIME |
224 | ); |
225 | } catch (SchemaException $e) { |
226 | \common_Logger::i('Database Schema already up to date.'); |
227 | } |
228 | |
229 | $queries = $persistence->getPlatform()->getMigrateSchemaSql($fromSchema, $schema); |
230 | foreach ($queries as $query) { |
231 | $persistence->exec($query); |
232 | } |
233 | |
234 | return new common_report_Report( |
235 | common_report_Report::TYPE_SUCCESS, |
236 | __('User activity log successfully registered.') |
237 | ); |
238 | } |
239 | |
240 | /** |
241 | * @param Event $event |
242 | * @throws common_exception_Error |
243 | */ |
244 | public function catchEvent(Event $event) |
245 | { |
246 | if (common_session_SessionManager::isAnonymous()) { |
247 | return; |
248 | } |
249 | |
250 | $phpSession = \PHPSession::singleton(); |
251 | $lastStoredActivity = null; |
252 | if ($phpSession->hasAttribute(self::PHP_SESSION_LAST_ACTIVITY)) { |
253 | $lastStoredActivity = $phpSession->getAttribute(self::PHP_SESSION_LAST_ACTIVITY); |
254 | } |
255 | |
256 | $threshold = $this->getOption(self::OPTION_ACTIVE_USER_THRESHOLD) ?: 0; |
257 | |
258 | $currentTime = microtime(true); |
259 | if (!$lastStoredActivity || $currentTime > ($lastStoredActivity + $threshold)) { |
260 | $user = common_session_SessionManager::getSession()->getUser(); |
261 | $request = ServerRequest::fromGlobals(); |
262 | $phpSession->setAttribute(self::PHP_SESSION_LAST_ACTIVITY, $currentTime); |
263 | /** @var UserActivityLogStorage $userActivityLog */ |
264 | $this->log($user, $request->getUri()); |
265 | } |
266 | } |
267 | } |