312 lines
11 KiB
PHP
312 lines
11 KiB
PHP
|
<?php
|
||
|
|
||
|
/**
|
||
|
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||
|
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||
|
*
|
||
|
* Available at:
|
||
|
* https://github.com/liuch/dmarc-srg
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify it
|
||
|
* under the terms of the GNU General Public License as published by the Free
|
||
|
* Software Foundation, either version 3 of the License.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
|
* more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License along with
|
||
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*
|
||
|
* =========================
|
||
|
*
|
||
|
* This file contains the ReportLogMapper class
|
||
|
*
|
||
|
* @category API
|
||
|
* @package DmarcSrg
|
||
|
* @author Aleksey Andreev (liuch)
|
||
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||
|
*/
|
||
|
|
||
|
namespace Liuch\DmarcSrg\Database\Mariadb;
|
||
|
|
||
|
use Liuch\DmarcSrg\DateTime;
|
||
|
use Liuch\DmarcSrg\Database\ReportLogMapperInterface;
|
||
|
use Liuch\DmarcSrg\Exception\DatabaseFatalException;
|
||
|
use Liuch\DmarcSrg\Exception\DatabaseNotFoundException;
|
||
|
|
||
|
/**
|
||
|
* ReportLogMapper class implementation for MariaDB
|
||
|
*/
|
||
|
class ReportLogMapper implements ReportLogMapperInterface
|
||
|
{
|
||
|
private $connector = null;
|
||
|
|
||
|
/**
|
||
|
* The constructor
|
||
|
*
|
||
|
* @param Connector $connector DatabaseConnector
|
||
|
*/
|
||
|
public function __construct(object $connector)
|
||
|
{
|
||
|
$this->connector = $connector;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fetches data of report log item from the database by id
|
||
|
*
|
||
|
* @param Report log data
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function fetch(array &$data): void
|
||
|
{
|
||
|
try {
|
||
|
$st = $this->connector->dbh()->prepare(
|
||
|
'SELECT `domain`, `external_id`, `event_time`, `filename`, `source`, `success`, `message` FROM `'
|
||
|
. $this->connector->tablePrefix('reportlog') . '` WHERE `id` = ?'
|
||
|
);
|
||
|
$st->bindValue(1, $data['id'], \PDO::PARAM_INT);
|
||
|
$st->execute();
|
||
|
if (!($row = $st->fetch(\PDO::FETCH_NUM))) {
|
||
|
throw new DatabaseNotFoundException();
|
||
|
}
|
||
|
$data['domain'] = $row[0];
|
||
|
$data['external_id'] = $row[1];
|
||
|
$data['event_time'] = new DateTime($row[2]);
|
||
|
$data['filename'] = $row[3];
|
||
|
$data['source'] = intval($row[4]);
|
||
|
$data['success'] = boolval($row[5]);
|
||
|
$data['message'] = $row[6];
|
||
|
$st->closeCursor();
|
||
|
} catch (\PDOException $e) {
|
||
|
throw new DatabaseFatalException('Failed to get the log item', -1, $e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Saves data of report log item to the database
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function save(array &$data): void
|
||
|
{
|
||
|
$db = $this->connector->dbh();
|
||
|
try {
|
||
|
$id = $data['id'];
|
||
|
if (is_null($id)) {
|
||
|
$st = $db->prepare(
|
||
|
'INSERT INTO `' . $this->connector->tablePrefix('reportlog')
|
||
|
. '` (`domain`, `external_id`, `event_time`, `filename`, `source`, `success`, `message`)'
|
||
|
. ' VALUES (?, ?, ?, ?, ?, ?, ?)'
|
||
|
);
|
||
|
} else {
|
||
|
$st = $db->prepare(
|
||
|
'UPDATE `' . $this->connector->tablePrefix('reportlog')
|
||
|
. '` SET `domain` = ?, `external_id` = ?, `event_time` = ?, `filename` = ?,'
|
||
|
. ' `source` = ?, `success` = ?, `message` = ? WHERE `id` = ?'
|
||
|
);
|
||
|
$st->bindValue(8, $id, \PDO::PARAM_INT);
|
||
|
}
|
||
|
$ts = $data['event_time'] ?? (new DateTime());
|
||
|
$st->bindValue(1, $data['domain'], \PDO::PARAM_STR);
|
||
|
$st->bindValue(2, $data['external_id'], \PDO::PARAM_STR);
|
||
|
$st->bindValue(3, $ts->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
|
||
|
$st->bindValue(4, $data['filename'], \PDO::PARAM_STR);
|
||
|
$st->bindValue(5, $data['source'], \PDO::PARAM_INT);
|
||
|
$st->bindValue(6, $data['success'], \PDO::PARAM_BOOL);
|
||
|
$st->bindValue(7, $data['message'], \PDO::PARAM_STR);
|
||
|
$st->execute();
|
||
|
if (is_null($id)) {
|
||
|
$data['id'] = intval($db->lastInsertId());
|
||
|
}
|
||
|
$st->closeCursor();
|
||
|
$data['event_time'] = $ts;
|
||
|
} catch (\PDOException $e) {
|
||
|
throw new DatabaseFatalException('Failed to save a report log item');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a list of report log items with given criteria
|
||
|
*
|
||
|
* @param array $filter Key-value array:
|
||
|
* 'from_time' => DateTime
|
||
|
* 'till_time' => DateTime
|
||
|
* @param array $order Key-value array with order options:
|
||
|
* 'direction' => string, 'ascent' or 'descent'
|
||
|
* @param array $limit Key-value array:
|
||
|
* 'offset' => int
|
||
|
* 'count' => int
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
public function list(array &$filter, array &$order, array &$limit): array
|
||
|
{
|
||
|
$list = [];
|
||
|
try {
|
||
|
$st = $this->connector->dbh()->prepare(
|
||
|
'SELECT `id`, `domain`, `event_time`, `source`, `success`, `message` FROM `'
|
||
|
. $this->connector->tablePrefix('reportlog') . '`'
|
||
|
. $this->sqlCondition($filter)
|
||
|
. $this->sqlOrder($order)
|
||
|
. $this->sqlLimit($limit)
|
||
|
);
|
||
|
$this->sqlBindValues($st, $filter, $limit);
|
||
|
$st->execute();
|
||
|
while ($row = $st->fetch(\PDO::FETCH_NUM)) {
|
||
|
$list[] = [
|
||
|
'id' => intval($row[0]),
|
||
|
'domain' => $row[1],
|
||
|
'event_time' => new DateTime($row[2]),
|
||
|
'source' => intval($row[3]),
|
||
|
'success' => boolval($row[4]),
|
||
|
'message' => $row[5]
|
||
|
];
|
||
|
}
|
||
|
$st->closeCursor();
|
||
|
} catch (\PDOException $e) {
|
||
|
throw new DatabaseFatalException('Failed to get the logs', -1, $e);
|
||
|
}
|
||
|
return $list;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the number of report log items matching the specified filter and limits
|
||
|
*
|
||
|
* @param array $filter Key-value array with filtering parameters
|
||
|
* @param array $limit Key-value array with limits
|
||
|
*
|
||
|
* @return int
|
||
|
*/
|
||
|
public function count(array &$filter, array &$limit): int
|
||
|
{
|
||
|
$cnt = 0;
|
||
|
try {
|
||
|
$st = $this->connector->dbh()->prepare(
|
||
|
'SELECT COUNT(*) FROM `' . $this->connector->tablePrefix('reportlog') . '`'
|
||
|
. $this->sqlCondition($filter)
|
||
|
. $this->sqlLimit($limit)
|
||
|
);
|
||
|
$this->sqlBindValues($st, $filter, $limit);
|
||
|
$st->execute();
|
||
|
$cnt = intval($st->fetch(\PDO::FETCH_NUM)[0]);
|
||
|
$st->closeCursor();
|
||
|
} catch (\PDOException $e) {
|
||
|
throw new DatabaseFatalException('Failed to get the log data', -1, $e);
|
||
|
}
|
||
|
return $cnt;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Deletes report log items from the database
|
||
|
*
|
||
|
* @param array $filter Key-value array with filtering parameters
|
||
|
* @param array $order Key-value array with order options:
|
||
|
* 'direction' => string, 'ascent' or 'descent'
|
||
|
* @param array $limit Key-value array with limits
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function delete(array &$filter, array &$order, array &$limit): void
|
||
|
{
|
||
|
try {
|
||
|
$st = $this->connector->dbh()->prepare(
|
||
|
'DELETE FROM `' . $this->connector->tablePrefix('reportlog') . '`'
|
||
|
. $this->sqlCondition($filter)
|
||
|
. $this->sqlOrder($order)
|
||
|
. $this->sqlLimit($limit)
|
||
|
);
|
||
|
$this->sqlBindValues($st, $filter, $limit);
|
||
|
$st->execute();
|
||
|
$st->closeCursor();
|
||
|
} catch (\PDOException $e) {
|
||
|
throw new DatabaseFatalException('Failed to remove the log data', -1, $e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a string with an SQL condition 'WHERE ...'
|
||
|
*
|
||
|
* @param array $filter Key-value with filtering paremeters
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
private function sqlCondition(array &$filter): string
|
||
|
{
|
||
|
$res = '';
|
||
|
if (!is_null($filter['from_time']) || !is_null($filter['till_time'])) {
|
||
|
$res = ' WHERE';
|
||
|
$till_time = $filter['till_time'];
|
||
|
if (!is_null($filter['from_time'])) {
|
||
|
$res .= ' `event_time` >= ?';
|
||
|
if (!is_null($till_time)) {
|
||
|
$res .= ' AND';
|
||
|
}
|
||
|
}
|
||
|
if (!is_null($till_time)) {
|
||
|
$res .= ' `event_time` < ?';
|
||
|
}
|
||
|
}
|
||
|
return $res;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns 'ORDER BY ...' part of the SQL query
|
||
|
*
|
||
|
* @param array $order Key-value array with ordering options
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
private function sqlOrder(array &$order): string
|
||
|
{
|
||
|
return ' ORDER BY `event_time` ' . ($order['direction'] === 'descent' ? 'DESC' : 'ASC');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns 'LIMIT ...' part of the SQL string
|
||
|
*
|
||
|
* @param array $limit Key-value array with keys 'offset' and 'count'
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
private function sqlLimit(array &$limit): string
|
||
|
{
|
||
|
$res = '';
|
||
|
if ($limit['count'] > 0) {
|
||
|
$res = ' LIMIT ?';
|
||
|
if ($limit['offset'] > 0) {
|
||
|
$res .= ', ?';
|
||
|
}
|
||
|
}
|
||
|
return $res;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Binds the values of the filter and the limit to SQL query
|
||
|
*
|
||
|
* @param PDOStatement $st Prepared SOL statement to bind to
|
||
|
* @param array $filter Key-value array with filter data
|
||
|
* @param array $limit Key-value array with limit data
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
private function sqlBindValues($st, array &$filter, array &$limit): void
|
||
|
{
|
||
|
$pos = 0;
|
||
|
if (!is_null($filter['from_time'])) {
|
||
|
$st->bindValue(++$pos, $filter['from_time']->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
|
||
|
}
|
||
|
if (!is_null($filter['till_time'])) {
|
||
|
$st->bindValue(++$pos, $filter['till_time']->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
|
||
|
}
|
||
|
if ($limit['count'] > 0) {
|
||
|
if ($limit['offset'] > 0) {
|
||
|
$st->bindValue(++$pos, $limit['offset'], \PDO::PARAM_INT);
|
||
|
}
|
||
|
$st->bindValue(++$pos, $limit['count'], \PDO::PARAM_INT);
|
||
|
}
|
||
|
}
|
||
|
}
|