. */ namespace Liuch\DmarcSrg\Report; use Liuch\DmarcSrg\Core; use Liuch\DmarcSrg\Exception\SoftException; use Liuch\DmarcSrg\Exception\DatabaseNotFoundException; class Report { private $db = null; private $data = null; public function __construct($data, $db = null) { $this->data = $data; $this->db = $db ?? Core::instance()->database(); } public static function fromXmlFile($fd) { $data = ReportData::fromXmlFile($fd); if (!self::checkData($data)) { throw new SoftException('Incorrect or incomplete report data'); } return new Report($data); } public function fetch() { $domain = $this->data['domain']; $report_id = $this->data['report_id']; if (empty($domain) || empty($report_id)) { throw new SoftException('Not specified report\'s domain or id'); } $this->data = [ 'domain' => $domain, 'report_id' => $report_id ]; try { $this->db->getMapper('report')->fetch($this->data); } catch (DatabaseNotFoundException $e) { throw new SoftException('The report is not found'); } } public function save(string $real_fname) { $b_ts = $this->data['begin_time']; $e_ts = $this->data['end_time']; if (!$b_ts->getTimestamp() || !$e_ts->getTimestamp() || $b_ts > $e_ts) { throw new SoftException('Failed to add an incoming report: wrong date range'); } $this->db->getMapper('report')->save($this->data); return [ 'message' => 'The report is loaded successfully' ]; } public function get() { return $this->data; } public function set($name, $value) { if (empty($this->data['domain']) || empty($this->data['report_id'])) { throw new SoftException('Not specified report\'s domain or id'); } $this->db->getMapper('report')->setProperty($this->data, $name, $value); return [ 'message' => 'Ok' ]; } /** * Checks report data for correctness and completeness * * @param array $data Report data * * @return bool */ private static function checkData(array $data): bool { static $fields = [ 'domain' => [ 'required' => true, 'type' => 'string' ], 'begin_time' => [ 'required' => true, 'type' => 'object' ], 'end_time' => [ 'required' => true, 'type' => 'object' ], 'org' => [ 'required' => true, 'type' => 'string' ], 'external_id' => [ 'required' => true, 'type' => 'string' ], 'email' => [ 'required' => false, 'type' => 'string' ], 'extra_contact_info' => [ 'required' => false, 'type' => 'string' ], 'error_string' => [ 'required' => false, 'type' => 'array' ], 'policy_adkim' => [ 'required' => false, 'type' => 'string' ], 'policy_aspf' => [ 'required' => false, 'type' => 'string' ], 'policy_p' => [ 'required' => false, 'type' => 'string' ], 'policy_sp' => [ 'required' => false, 'type' => 'string' ], 'policy_np' => [ 'required' => false, 'type' => 'string' ], 'policy_pct' => [ 'required' => false, 'type' => 'string' ], 'policy_fo' => [ 'required' => false, 'type' => 'string' ], 'records' => [ 'required' => true, 'type' => 'array' ] ]; if (!self::checkRow($data, $fields) || count($data['records']) === 0) { return false; } static $rfields = [ 'ip' => [ 'required' => true, 'type' => 'string' ], 'rcount' => [ 'required' => true, 'type' => 'integer' ], 'disposition' => [ 'required' => true, 'type' => 'string' ], 'reason' => [ 'required' => false, 'type' => 'array' ], 'dkim_auth' => [ 'required' => false, 'type' => 'array' ], 'spf_auth' => [ 'required' => false, 'type' => 'array' ], 'dkim_align' => [ 'required' => true, 'type' => 'string' ], 'spf_align' => [ 'required' => true, 'type' => 'string' ], 'envelope_to' => [ 'required' => false, 'type' => 'string' ], 'envelope_from' => [ 'required' => false, 'type' => 'string' ], 'header_from' => [ 'required' => false, 'type' => 'string' ] ]; foreach ($data['records'] as &$rec) { if (gettype($rec) !== 'array' || !self::checkRow($rec, $rfields)) { return false; } } return true; } /** * Checks one row of report data * * @param array $row Data row * @param array $def Row definition * * @return bool */ private static function checkRow(array &$row, array &$def): bool { foreach ($def as $key => &$dd) { if (isset($row[$key])) { if (gettype($row[$key]) !== $dd['type']) { return false; } } elseif ($dd['required']) { return false; } } return true; } }