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 | } |