Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3893ed2231
|
||
|
|
2c18f74f97
|
||
|
|
831433f9d8
|
||
|
|
aa31c857c7
|
||
|
|
73272fa6e5
|
||
|
|
0b047b0bcc
|
||
|
|
ad7b8c3eda
|
||
|
|
51801d4228
|
||
|
|
644197fae8
|
||
|
|
f43121769b
|
||
|
|
948b36c80d
|
||
|
|
635b68ab46
|
||
|
|
8df2e64872
|
||
|
|
427426bebe
|
No files matched your search
Vendored
+3
-3
@@ -32,7 +32,7 @@ pipeline {
|
||||
sh 'vendor/bin/phpunit --log-junit unit_test_results.xml --testdox tests'
|
||||
}
|
||||
post {
|
||||
success {
|
||||
always {
|
||||
archiveArtifacts 'unit_test_results.xml'
|
||||
}
|
||||
}
|
||||
@@ -47,10 +47,10 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
sh 'php vendor/bin/phpstan analyse -c phpstan.neon --error-format=prettyJson > static_code_analysis_results.json'
|
||||
sh 'php -d memory_limit=1G vendor/bin/phpstan analyse -c phpstan.neon --error-format=prettyJson > static_code_analysis_results.json'
|
||||
}
|
||||
post {
|
||||
success {
|
||||
always {
|
||||
archiveArtifacts 'static_code_analysis_results.json'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ const ROOT = __DIR__;
|
||||
class Container
|
||||
{
|
||||
static SokoWeb\Interfaces\Database\IConnection $dbConnection;
|
||||
static SokoWeb\Interfaces\Database\IAuditLogger $auditLogger;
|
||||
static SokoWeb\Routing\RouteCollection $routeCollection;
|
||||
static SokoWeb\Interfaces\Session\ISessionHandler $sessionHandler;
|
||||
static SokoWeb\Interfaces\Request\IRequest $request;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php namespace SokoWeb\Database;
|
||||
|
||||
use SokoWeb\Interfaces\Database\IAuditLogger;
|
||||
use SokoWeb\Interfaces\Database\IConnection;
|
||||
|
||||
abstract class AuditLoggerBase implements IAuditLogger
|
||||
{
|
||||
const LOG_TYPE_INSERT = 'insert';
|
||||
const LOG_TYPE_UPDATE = 'update';
|
||||
const LOG_TYPE_DELETE = 'delete';
|
||||
|
||||
private IConnection $connection;
|
||||
|
||||
private string $logTable;
|
||||
|
||||
public function __construct(IConnection $connection, string $logTable)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->logTable = $logTable;
|
||||
}
|
||||
|
||||
public function logInsert(string $localTable, $localId): void
|
||||
{
|
||||
$data = [
|
||||
'local_table' => $localTable,
|
||||
'local_id' => $localId,
|
||||
'type' => static::LOG_TYPE_INSERT,
|
||||
'modifier_id' => $this->getModifierId(),
|
||||
];
|
||||
|
||||
$query = 'INSERT INTO ' . Utils::backtick($this->logTable) . ' SET ' . Utils::generateColumnsWithBinding(array_keys($data));
|
||||
$stmt = $this->connection->prepare($query);
|
||||
$stmt->execute($data);
|
||||
}
|
||||
|
||||
public function logUpdate(string $localTable, $localId, array $diff): void
|
||||
{
|
||||
$data = [
|
||||
'local_table' => $localTable,
|
||||
'local_id' => $localId,
|
||||
'type' => static::LOG_TYPE_UPDATE,
|
||||
'modifier_id' => $this->getModifierId(),
|
||||
'column' => null,
|
||||
'old' => null,
|
||||
'new' => null,
|
||||
];
|
||||
|
||||
$query = 'INSERT INTO ' . Utils::backtick($this->logTable) . ' SET ' . Utils::generateColumnsWithBinding(array_keys($data));
|
||||
$stmt = $this->connection->prepare($query);
|
||||
|
||||
foreach ($diff as $name => $values) {
|
||||
$data['column'] = $name;
|
||||
$data['old'] = $values['old'];
|
||||
$data['new'] = $values['new'];
|
||||
$stmt->execute($data);
|
||||
}
|
||||
}
|
||||
|
||||
public function logDelete(string $localTable, $localId, array $attributes): void
|
||||
{
|
||||
$data = [
|
||||
'local_table' => $localTable,
|
||||
'local_id' => $localId,
|
||||
'type' => static::LOG_TYPE_DELETE,
|
||||
'modifier_id' => $this->getModifierId(),
|
||||
'old' => $attributes,
|
||||
];
|
||||
|
||||
$query = 'INSERT INTO ' . Utils::backtick($this->logTable) . ' SET ' . Utils::generateColumnsWithBinding(array_keys($data));
|
||||
$stmt = $this->connection->prepare($query);
|
||||
$stmt->execute($data);
|
||||
}
|
||||
|
||||
abstract protected function getModifierId();
|
||||
}
|
||||
@@ -19,15 +19,9 @@ class Connection implements IConnection
|
||||
$socket = (string) ini_get('mysqli.default_socket');
|
||||
}
|
||||
|
||||
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||
$this->connection = new mysqli($host, $user, $password, $db, $port, $socket);
|
||||
|
||||
if ($this->connection->connect_error) {
|
||||
throw new \Exception('Connection failed: ' . $this->connection->connect_error);
|
||||
}
|
||||
|
||||
if (!$this->connection->set_charset('utf8mb4')) {
|
||||
throw new \Exception($this->connection->error);
|
||||
}
|
||||
$this->connection->set_charset('utf8mb4');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
@@ -37,30 +31,24 @@ class Connection implements IConnection
|
||||
|
||||
public function startTransaction(): void
|
||||
{
|
||||
if (!$this->connection->autocommit(false)) {
|
||||
throw new \Exception($this->connection->error);
|
||||
}
|
||||
$this->connection->autocommit(false);
|
||||
}
|
||||
|
||||
public function commit(): void
|
||||
{
|
||||
if (!$this->connection->commit() || !$this->connection->autocommit(true)) {
|
||||
throw new \Exception($this->connection->error);
|
||||
}
|
||||
$this->connection->commit();
|
||||
$this->connection->autocommit(true);
|
||||
}
|
||||
|
||||
public function rollback(): void
|
||||
{
|
||||
if (!$this->connection->rollback() || !$this->connection->autocommit(true)) {
|
||||
throw new \Exception($this->connection->error);
|
||||
}
|
||||
$this->connection->rollback();
|
||||
$this->connection->autocommit(true);
|
||||
}
|
||||
|
||||
public function query(string $query): ?IResultSet
|
||||
{
|
||||
if (!($result = $this->connection->query($query))) {
|
||||
throw new \Exception($this->connection->error . '. Query: ' . $query);
|
||||
}
|
||||
$result = $this->connection->query($query);
|
||||
|
||||
if ($result !== true) {
|
||||
return new ResultSet($result);
|
||||
@@ -71,9 +59,7 @@ class Connection implements IConnection
|
||||
|
||||
public function multiQuery(string $query): array
|
||||
{
|
||||
if (!$this->connection->multi_query($query)) {
|
||||
throw new \Exception($this->connection->error . '. Query: ' . $query);
|
||||
}
|
||||
$this->connection->multi_query($query);
|
||||
|
||||
$ret = [];
|
||||
do {
|
||||
@@ -86,18 +72,12 @@ class Connection implements IConnection
|
||||
$this->connection->more_results();
|
||||
} while ($this->connection->next_result());
|
||||
|
||||
if ($this->connection->error) {
|
||||
throw new \Exception($this->connection->error . '. Query: ' . $query);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function prepare(string $query): IStatement
|
||||
{
|
||||
if (!($stmt = $this->connection->prepare($query))) {
|
||||
throw new \Exception($this->connection->error . '. Query: ' . $query);
|
||||
}
|
||||
$stmt = $this->connection->prepare($query);
|
||||
|
||||
return new Statement($stmt);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
use SokoWeb\Interfaces\Database\IConnection;
|
||||
use SokoWeb\Database\Utils;
|
||||
use SokoWeb\Interfaces\Database\IAuditLogger;
|
||||
use SokoWeb\Interfaces\Database\IResultSet;
|
||||
|
||||
class Modify
|
||||
{
|
||||
@@ -9,6 +11,8 @@ class Modify
|
||||
|
||||
private string $table;
|
||||
|
||||
private ?IAuditLogger $auditLogger;
|
||||
|
||||
private string $idName = 'id';
|
||||
|
||||
private array $attributes = [];
|
||||
@@ -17,10 +21,13 @@ class Modify
|
||||
|
||||
private bool $autoIncrement = true;
|
||||
|
||||
public function __construct(IConnection $connection, string $table)
|
||||
private ?array $diff = null;
|
||||
|
||||
public function __construct(IConnection $connection, string $table, ?IAuditLogger $auditLogger = null)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->table = $table;
|
||||
$this->auditLogger = $auditLogger;
|
||||
}
|
||||
|
||||
public function setIdName(string $idName): Modify
|
||||
@@ -65,6 +72,13 @@ class Modify
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDiff(array $diff): Modify
|
||||
{
|
||||
$this->diff = $diff;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->attributes[$this->idName];
|
||||
@@ -89,6 +103,10 @@ class Modify
|
||||
|
||||
$stmt = $this->connection->prepare($query);
|
||||
$stmt->execute([$this->idName => $this->attributes[$this->idName]]);
|
||||
|
||||
if ($this->auditLogger !== null) {
|
||||
$this->auditLogger->logDelete($this->table, $this->attributes[$this->idName], $this->attributes);
|
||||
}
|
||||
}
|
||||
|
||||
private function insert(): void
|
||||
@@ -99,7 +117,7 @@ class Modify
|
||||
$this->attributes[$this->idName] = $this->generateKey();
|
||||
}
|
||||
|
||||
$set = $this->generateColumnsWithBinding(array_keys($this->attributes));
|
||||
$set = Utils::generateColumnsWithBinding(array_keys($this->attributes));
|
||||
|
||||
$query = 'INSERT INTO ' . Utils::backtick($this->table) . ' SET ' . $set;
|
||||
|
||||
@@ -109,32 +127,62 @@ class Modify
|
||||
if ($this->autoIncrement) {
|
||||
$this->attributes[$this->idName] = $this->connection->lastId();
|
||||
}
|
||||
|
||||
if ($this->auditLogger !== null) {
|
||||
$this->auditLogger->logInsert($this->table, $this->attributes[$this->idName]);
|
||||
}
|
||||
}
|
||||
|
||||
private function update(): void
|
||||
{
|
||||
if ($this->auditLogger !== null) {
|
||||
$this->generateDiff();
|
||||
if (count($this->diff) === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$attributes = $this->attributes;
|
||||
unset($attributes[$this->idName]);
|
||||
|
||||
$set = $this->generateColumnsWithBinding(array_keys($attributes));
|
||||
$set = Utils::generateColumnsWithBinding(array_keys($attributes));
|
||||
|
||||
$query = 'UPDATE ' . Utils::backtick($this->table) . ' SET ' . $set . ' WHERE ' . Utils::backtick($this->idName) . '=?';
|
||||
|
||||
$stmt = $this->connection->prepare($query);
|
||||
$stmt->execute(array_merge($attributes, [$this->idName => $this->attributes[$this->idName]]));
|
||||
}
|
||||
|
||||
public static function generateColumnsWithBinding(array $columns): string
|
||||
{
|
||||
array_walk($columns, function(&$value, $key) {
|
||||
$value = Utils::backtick($value) . '=?';
|
||||
});
|
||||
|
||||
return implode(',', $columns);
|
||||
if ($this->auditLogger !== null) {
|
||||
$this->auditLogger->logUpdate($this->table, $this->attributes[$this->idName], $this->diff);
|
||||
}
|
||||
}
|
||||
|
||||
private function generateKey(): string
|
||||
{
|
||||
return substr(hash('sha256', serialize($this->attributes) . random_bytes(5) . microtime()), 0, 7);
|
||||
}
|
||||
|
||||
private function generateDiff(): void
|
||||
{
|
||||
if (isset($this->diff)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->diff = [];
|
||||
$original = $this->readFromDb(array_keys($this->attributes));
|
||||
foreach ($original as $key => $value) {
|
||||
if ($value !== $this->attributes[$key]) {
|
||||
$this->diff[$key] = ['old' => $value, 'new' => $this->attributes[$key]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function readFromDb($columns): array
|
||||
{
|
||||
return (new Select($this->connection, $this->table))
|
||||
->columns($columns)
|
||||
->where($this->idName, '=', $this->attributes[$this->idName])
|
||||
->execute()
|
||||
->fetch(IResultSet::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,13 @@ class Utils {
|
||||
{
|
||||
return '`' . $name . '`';
|
||||
}
|
||||
|
||||
public static function generateColumnsWithBinding(array $columns): string
|
||||
{
|
||||
array_walk($columns, function(&$value, $key) {
|
||||
$value = static::backtick($value) . '=?';
|
||||
});
|
||||
|
||||
return implode(',', $columns);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php namespace SokoWeb\Interfaces\Database;
|
||||
|
||||
interface IAuditLogger
|
||||
{
|
||||
public function logInsert(string $localTable, $localId): void;
|
||||
|
||||
public function logUpdate(string $localTable, $localId, array $diff): void;
|
||||
|
||||
public function logDelete(string $localTable, $localId, array $attributes): void;
|
||||
}
|
||||
@@ -117,19 +117,23 @@ class PersistentDataManager
|
||||
$modified = $model->toArray();
|
||||
$id = $model->getId();
|
||||
|
||||
$modify = new Modify(\Container::$dbConnection, $model::getTable());
|
||||
$modify = new Modify(\Container::$dbConnection, $model::getTable(), \Container::$auditLogger);
|
||||
|
||||
if ($id !== null) {
|
||||
$original = $model->getSnapshot();
|
||||
$diff = [];
|
||||
|
||||
foreach ($original as $key => $value) {
|
||||
if ($value === $modified[$key]) {
|
||||
unset($modified[$key]);
|
||||
} else {
|
||||
$diff[$key] = ['old' => $value, 'new' => $modified[$key]];
|
||||
}
|
||||
}
|
||||
|
||||
if (count($modified) > 0) {
|
||||
$modify->setId($id);
|
||||
$modify->setDiff($diff);
|
||||
$modify->fill($modified);
|
||||
$modify->save();
|
||||
}
|
||||
@@ -145,8 +149,9 @@ class PersistentDataManager
|
||||
|
||||
public function deleteFromDb(Model $model): void
|
||||
{
|
||||
$modify = new Modify(\Container::$dbConnection, $model::getTable());
|
||||
$modify = new Modify(\Container::$dbConnection, $model::getTable(), \Container::$auditLogger);
|
||||
$modify->setId($model->getId());
|
||||
$modify->fill($model->toArray());
|
||||
$modify->delete();
|
||||
|
||||
$model->setId(null);
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php namespace SokoWeb\Response;
|
||||
|
||||
use ErrorException;
|
||||
use Exception;
|
||||
use SokoWeb\Interfaces\Response\IRedirect;
|
||||
use SokoWeb\Interfaces\Response\IContent;
|
||||
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
||||
use SokoWeb\Interfaces\Authorization\ISecured;
|
||||
use SokoWeb\Interfaces\Database\IConnection;
|
||||
use SokoWeb\Interfaces\Request\IRequest;
|
||||
use SokoWeb\Response\Redirect;
|
||||
use SokoWeb\Response\HtmlContent;
|
||||
use SokoWeb\Response\JsonContent;
|
||||
use SokoWeb\Routing\RouteCollection;
|
||||
|
||||
class HttpResponse
|
||||
{
|
||||
private IRequest $request;
|
||||
|
||||
private IConnection $dbConnection;
|
||||
|
||||
private RouteCollection $routeCollection;
|
||||
|
||||
private array $appConfig;
|
||||
|
||||
private string $method;
|
||||
|
||||
private string $rawUrl;
|
||||
|
||||
private array $parsedUrl;
|
||||
|
||||
public function __construct(
|
||||
IRequest $request,
|
||||
IConnection $dbConnection,
|
||||
RouteCollection $routeCollection,
|
||||
array $appConfig,
|
||||
string $requestMethod,
|
||||
string $requestUrl
|
||||
) {
|
||||
set_error_handler([$this, 'exceptionsErrorHandler']);
|
||||
|
||||
$this->request = $request;
|
||||
$this->dbConnection = $dbConnection;
|
||||
$this->routeCollection = $routeCollection;
|
||||
$this->appConfig = $appConfig;
|
||||
$this->method = strtolower($requestMethod);
|
||||
$this->parsedUrl = parse_url($requestUrl);
|
||||
$this->rawUrl = $requestUrl;
|
||||
}
|
||||
|
||||
public function exceptionsErrorHandler($severity, $message, $filename, $lineno)
|
||||
{
|
||||
throw new ErrorException($message, 0, $severity, $filename, $lineno);
|
||||
}
|
||||
|
||||
public function render(): void
|
||||
{
|
||||
$match = $this->routeCollection->match($this->method, $this->parsedUrl['path']);
|
||||
if ($match === null) {
|
||||
$this->render404();
|
||||
return;
|
||||
}
|
||||
|
||||
list($route, $params) = $match;
|
||||
$this->request->setParsedRouteParams($params);
|
||||
$handler = $route->getHandler();
|
||||
$controller = new $handler[0]($this->request);
|
||||
|
||||
if (
|
||||
$controller instanceof IAuthenticationRequired &&
|
||||
$controller->isAuthenticationRequired() &&
|
||||
$this->request->user() === null
|
||||
) {
|
||||
$this->redirectToLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
$this->method === 'post' &&
|
||||
!in_array($this->parsedUrl['path'], $this->appConfig['antiCsrfTokenExceptions']) &&
|
||||
$this->request->post($this->appConfig['antiCsrfTokenName']) !== $this->request->session()->get($this->appConfig['antiCsrfTokenName'])
|
||||
) {
|
||||
$this->renderAntiCsrfError();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($controller instanceof ISecured && !$controller->authorize()) {
|
||||
$this->render404();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dbConnection->startTransaction();
|
||||
try {
|
||||
$response = call_user_func([$controller, $handler[1]]);
|
||||
} catch (Exception $exception) {
|
||||
$this->dbConnection->rollback();
|
||||
$this->render500($exception);
|
||||
return;
|
||||
}
|
||||
$this->dbConnection->commit();
|
||||
|
||||
if ($response instanceof IContent) {
|
||||
header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8');
|
||||
$response->render();
|
||||
} elseif ($response instanceof IRedirect) {
|
||||
header('Location: ' . $response->getUrl(), true, $response->getHttpCode());
|
||||
} else {
|
||||
$this->render404();
|
||||
}
|
||||
}
|
||||
|
||||
private function redirectToLogin(): void
|
||||
{
|
||||
$this->request->session()->set('redirect_after_login', $this->rawUrl);
|
||||
$response = new Redirect($this->routeCollection->getRoute($this->appConfig['loginRouteId'])->generateLink(), IRedirect::TEMPORARY);
|
||||
header('Location: ' . $response->getUrl(), true, $response->getHttpCode());
|
||||
}
|
||||
|
||||
private function renderAntiCsrfError(): void
|
||||
{
|
||||
$content = new JsonContent($this->appConfig['antiCsrfTokenErrorResponse']);
|
||||
header('Content-Type: text/html; charset=UTF-8', true, 403);
|
||||
$content->render();
|
||||
}
|
||||
|
||||
private function render404(): void
|
||||
{
|
||||
$content = new HtmlContent($this->appConfig['error404View']);
|
||||
header('Content-Type: text/html; charset=UTF-8', true, 404);
|
||||
$content->render();
|
||||
}
|
||||
|
||||
private function render500(Exception $exception): void
|
||||
{
|
||||
if (empty($_ENV['DEV'])) {
|
||||
$exceptionToPrint = null;
|
||||
} else {
|
||||
$exceptionToPrint = (string)$exception;
|
||||
}
|
||||
|
||||
$content = new HtmlContent($this->appConfig['error500View'], ['exceptionToPrint' => $exceptionToPrint]);
|
||||
header('Content-Type: text/html; charset=UTF-8', true, 500);
|
||||
$content->render();
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ class Redirect implements IRedirect
|
||||
if (preg_match('/^http(s)?/', $this->target) === 1) {
|
||||
$link = $this->target;
|
||||
} else {
|
||||
$link = \Container::$request->getBase() . '/' . $this->target;
|
||||
$link = \Container::$request->getBase() . $this->target;
|
||||
}
|
||||
|
||||
return $link;
|
||||
|
||||
@@ -53,7 +53,7 @@ class Route
|
||||
|
||||
$query = count($queryParams) > 0 ? '?' . http_build_query($queryParams) : '';
|
||||
|
||||
return implode('/', $link) . $query;
|
||||
return '/' . implode('/', $link) . $query;
|
||||
}
|
||||
|
||||
public function testAgainst(array $path): ?array
|
||||
|
||||
@@ -41,9 +41,10 @@ class RouteCollection
|
||||
return $this->routes[$id];
|
||||
}
|
||||
|
||||
public function match(string $method, array $uri): ?array
|
||||
public function match(string $method, string $uri): ?array
|
||||
{
|
||||
$groupNumber = count($uri);
|
||||
$path = $uri === '/' ? [] : explode('/', ltrim($uri, '/'));
|
||||
$groupNumber = count($path);
|
||||
|
||||
// response to HEAD request with the GET content
|
||||
if ($method === 'head') {
|
||||
@@ -55,7 +56,7 @@ class RouteCollection
|
||||
}
|
||||
|
||||
foreach ($this->searchTable[$method][$groupNumber] as $route) {
|
||||
if (($parameters = $route->testAgainst($uri)) !== null) {
|
||||
if (($parameters = $route->testAgainst($path)) !== null) {
|
||||
return [$route, $parameters];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@ $dotenv->load();
|
||||
class Container
|
||||
{
|
||||
static SokoWeb\Interfaces\Database\IConnection $dbConnection;
|
||||
static SokoWeb\Interfaces\Database\IAuditLogger $auditLogger;
|
||||
static SokoWeb\Routing\RouteCollection $routeCollection;
|
||||
static SokoWeb\Interfaces\Session\ISessionHandler $sessionHandler;
|
||||
static SokoWeb\Interfaces\Request\IRequest $request;
|
||||
}
|
||||
|
||||
Container::$dbConnection = new SokoWeb\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
|
||||
Container::$auditLogger = new {app}\Database\AuditLogger(Container::$dbConnection, 'audit_log');
|
||||
@@ -13,3 +13,16 @@ CREATE TABLE `users` (
|
||||
UNIQUE KEY `email` (`email`),
|
||||
UNIQUE KEY `google_sub` (`google_sub`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `audit_log` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`local_table` varchar(255) NOT NULL,
|
||||
`local_id` int(10) unsigned NOT NULL,
|
||||
`type` enum('insert','update','delete') NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`modifier_id` int(10) unsigned NULL,
|
||||
`column` varchar(255) NULL,
|
||||
`old` text NULL,
|
||||
`new` text NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php namespace {app}\Database;
|
||||
|
||||
use SokoWeb\Database\AuditLoggerBase;
|
||||
|
||||
class AuditLogger extends AuditLoggerBase
|
||||
{
|
||||
protected function getModifierId()
|
||||
{
|
||||
\Container::$request->user()->getUniqueId();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user