diff --git a/Application/ApplicationType.php b/Application/ApplicationType.php new file mode 100644 index 000000000..00df8d925 --- /dev/null +++ b/Application/ApplicationType.php @@ -0,0 +1,34 @@ +flushAll(); + return true; } /** diff --git a/DataStorage/Cache/Connection/RedisCache.php b/DataStorage/Cache/Connection/RedisCache.php index fe6e217ab..12387d4d0 100755 --- a/DataStorage/Cache/Connection/RedisCache.php +++ b/DataStorage/Cache/Connection/RedisCache.php @@ -305,7 +305,7 @@ final class RedisCache extends ConnectionAbstract return false; } - return $this->flushDb(); + return true; } /** diff --git a/DataStorage/Database/Connection/ConnectionAbstract.php b/DataStorage/Database/Connection/ConnectionAbstract.php index 185ede627..28cd96c83 100755 --- a/DataStorage/Database/Connection/ConnectionAbstract.php +++ b/DataStorage/Database/Connection/ConnectionAbstract.php @@ -213,4 +213,25 @@ abstract class ConnectionAbstract implements ConnectionInterface return isset($this->{$name}) ? $this->{$name} : null; } + + /** + * Start a transaction + * + * @since 1.0.0 + */ + abstract public function beginTransaction() : void; + + /** + * Roll back a transaction + * + * @since 1.0.0 + */ + abstract public function rollBack() : void; + + /** + * Commit a transaction + * + * @since 1.0.0 + */ + abstract public function commit() : void; } diff --git a/DataStorage/Database/Connection/MysqlConnection.php b/DataStorage/Database/Connection/MysqlConnection.php index 9bc050f33..9ccdda045 100755 --- a/DataStorage/Database/Connection/MysqlConnection.php +++ b/DataStorage/Database/Connection/MysqlConnection.php @@ -87,4 +87,28 @@ final class MysqlConnection extends ConnectionAbstract $this->dbdata['password'] = '****'; } } + + /** + * {@inheritdoc} + */ + public function beginTransaction() : void + { + $this->con->beginTransaction(); + } + + /** + * {@inheritdoc} + */ + public function rollBack() : void + { + $this->con->rollBack(); + } + + /** + * {@inheritdoc} + */ + public function commit() : void + { + $this->con->commit(); + } } diff --git a/DataStorage/Database/Connection/NullConnection.php b/DataStorage/Database/Connection/NullConnection.php index 7c68396ac..67e29a7d8 100755 --- a/DataStorage/Database/Connection/NullConnection.php +++ b/DataStorage/Database/Connection/NullConnection.php @@ -38,4 +38,25 @@ final class NullConnection extends ConnectionAbstract { return ['id' => $this->id]; } + + /** + * {@inheritdoc} + */ + public function beginTransaction() : void + { + } + + /** + * {@inheritdoc} + */ + public function rollBack() : void + { + } + + /** + * {@inheritdoc} + */ + public function commit() : void + { + } } diff --git a/DataStorage/Database/Connection/PostgresConnection.php b/DataStorage/Database/Connection/PostgresConnection.php index 0aaef6cc4..f54c8066f 100755 --- a/DataStorage/Database/Connection/PostgresConnection.php +++ b/DataStorage/Database/Connection/PostgresConnection.php @@ -88,4 +88,28 @@ final class PostgresConnection extends ConnectionAbstract $this->dbdata['password'] = '****'; } } + + /** + * {@inheritdoc} + */ + public function beginTransaction() : void + { + $this->con->beginTransaction(); + } + + /** + * {@inheritdoc} + */ + public function rollBack() : void + { + $this->con->rollBack(); + } + + /** + * {@inheritdoc} + */ + public function commit() : void + { + $this->con->commit(); + } } diff --git a/DataStorage/Database/Connection/SQLiteConnection.php b/DataStorage/Database/Connection/SQLiteConnection.php index 7e95a3968..6def03df0 100755 --- a/DataStorage/Database/Connection/SQLiteConnection.php +++ b/DataStorage/Database/Connection/SQLiteConnection.php @@ -98,4 +98,28 @@ final class SQLiteConnection extends ConnectionAbstract $this->dbdata['password'] = '****'; } } + + /** + * {@inheritdoc} + */ + public function beginTransaction() : void + { + $this->con->beginTransaction(); + } + + /** + * {@inheritdoc} + */ + public function rollBack() : void + { + $this->con->rollBack(); + } + + /** + * {@inheritdoc} + */ + public function commit() : void + { + $this->con->commit(); + } } diff --git a/DataStorage/Database/Connection/SqlServerConnection.php b/DataStorage/Database/Connection/SqlServerConnection.php index ca66e484a..26208005f 100755 --- a/DataStorage/Database/Connection/SqlServerConnection.php +++ b/DataStorage/Database/Connection/SqlServerConnection.php @@ -88,4 +88,28 @@ final class SqlServerConnection extends ConnectionAbstract $this->dbdata['password'] = '****'; } } + + /** + * {@inheritdoc} + */ + public function beginTransaction() : void + { + $this->con->beginTransaction(); + } + + /** + * {@inheritdoc} + */ + public function rollBack() : void + { + $this->con->rollBack(); + } + + /** + * {@inheritdoc} + */ + public function commit() : void + { + $this->con->commit(); + } } diff --git a/DataStorage/Database/Mapper/ReadMapper.php b/DataStorage/Database/Mapper/ReadMapper.php index 27cfa0518..421968964 100755 --- a/DataStorage/Database/Mapper/ReadMapper.php +++ b/DataStorage/Database/Mapper/ReadMapper.php @@ -324,23 +324,25 @@ final class ReadMapper extends DataMapperAbstract // Example: Show all profiles who have written a news article. // "with()" only allows to go from articles to accounts but we want to go the other way foreach ($this->join as $member => $values) { - if (($col = $this->mapper::getColumnByMember($member)) !== null) { - /* variable in model */ - foreach ($values as $join) { - // @todo: the has many, etc. if checks only work if it is a relation on the first level, if we have a deeper where condition nesting this fails - if ($join['child'] !== '') { - continue; - } + if (($col = $this->mapper::getColumnByMember($member)) === null) { + continue; + } - $query->join($join['mapper']::TABLE, $join['type'], $join['mapper']::TABLE . '_d' . ($this->depth + 1)) - ->on( - $this->mapper::TABLE . '_d' . $this->depth . '.' . $col, - '=', - $join['mapper']::TABLE . '_d' . ($this->depth + 1) . '.' . $join['mapper']::getColumnByMember($join['value']), - 'and', - $join['mapper']::TABLE . '_d' . ($this->depth + 1) - ); + /* variable in model */ + foreach ($values as $join) { + // @todo: the has many, etc. if checks only work if it is a relation on the first level, if we have a deeper where condition nesting this fails + if ($join['child'] !== '') { + continue; } + + $query->join($join['mapper']::TABLE, $join['type'], $join['mapper']::TABLE . '_d' . ($this->depth + 1)) + ->on( + $this->mapper::TABLE . '_d' . $this->depth . '.' . $col, + '=', + $join['mapper']::TABLE . '_d' . ($this->depth + 1) . '.' . $join['mapper']::getColumnByMember($join['value']), + 'and', + $join['mapper']::TABLE . '_d' . ($this->depth + 1) + ); } } @@ -354,7 +356,13 @@ final class ReadMapper extends DataMapperAbstract } if (($col = $this->mapper::getColumnByMember($member)) !== null) { + // In case alternative where values are allowed + // This is different from normal or conditions as these are exclusive or conditions + // This means they are only selected IFF the previous where clause fails + $alt = []; + /* variable in model */ + $previous = null; foreach ($values as $where) { // @todo: the has many, etc. if checks only work if it is a relation on the first level, if we have a deeper where condition nesting this fails if ($where['child'] !== '') { @@ -362,74 +370,44 @@ final class ReadMapper extends DataMapperAbstract } $comparison = \is_array($where['value']) && \count($where['value']) > 1 ? 'in' : $where['logic']; - $query->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $col, $comparison, $where['value'], $where['comparison']); - } - } /* elseif (isset($this->mapper::HAS_MANY[$member])) { - // variable in has many - // @todo: maybe needed in the future, but needs adjustment, doesn't make sense at the moment - foreach ($values as $where) { - // @todo: the has many, etc. if checks only work if it is a relation on the first level, if we have a deeper where condition nesting this fails - if ($where['child'] !== '') { - continue; - } + if ($where['comparison'] === 'ALT') { + // This uses an alternative value if the previous value(s) in the where clause don't exist (e.g. for localized results where you allow a user language, alternatively a primary language, and then alternatively any language if the first two don't exist). - $comparison = \is_array($where['value']) && \count($where['value']) > 1 ? 'in' : $where['logic']; - $query->where($this->mapper::HAS_MANY[$member]['table'] . '_d' . $this->depth . '.' . $this->mapper::HAS_MANY[$member]['external'], $comparison, $where['value'], $where['comparison']); + // is first value + if (empty($alt)) { + $alt[] = $previous['value']; + } - $query->leftJoin($this->mapper::HAS_MANY[$member]['table'], $this->mapper::HAS_MANY[$member]['table'] . '_d' . $this->depth); - if ($this->mapper::HAS_MANY[$member]['external'] !== null) { - $query->on( - $this->mapper::TABLE . '_d' . $this->depth . '.' . $this->mapper::HAS_MANY[$member][$this->mapper::PRIMARYFIELD], '=', - $this->mapper::HAS_MANY[$member]['table'] . '_d' . $this->depth . '.' . $this->mapper::HAS_MANY[$member]['self'], 'AND', - $this->mapper::HAS_MANY[$member]['table'] . '_d' . $this->depth - ); + /* + select * from table_name + where // where starts here + field1 = 'value1' // comes from normal where + or ( // where1 starts here + field1 = 'default' + and NOT EXISTS ( // where2 starts here + select 1 from table_name where field1 = 'value1' + ) + ) + */ + $where1 = new Where($query->db); + $where1->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $col, $comparison, $where['value'], 'and'); + + $where2 = new Builder($query->db); + $where2->select('1') + ->from($this->mapper::TABLE . '_d' . $this->depth) + ->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $col, 'in', $alt); + + $where1->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $col, 'not exists', $where2, 'and'); + + $query->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $col, $comparison, $where1, 'or'); + + $alt[] = $where['value']; } else { - $query->on( - $this->mapper::TABLE . '_d' . $this->depth . '.' . $this->mapper::PRIMARYFIELD, '=', - $this->mapper::HAS_MANY[$member]['table'] . '_d' . $this->depth . '.' . $this->mapper::HAS_MANY[$member]['self'], 'AND', - $this->mapper::HAS_MANY[$member]['table'] . '_d' . $this->depth - ); + $previous = $where; + $query->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $col, $comparison, $where['value'], $where['comparison']); } } - - } */ /* elseif (isset($this->mapper::BELONGS_TO[$member])) { - // variable in belogns to - // @todo: maybe needed in the future, but needs adjustment, doesn't make sense at the moment - foreach ($values as $index => $where) { - // @todo: the has many, etc. if checks only work if it is a relation on the first level, if we have a deeper where condition nesting this fails - if ($where['child'] !== '') { - continue; - } - - $comparison = \is_array($where['value']) && \count($where['value']) > 1 ? 'in' : $where['logic']; - $query->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $member, $comparison, $where['value'], $where['comparison']); - - $query->leftJoin($this->mapper::BELONGS_TO[$member]['mapper']::TABLE, $this->mapper::BELONGS_TO[$member]['mapper']::TABLE . '_d' . $this->depth) - ->on( - $this->mapper::TABLE . '_d' . $this->depth . '.' . $this->mapper::BELONGS_TO[$member]['external'], '=', - $this->mapper::BELONGS_TO[$member]['mapper']::TABLE . '_d' . $this->depth . '.' . $this->mapper::BELONGS_TO[$member]['mapper']::PRIMARYFIELD , 'AND', - $this->mapper::BELONGS_TO[$member]['mapper']::TABLE . '_d' . $this->depth - ); - } - } */ /* elseif (isset($this->mapper::OWNS_ONE[$member])) { - // variable in owns one - // @todo: maybe needed in the future, but needs adjustment, doesn't make sense at the moment - foreach ($values as $index => $where) { - // @todo: the has many, etc. if checks only work if it is a relation on the first level, if we have a deeper where condition nesting this fails - if ($where['child'] !== '') { - continue; - } - - $comparison = \is_array($where['value']) && \count($where['value']) > 1 ? 'in' : $where['logic']; - $query->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $member, $comparison, $where['value'], $where['comparison']); - $query->leftJoin($this->mapper::OWNS_ONE[$member]['mapper']::TABLE, $this->mapper::OWNS_ONE[$member]['mapper']::TABLE . '_d' . $this->depth) - ->on( - $this->mapper::TABLE . '_d' . $this->depth . '.' . $this->mapper::OWNS_ONE[$member]['external'], '=', - $this->mapper::OWNS_ONE[$member]['mapper']::TABLE . '_d' . $this->depth . '.' . $this->mapper::OWNS_ONE[$member]['mapper']::PRIMARYFIELD , 'AND', - $this->mapper::OWNS_ONE[$member]['mapper']::TABLE . '_d' . $this->depth - ); - } - } */ + } } // load relations diff --git a/DataStorage/Database/Query/Grammar/Grammar.php b/DataStorage/Database/Query/Grammar/Grammar.php index 8d1027103..950e44332 100755 --- a/DataStorage/Database/Query/Grammar/Grammar.php +++ b/DataStorage/Database/Query/Grammar/Grammar.php @@ -305,13 +305,13 @@ class Grammar extends GrammarAbstract /** * Compile column query. * - * @param column $column Where query + * @param Column $column Where query * * @return string * * @since 1.0.0 */ - protected function compileColumnQuery(column $column) : string + protected function compileColumnQuery(Column $column) : string { return $column->toSql(); } diff --git a/DataStorage/Database/tableDefinition.json b/DataStorage/Database/tableDefinition.json index 4ba1e7fab..f8c3a07ff 100755 --- a/DataStorage/Database/tableDefinition.json +++ b/DataStorage/Database/tableDefinition.json @@ -3,15 +3,15 @@ "name": "[a-z\\_]+", "fields": { ".*": { - "name": "[a-z\\_]+", + "name": "[a-z0-9\\_]+", "type": "[A-Z0-9\\(\\)]+", ".*?default": ".*", ".*?null": "1|0", ".*?primary": "1|0", ".*?unique": "1|0", ".*?autoincrement": "1|0", - ".*?foreignTable": "[a-z\\_]+", - ".*?foreignKey": "[a-z\\_]+", + ".*?foreignTable": "[a-z0-9\\_]+", + ".*?foreignKey": "[a-z0-9\\_]+", ".*?annotations": ".*" } } diff --git a/DataStorage/Session/FileSessionHandler.php b/DataStorage/Session/CacheSessionHandler.php old mode 100755 new mode 100644 similarity index 64% rename from DataStorage/Session/FileSessionHandler.php rename to DataStorage/Session/CacheSessionHandler.php index 7261240d2..82491bdc0 --- a/DataStorage/Session/FileSessionHandler.php +++ b/DataStorage/Session/CacheSessionHandler.php @@ -14,8 +14,11 @@ declare(strict_types=1); namespace phpOMS\DataStorage\Session; +use phpOMS\DataStorage\Cache\Connection\ConnectionAbstract; +use phpOMS\DataStorage\Cache\CacheStatus; + /** - * File session handler. + * Cache session handler. * * @package phpOMS\DataStorage\Session * @license OMS License 1.0 @@ -24,30 +27,35 @@ namespace phpOMS\DataStorage\Session; * * @SuppressWarnings(PHPMD.Superglobals) */ -final class FileSessionHandler implements \SessionHandlerInterface, \SessionIdInterface +final class CacheSessionHandler implements \SessionHandlerInterface, \SessionIdInterface { /** - * File path for session + * Cache connection * - * @var string + * @var ConnectionAbstract * @since 1.0.0 */ - private string $savePath; + private ConnectionAbstract $con; + + /** + * Expiration time + * + * @var int + * @since 1.0.0 + */ + private int $expire = 3600; /** * Constructor * - * @param string $path Path of the session data + * @param ConnectionAbstract $con ConnectionAbstract * * @since 1.0.0 */ - public function __construct(string $path) + public function __construct(ConnectionAbstract $con, int $expire = 3600) { - $this->savePath = $path; - - if (\realpath($path) === false) { - \mkdir($path, 0755, true); - } + $this->con = $con; + $this->expire = $expire; } /** @@ -74,13 +82,15 @@ final class FileSessionHandler implements \SessionHandlerInterface, \SessionIdIn */ public function open(string $savePath, string $sessionName) : bool { - $this->savePath = $savePath; + if ($con->getStatus() !== CacheStatus::OK) { + $con->connect(); + } - return \is_dir($this->savePath); + return $con->getStatus() === CacheStatus::OK; } /** - * Close the session + * Closing the cache connection doesn't happen in here and must be implemented in the application * * @return bool * @@ -92,7 +102,7 @@ final class FileSessionHandler implements \SessionHandlerInterface, \SessionIdIn } /** - * Read the session data + * Read the session data (also prolongs the expire) * * @param string $id Session id * @@ -102,11 +112,15 @@ final class FileSessionHandler implements \SessionHandlerInterface, \SessionIdIn */ public function read(string $id) : string|false { - if (!\is_file($this->savePath . '/sess_' . $id)) { - return ''; + $data = $this->con->get($id); + + if ($data === null) { + return false; } - return \file_get_contents($this->savePath . '/sess_' . $id); + $this->con->updateExpire($this->expire); + + return $data; } /** @@ -121,7 +135,9 @@ final class FileSessionHandler implements \SessionHandlerInterface, \SessionIdIn */ public function write(string $id, string $data) : bool { - return \file_put_contents($this->savePath . '/sess_' . $id, $data) === false ? false : true; + $this->con->set($id, $data, -1); + + return true; } /** @@ -135,10 +151,7 @@ final class FileSessionHandler implements \SessionHandlerInterface, \SessionIdIn */ public function destroy(string $id) : bool { - $file = $this->savePath . '/sess_' . $id; - if (\is_file($file)) { - \unlink($file); - } + $this->con->delete($id); return true; } @@ -154,18 +167,6 @@ final class FileSessionHandler implements \SessionHandlerInterface, \SessionIdIn */ public function gc(int $maxlifetime) : int|false { - $files = \glob("{$this->savePath}/sess_*"); - - if ($files === false) { - return 0; - } - - foreach ($files as $file) { - if (\filemtime($file) + $maxlifetime < \time() && \is_file($file)) { - \unlink($file); - } - } - - return 1; + (int) $this->con->flush($maxlifetime); } } diff --git a/DataStorage/Session/FileSession.php b/DataStorage/Session/FileSession.php deleted file mode 100755 index a42689118..000000000 --- a/DataStorage/Session/FileSession.php +++ /dev/null @@ -1,219 +0,0 @@ - - * @since 1.0.0 - */ - private array $sessionData = []; - - /** - * Session ID. - * - * @var string - * @since 1.0.0 - */ - private string $sid; - - /** - * Inactivity Interval. - * - * @var int - * @since 1.0.0 - */ - private int $inactivityInterval = 0; - - /** - * Constructor. - * - * @param int $liftetime Session life time - * @param string $sid Session id - * @param int $inactivityInterval Interval for session activity - * - * @throws LockException throws this exception if the session is alrady locked for further interaction - * - * @since 1.0.0 - */ - public function __construct(int $liftetime = 3600, string $sid = '', int $inactivityInterval = 0) - { - if (\session_id()) { - \session_write_close(); // @codeCoverageIgnore - } - - if ($sid !== '') { - \session_id((string) $sid); - } - - $this->inactivityInterval = $inactivityInterval; - - if (\session_status() !== \PHP_SESSION_ACTIVE && !\headers_sent()) { - // @codeCoverageIgnoreStart - \session_set_cookie_params($liftetime, '/', '', false, true); - \session_start(); - // @codeCoverageIgnoreEnd - } - - if ($this->inactivityInterval > 0 - && ($this->inactivityInterval + ($_SESSION['lastActivity'] ?? 0) < \time()) - ) { - $this->destroy(); // @codeCoverageIgnore - } - - $this->sessionData = $_SESSION ?? []; - $_SESSION = null; - $this->sessionData['lastActivity'] = \time(); - $this->sid = (string) \session_id(); - } - - /** - * {@inheritdoc} - */ - public function set(string $key, mixed $value, bool $overwrite = false) : bool - { - if (!$this->isLocked && ($overwrite || !isset($this->sessionData[$key]))) { - $this->sessionData[$key] = $value; - - return true; - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function get(string $key) : mixed - { - return $this->sessionData[$key] ?? null; - } - - /** - * {@inheritdoc} - */ - public function lock() : void - { - $this->isLocked = true; - } - - /** - * Check if session is locked. - * - * @return bool Lock status - * - * @since 1.0.0 - */ - public function isLocked() : bool - { - return $this->isLocked; - } - - /** - * {@inheritdoc} - */ - public function save() : bool - { - if ($this->isLocked) { - return false; - } - - $_SESSION = $this->sessionData; - \session_write_close(); - - return true; - } - - /** - * {@inheritdoc} - */ - public function remove(string $key) : bool - { - if (!$this->isLocked && isset($this->sessionData[$key])) { - unset($this->sessionData[$key]); - - return true; - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function getSID() : string - { - return $this->sid; - } - - /** - * {@inheritdoc} - */ - public function setSID(string $sid) : void - { - $this->sid = $sid; - } - - /** - * Destroy the current session. - * - * @return void - * - * @since 1.0.0 - * @codeCoverageIgnore - */ - private function destroy() : void - { - if (\session_status() !== \PHP_SESSION_NONE) { - \session_destroy(); - $this->sessionData = []; - \session_start(); - } - } - - /** - * Destruct session. - * - * @since 1.0.0 - * @codeCoverageIgnore - */ - public function __destruct() - { - $this->save(); - } -} diff --git a/DataStorage/Session/HttpSession.php b/DataStorage/Session/HttpSession.php index 3f7fc29df..9a239dc2c 100755 --- a/DataStorage/Session/HttpSession.php +++ b/DataStorage/Session/HttpSession.php @@ -98,7 +98,9 @@ final class HttpSession implements SessionInterface // @codeCoverageIgnoreEnd } - if ($this->inactivityInterval > 0 && ($this->inactivityInterval + ($_SESSION['lastActivity'] ?? 0) < \time())) { + if ($this->inactivityInterval > 0 + && ($this->inactivityInterval + ($_SESSION['lastActivity'] ?? 0) < \time()) + ) { $this->destroy(); // @codeCoverageIgnore } diff --git a/DataStorage/Session/README.md b/DataStorage/Session/README.md deleted file mode 100755 index 7068b1076..000000000 --- a/DataStorage/Session/README.md +++ /dev/null @@ -1 +0,0 @@ -# Session # diff --git a/Localization/BaseStringL11n.php b/Localization/BaseStringL11n.php new file mode 100644 index 000000000..85c5c182a --- /dev/null +++ b/Localization/BaseStringL11n.php @@ -0,0 +1,183 @@ +content = $content; + $this->language = $language; + $this->country = $country; + } + + /** + * Get id + * + * @return int + * + * @since 1.0.0 + */ + public function getId() : int + { + return $this->id; + } + + /** + * Get language + * + * @return string + * + * @since 1.0.0 + */ + public function getLanguage() : string + { + return $this->language; + } + + /** + * Set language + * + * @param string $language Language + * + * @return void + * + * @since 1.0.0 + */ + public function setLanguage(string $language) : void + { + $this->language = $language; + } + + /** + * Get country + * + * @return string + * + * @since 1.0.0 + */ + public function getCountry() : string + { + return $this->country; + } + + /** + * Set country + * + * @param string $country Country + * + * @return void + * + * @since 1.0.0 + */ + public function setCountry(string $country) : void + { + $this->country = $country; + } + + /** + * {@inheritdoc} + */ + public function toArray() : array + { + return [ + 'id' => $this->id, + 'content' => $this->content, + 'ref' => $this->ref, + 'language' => $this->language, + 'country' => $this->country, + ]; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return $this->toArray(); + } +} diff --git a/Localization/NullBaseStringL11n.php b/Localization/NullBaseStringL11n.php new file mode 100644 index 000000000..b25f607d0 --- /dev/null +++ b/Localization/NullBaseStringL11n.php @@ -0,0 +1,47 @@ +id = $id; + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return ['id' => $this->id]; + } +} diff --git a/Message/RequestAbstract.php b/Message/RequestAbstract.php index eb4411a9c..c58fbab6a 100755 --- a/Message/RequestAbstract.php +++ b/Message/RequestAbstract.php @@ -109,6 +109,8 @@ abstract class RequestAbstract implements MessageInterface return (float) $this->data[$key]; case 'bool': return (bool) $this->data[$key]; + case 'DateTime': + return new \DateTime($this->data[$key]); default: return $this->data[$key]; } diff --git a/Security/Guard.php b/Security/Guard.php new file mode 100644 index 000000000..c49129120 --- /dev/null +++ b/Security/Guard.php @@ -0,0 +1,55 @@ + 2); + + if (\count(\scandir($path)) > 2) { + return false; } \rmdir($path); diff --git a/Utils/ImageUtils.php b/Utils/ImageUtils.php index 56a0c5237..8a9de15e0 100755 --- a/Utils/ImageUtils.php +++ b/Utils/ImageUtils.php @@ -112,10 +112,16 @@ final class ImageUtils */ public static function resize(string $srcPath, string $dstPath, int $width, int $height, bool $crop = false) : void { + if (!\is_file($srcPath)) { + return; + } + /** @var array $imageDim */ $imageDim = \getimagesize($srcPath); - if (($imageDim[0] ?? -1) >= $width && ($imageDim[1] ?? -1) >= $height) { + if ((($imageDim[0] ?? -1) >= $width && ($imageDim[1] ?? -1) >= $height) + || ($imageDim[0] === 0 || $imageDim[1] === 0) + ) { return; } @@ -158,5 +164,133 @@ final class ImageUtils } elseif (\stripos($srcPath, '.gif')) { \imagegif($dst, $dstPath); } + + \imagedestroy($src); + \imagedestroy($dst); + } + + /** + * Get difference between two images + * + * @param string $img1 Path to first image + * @param string $img2 Path to second image + * @param string $out Output path for difference image (empty = no difference image is created) + * @param int $diff Difference image type (0 = only show differences of img2, 1 = make differences red/green colored) + * + * @return int Amount of pixel differences + * + */ + public static function difference(string $img1, string $img2, string $out = '', int $diff = 0) : int + { + $src1 = null; + if (\stripos($img1, '.jpg') !== false || \stripos($img1, '.jpeg') !== false) { + $src1 = \imagecreatefromjpeg($img1); + } elseif (\stripos($img1, '.png') !== false) { + $src1 = \imagecreatefrompng($img1); + } elseif (\stripos($img1, '.gif') !== false) { + $src1 = \imagecreatefromgif($img1); + } + + $src2 = null; + if (\stripos($img2, '.jpg') !== false || \stripos($img2, '.jpeg') !== false) { + $src2 = \imagecreatefromjpeg($img2); + } elseif (\stripos($img2, '.png') !== false) { + $src2 = \imagecreatefrompng($img2); + } elseif (\stripos($img2, '.gif') !== false) { + $src2 = \imagecreatefromgif($img2); + } + + if ($src1 === null || $src2 === null) { + return 0; + } + + $imageDim1 = [\imagesx($src1), \imagesy($src1)]; + $imageDim2 = [\imagesx($src2), \imagesy($src2)]; + + $newDim = [\max($imageDim1[0], $imageDim2[0]), \max($imageDim1[1], $imageDim2[1])]; + + $diff = empty($out) ? -1 : $out; + + if ($diff !== -1) { + $dst = $diff === 0 + ? \imagecreatetruecolor($newDim[0], $newDim[1]) + : \imagecrop($src2, ['x' => 0, 'y' => 0, 'width' => $imageDim2[0], 'height' => $imageDim2[1]]); + + $alpha = \imagecolorallocatealpha($dst, 255, 255, 255, 127); + if ($diff === 0) { + \imagefill($dst, 0, 0, $alpha); + } + + $red = \imagecolorallocate($dst, 255, 0, 0); + $green = \imagecolorallocate($dst, 0, 255, 0); + } + + $difference = 0; + + for ($i = 0; $i < $newDim[0]; ++$i) { + for ($j = 0; $j < $newDim[1]; ++$j) { + if ($i >= $imageDim1[0] || $j >= $imageDim1[1]) { + if ($diff === 0) { + \imagesetpixel($dst, $i, $j, $green); + } elseif ($diff === 1) { + if ($i >= $imageDim2[0] || $j >= $imageDim2[1]) { + \imagesetpixel($dst, $i, $j, $green); + } else { + $color2 = \imagecolerat($src2, $i, $j); + \imagesetpixel($dst, $i, $j, $color2); + } + } + + ++$difference; + continue; + } + + if ($i >= $imageDim2[0] || $j >= $imageDim2[1]) { + if ($diff === 0) { + \imagesetpixel($dst, $i, $j, $red); + } elseif ($diff === 1) { + if ($i >= $imageDim1[0] || $j >= $imageDim1[1]) { + \imagesetpixel($dst, $i, $j, $red); + } else { + $color1 = \imagecolerat($src1, $i, $j); + \imagesetpixel($dst, $i, $j, $color1); + } + } + + ++$difference; + continue; + } + + $color1 = \imagecolerat($src1, $i, $j); + $color2 = \imagecolerat($src2, $i, $j); + + if ($color1 !== $color2) { + ++$difference; + + if ($diff === 0) { + \imagesetpixel($dst, $i, $j, $color2); + } elseif ($diff === 1) { + \imagesetpixel($dst, $i, $j, $green); + } + } + } + } + + if ($diff !== -1) { + if (\stripos($out, '.jpg') || \stripos($out, '.jpeg')) { + \imagejpeg($dst, $out); + } elseif (\stripos($out, '.png')) { + \imagesavealpha($dst, true); + \imagepng($dst, $out); + } elseif (\stripos($out, '.gif')) { + \imagegif($dst, $out); + } + + \imagedestroy($src1); + \imagedestroy($src2); + \imagedestroy($dest); + } + + return $difference; } } diff --git a/Utils/Parser/Document/DocumentParser.php b/Utils/Parser/Document/DocumentParser.php new file mode 100644 index 000000000..43b3dea8f --- /dev/null +++ b/Utils/Parser/Document/DocumentParser.php @@ -0,0 +1,57 @@ +getContent(); + } elseif ($output === 'pdf') { + $doc = IOFactory::load($path); + + $writer = new DocumentWriter($doc); + + return $writer->toPdfString(); + } + + return ''; + } +} diff --git a/Utils/Parser/Document/DocumentWriter.php b/Utils/Parser/Document/DocumentWriter.php new file mode 100644 index 000000000..272493429 --- /dev/null +++ b/Utils/Parser/Document/DocumentWriter.php @@ -0,0 +1,53 @@ +_setPageSize($paperSize, $orientation); + $pdf->addPage($orientation); + + // Write document properties + $phpWord = $this->getPhpWord(); + $docProps = $phpWord->getDocInfo(); + $pdf->setTitle($docProps->getTitle()); + $pdf->setAuthor($docProps->getCreator()); + $pdf->setSubject($docProps->getSubject()); + $pdf->setKeywords($docProps->getKeywords()); + $pdf->setCreator($docProps->getCreator()); + + $pdf->writeHTML($this->getContent()); + + // Write to file + return $pdf->output($filename, 'S'); + } +} diff --git a/Utils/Parser/Presentation/PresentationParser.php b/Utils/Parser/Presentation/PresentationParser.php new file mode 100644 index 000000000..b981bdf40 --- /dev/null +++ b/Utils/Parser/Presentation/PresentationParser.php @@ -0,0 +1,52 @@ +load(...); + $presentation = IOFactory::load($path); + + $oTree = new PresentationWriter($presentation); + + return $oTree->renderHtml(); + } + + return ''; + } +} diff --git a/Utils/Parser/Presentation/PresentationWriter.php b/Utils/Parser/Presentation/PresentationWriter.php new file mode 100644 index 000000000..c3f3a203b --- /dev/null +++ b/Utils/Parser/Presentation/PresentationWriter.php @@ -0,0 +1,317 @@ +oPhpPresentation = $oPHPPpt; + } + + public function renderHtml() + { + $this->append('