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('
'); + $this->append('
'); + $this->append('
'); + $this->append('
'); + $this->append('
    '); + $this->displayPhpPresentation($this->oPhpPresentation); + $this->append('
'); + $this->append('
'); + $this->append('
'); + $this->append('
'); + $this->displayPhpPresentationInfo($this->oPhpPresentation); + $this->append('
'); + $this->append('
'); + $this->append('
'); + + return $this->htmlOutput; + } + + protected function append($sHTML) + { + $this->htmlOutput .= $sHTML; + } + + protected function displayPhpPresentation(PhpPresentation $oPHPPpt) + { + $this->append('
  • PhpPresentation'); + $this->append(''); + $this->append('
  • '); + } + + protected function displayShape(AbstractShape $shape) + { + if ($shape instanceof Drawing\Gd) { + $this->append('
  • Shape "Drawing\Gd"
  • '); + } elseif ($shape instanceof Drawing\File) { + $this->append('
  • Shape "Drawing\File"
  • '); + } elseif ($shape instanceof Drawing\Base64) { + $this->append('
  • Shape "Drawing\Base64"
  • '); + } elseif ($shape instanceof Drawing\ZipFile) { + $this->append('
  • Shape "Drawing\Zip"
  • '); + } elseif ($shape instanceof RichText) { + $this->append('
  • Shape "RichText"
  • '); + } else { + var_dump($shape); + } + } + + protected function displayPhpPresentationInfo(PhpPresentation $oPHPPpt) + { + $this->append('
    '); + $this->append('
    '); + $this->append('
    Number of slides
    ' . $oPHPPpt->getSlideCount() . '
    '); + $this->append('
    Document Layout Name
    ' . (empty($oPHPPpt->getLayout()->getDocumentLayout()) ? 'Custom' : $oPHPPpt->getLayout()->getDocumentLayout()) . '
    '); + $this->append('
    Document Layout Height
    ' . $oPHPPpt->getLayout()->getCY(DocumentLayout::UNIT_MILLIMETER) . ' mm
    '); + $this->append('
    Document Layout Width
    ' . $oPHPPpt->getLayout()->getCX(DocumentLayout::UNIT_MILLIMETER) . ' mm
    '); + $this->append('
    Properties : Category
    ' . $oPHPPpt->getDocumentProperties()->getCategory() . '
    '); + $this->append('
    Properties : Company
    ' . $oPHPPpt->getDocumentProperties()->getCompany() . '
    '); + $this->append('
    Properties : Created
    ' . $oPHPPpt->getDocumentProperties()->getCreated() . '
    '); + $this->append('
    Properties : Creator
    ' . $oPHPPpt->getDocumentProperties()->getCreator() . '
    '); + $this->append('
    Properties : Description
    ' . $oPHPPpt->getDocumentProperties()->getDescription() . '
    '); + $this->append('
    Properties : Keywords
    ' . $oPHPPpt->getDocumentProperties()->getKeywords() . '
    '); + $this->append('
    Properties : Last Modified By
    ' . $oPHPPpt->getDocumentProperties()->getLastModifiedBy() . '
    '); + $this->append('
    Properties : Modified
    ' . $oPHPPpt->getDocumentProperties()->getModified() . '
    '); + $this->append('
    Properties : Subject
    ' . $oPHPPpt->getDocumentProperties()->getSubject() . '
    '); + $this->append('
    Properties : Title
    ' . $oPHPPpt->getDocumentProperties()->getTitle() . '
    '); + $this->append('
    '); + $this->append('
    '); + + foreach ($oPHPPpt->getAllSlides() as $oSlide) { + $this->append('
    '); + $this->append('
    '); + $this->append('
    HashCode
    ' . $oSlide->getHashCode() . '
    '); + $this->append('
    Slide Layout
    Layout::' . $this->getConstantName('\PhpOffice\PhpPresentation\Slide\Layout', $oSlide->getSlideLayout()) . '
    '); + + $this->append('
    Offset X
    ' . $oSlide->getOffsetX() . '
    '); + $this->append('
    Offset Y
    ' . $oSlide->getOffsetY() . '
    '); + $this->append('
    Extent X
    ' . $oSlide->getExtentX() . '
    '); + $this->append('
    Extent Y
    ' . $oSlide->getExtentY() . '
    '); + $oBkg = $oSlide->getBackground(); + if ($oBkg instanceof Slide\AbstractBackground) { + if ($oBkg instanceof Slide\Background\Color) { + $this->append('
    Background Color
    #' . $oBkg->getColor()->getRGB() . '
    '); + } + if ($oBkg instanceof Slide\Background\Image) { + $sBkgImgContents = file_get_contents($oBkg->getPath()); + $this->append('
    Background Image
    '); + } + } + $oNote = $oSlide->getNote(); + if ($oNote->getShapeCollection()->count() > 0) { + $this->append('
    Notes
    '); + foreach ($oNote->getShapeCollection() as $oShape) { + if ($oShape instanceof RichText) { + $this->append('
    ' . $oShape->getPlainText() . '
    '); + } + } + } + + $this->append('
    '); + $this->append('
    '); + + foreach ($oSlide->getShapeCollection() as $oShape) { + if ($oShape instanceof Group) { + foreach ($oShape->getShapeCollection() as $oShapeChild) { + $this->displayShapeInfo($oShapeChild); + } + } else { + $this->displayShapeInfo($oShape); + } + } + } + } + + protected function displayShapeInfo(AbstractShape $oShape) + { + $this->append('
    '); + $this->append('
    '); + $this->append('
    HashCode
    ' . $oShape->getHashCode() . '
    '); + $this->append('
    Offset X
    ' . $oShape->getOffsetX() . '
    '); + $this->append('
    Offset Y
    ' . $oShape->getOffsetY() . '
    '); + $this->append('
    Height
    ' . $oShape->getHeight() . '
    '); + $this->append('
    Width
    ' . $oShape->getWidth() . '
    '); + $this->append('
    Rotation
    ' . $oShape->getRotation() . '°
    '); + $this->append('
    Hyperlink
    ' . ucfirst(var_export($oShape->hasHyperlink(), true)) . '
    '); + $this->append('
    Fill
    '); + if (is_null($oShape->getFill())) { + $this->append('
    None
    '); + } else { + switch ($oShape->getFill()->getFillType()) { + case \PhpOffice\PhpPresentation\Style\Fill::FILL_NONE: + $this->append('
    None
    '); + break; + case \PhpOffice\PhpPresentation\Style\Fill::FILL_SOLID: + $this->append('
    Solid ('); + $this->append('Color : #' . $oShape->getFill()->getStartColor()->getRGB()); + $this->append(' - Alpha : ' . $oShape->getFill()->getStartColor()->getAlpha() . '%'); + $this->append(')
    '); + break; + } + } + $this->append('
    Border
    @Todo
    '); + $this->append('
    IsPlaceholder
    ' . ($oShape->isPlaceholder() ? 'true' : 'false') . '
    '); + if ($oShape instanceof Drawing\Gd) { + $this->append('
    Name
    ' . $oShape->getName() . '
    '); + $this->append('
    Description
    ' . $oShape->getDescription() . '
    '); + ob_start(); + call_user_func($oShape->getRenderingFunction(), $oShape->getImageResource()); + $sShapeImgContents = ob_get_contents(); + ob_end_clean(); + $this->append('
    Mime-Type
    ' . $oShape->getMimeType() . '
    '); + $this->append('
    Image
    '); + if ($oShape->hasHyperlink()) { + $this->append('
    Hyperlink URL
    ' . $oShape->getHyperlink()->getUrl() . '
    '); + $this->append('
    Hyperlink Tooltip
    ' . $oShape->getHyperlink()->getTooltip() . '
    '); + } + } elseif ($oShape instanceof Drawing\AbstractDrawingAdapter) { + $this->append('
    Name
    ' . $oShape->getName() . '
    '); + $this->append('
    Description
    ' . $oShape->getDescription() . '
    '); + } elseif ($oShape instanceof RichText) { + $this->append('
    # of paragraphs
    ' . count($oShape->getParagraphs()) . '
    '); + $this->append('
    Inset (T / R / B / L)
    ' . $oShape->getInsetTop() . 'px / ' . $oShape->getInsetRight() . 'px / ' . $oShape->getInsetBottom() . 'px / ' . $oShape->getInsetLeft() . 'px
    '); + $this->append('
    Text
    '); + $this->append('
    '); + foreach ($oShape->getParagraphs() as $oParagraph) { + $this->append('Paragraph
    '); + $this->append('
    Alignment Horizontal
    Alignment::' . $this->getConstantName('\PhpOffice\PhpPresentation\Style\Alignment', $oParagraph->getAlignment()->getHorizontal()) . '
    '); + $this->append('
    Alignment Vertical
    Alignment::' . $this->getConstantName('\PhpOffice\PhpPresentation\Style\Alignment', $oParagraph->getAlignment()->getVertical()) . '
    '); + $this->append('
    Alignment Margin (L / R)
    ' . $oParagraph->getAlignment()->getMarginLeft() . ' px / ' . $oParagraph->getAlignment()->getMarginRight() . 'px
    '); + $this->append('
    Alignment Indent
    ' . $oParagraph->getAlignment()->getIndent() . ' px
    '); + $this->append('
    Alignment Level
    ' . $oParagraph->getAlignment()->getLevel() . '
    '); + $this->append('
    Bullet Style
    Bullet::' . $this->getConstantName('\PhpOffice\PhpPresentation\Style\Bullet', $oParagraph->getBulletStyle()->getBulletType()) . '
    '); + if (Bullet::TYPE_NONE != $oParagraph->getBulletStyle()->getBulletType()) { + $this->append('
    Bullet Font
    ' . $oParagraph->getBulletStyle()->getBulletFont() . '
    '); + $this->append('
    Bullet Color
    ' . $oParagraph->getBulletStyle()->getBulletColor()->getARGB() . '
    '); + } + if (Bullet::TYPE_BULLET == $oParagraph->getBulletStyle()->getBulletType()) { + $this->append('
    Bullet Char
    ' . $oParagraph->getBulletStyle()->getBulletChar() . '
    '); + } + if (Bullet::TYPE_NUMERIC == $oParagraph->getBulletStyle()->getBulletType()) { + $this->append('
    Bullet Start At
    ' . $oParagraph->getBulletStyle()->getBulletNumericStartAt() . '
    '); + $this->append('
    Bullet Style
    ' . $oParagraph->getBulletStyle()->getBulletNumericStyle() . '
    '); + } + $this->append('
    Line Spacing
    ' . $oParagraph->getLineSpacing() . '
    '); + $this->append('
    RichText
    '); + foreach ($oParagraph->getRichTextElements() as $oRichText) { + if ($oRichText instanceof BreakElement) { + $this->append('
    Break
    '); + } else { + if ($oRichText instanceof TextElement) { + $this->append('
    TextElement
    '); + } else { + $this->append('
    Run
    '); + } + $this->append('
    ' . $oRichText->getText()); + $this->append('
    '); + $this->append('
    Font Name
    ' . $oRichText->getFont()->getName() . '
    '); + $this->append('
    Font Size
    ' . $oRichText->getFont()->getSize() . '
    '); + $this->append('
    Font Color
    #' . $oRichText->getFont()->getColor()->getARGB() . '
    '); + $this->append('
    Font Transform
    '); + $this->append('Bold : ' . ($oRichText->getFont()->isBold() ? 'Y' : 'N') . ' - '); + $this->append('Italic : ' . ($oRichText->getFont()->isItalic() ? 'Y' : 'N') . ' - '); + $this->append('Underline : Underline::' . $this->getConstantName('\PhpOffice\PhpPresentation\Style\Font', $oRichText->getFont()->getUnderline()) . ' - '); + $this->append('Strikethrough : ' . ($oRichText->getFont()->isStrikethrough() ? 'Y' : 'N') . ' - '); + $this->append('SubScript : ' . ($oRichText->getFont()->isSubScript() ? 'Y' : 'N') . ' - '); + $this->append('SuperScript : ' . ($oRichText->getFont()->isSuperScript() ? 'Y' : 'N')); + $this->append('
    '); + if ($oRichText instanceof TextElement) { + if ($oRichText->hasHyperlink()) { + $this->append('
    Hyperlink URL
    ' . $oRichText->getHyperlink()->getUrl() . '
    '); + $this->append('
    Hyperlink Tooltip
    ' . $oRichText->getHyperlink()->getTooltip() . '
    '); + } + } + $this->append('
    '); + $this->append('
    '); + } + } + $this->append('
    '); + } + $this->append('
    '); + } else { + // Add another shape + } + $this->append('
    '); + $this->append('
    '); + } + + protected function getConstantName($class, $search, $startWith = '') + { + $fooClass = new \ReflectionClass($class); + $constants = $fooClass->getConstants(); + $constName = null; + foreach ($constants as $key => $value) { + if ($value == $search) { + if (empty($startWith) || (!empty($startWith) && 0 === strpos($key, $startWith))) { + $constName = $key; + } + break; + } + } + + return $constName; + } +} diff --git a/Utils/Parser/Spreadsheet/SpreadsheetParser.php b/Utils/Parser/Spreadsheet/SpreadsheetParser.php new file mode 100644 index 000000000..874efe468 --- /dev/null +++ b/Utils/Parser/Spreadsheet/SpreadsheetParser.php @@ -0,0 +1,71 @@ +getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + $csv[] = $spreadsheet->getSheet($i)->toArray(null, true, true, true); + } + + return \json_encode($csv); + } elseif ($output === 'pdf') { + $spreadsheet = IOFactory::load($path); + + $spreadsheet->getActiveSheet()->setShowGridLines(false); + $spreadsheet->getActiveSheet()->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); + + IOFactory::registerWriter('custom', \phpOMS\Utils\Parser\Spreadsheet\SpreadsheetWriter::class); + $writer = IOFactory::createWriter($spreadsheet, 'custom'); + + return $writer->toPdfString(); + } elseif ($output === 'html') { + $spreadsheet = IOFactory::load($path); + + IOFactory::registerWriter('custom', \phpOMS\Utils\Parser\Spreadsheet\SpreadsheetWriter::class); + $writer = IOFactory::createWriter($spreadsheet, 'custom'); + + return $writer->generateHtmlAll(); + } + + return ''; + } +} diff --git a/Utils/Parser/Spreadsheet/SpreadsheetWriter.php b/Utils/Parser/Spreadsheet/SpreadsheetWriter.php new file mode 100644 index 000000000..f68582d04 --- /dev/null +++ b/Utils/Parser/Spreadsheet/SpreadsheetWriter.php @@ -0,0 +1,91 @@ +spreadsheet->getSheet($this->getSheetIndex() ?? 0)->getPageSetup(); + $orientation = $this->getOrientation() ?? $setup->getOrientation(); + $orientation = ($orientation === PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; + $printPaperSize = $this->getPaperSize() ?? $setup->getPaperSize(); + $paperSize = self::$paperSizes[$printPaperSize] ?? PageSetup::getPaperSizeDefault(); + + $ortmp = $orientation; + $pdf->_setPageSize($paperSize, $ortmp); + $pdf->DefOrientation = $orientation; + $pdf->AddPageByArray([ + 'orientation' => $orientation, + 'margin-left' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getLeft()), + 'margin-right' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getRight()), + 'margin-top' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getTop()), + 'margin-bottom' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getBottom()), + ]); + + // Document info + $pdf->SetTitle($this->spreadsheet->getProperties()->getTitle()); + $pdf->SetAuthor($this->spreadsheet->getProperties()->getCreator()); + $pdf->SetSubject($this->spreadsheet->getProperties()->getSubject()); + $pdf->SetKeywords($this->spreadsheet->getProperties()->getKeywords()); + $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator()); + + $html = $this->generateHTMLAll(); + $bodyLocation = strpos($html, Html::BODY_LINE); + // Make sure first data presented to Mpdf includes body tag + // so that Mpdf doesn't parse it as content. Issue 2432. + if ($bodyLocation !== false) { + $bodyLocation += strlen(Html::BODY_LINE); + $pdf->WriteHTML(substr($html, 0, $bodyLocation)); + $html = substr($html, $bodyLocation); + } + foreach (\array_chunk(\explode(PHP_EOL, $html), 1000) as $lines) { + $pdf->WriteHTML(\implode(PHP_EOL, $lines)); + } + + $html = $pdf->Output('', 'S'); + + parent::restoreStateAfterSave(); + + return $html; + } + + /** + * Convert inches to mm. + * + * @param float $inches + * + * @return float + */ + private function inchesToMm($inches) + { + return $inches * 25.4; + } +} diff --git a/tests/DataStorage/Session/FileSessionHandlerTest.php b/tests/DataStorage/Session/FileSessionHandlerTest.php deleted file mode 100755 index 6198f5bc7..000000000 --- a/tests/DataStorage/Session/FileSessionHandlerTest.php +++ /dev/null @@ -1,133 +0,0 @@ -sh = new FileSessionHandler(__DIR__ . '/test'); - } - - protected function tearDown() : void - { - if (\is_dir(__DIR__ . '/test')) { - Directory::delete(__DIR__ . '/test'); - } - } - - /** - * @testdox A session id can be generated - * @group framework - */ - public function testCreateSid() : void - { - self::assertMatchesRegularExpression('/^s\-[a-z0-9]+/', $this->sh->create_sid()); - } - - /** - * @testdox The session path can be accessed - * @group framework - */ - public function testSessionPath() : void - { - self::assertTrue($this->sh->open(__DIR__ . '/test', '')); - } - - /** - * @testdox A invalid session path cannot be accessed - * @group framework - */ - public function testInvalidSessionPath() : void - { - self::assertFalse($this->sh->open(__DIR__ . '/invalid', '')); - } - - /** - * @testdox A session can be closed - * @group framework - */ - public function testSessionClose() : void - { - self::assertTrue($this->sh->close()); - } - - /** - * @testdox A valid session id can store and return data - * @group framework - */ - public function testSessionInputOutput() : void - { - $id = $this->sh->create_sid(); - self::assertTrue($this->sh->write($id, 'test')); - self::assertEquals('test', $this->sh->read($id)); - } - - /** - * @testdox A invalid session id doesn't return any data - * @group framework - */ - public function testReadInvalidSessionId() : void - { - self::assertEquals('', $this->sh->read('invalid')); - } - - /** - * @testdox A session can be destroyed - * @group framework - */ - public function testSessionDestroy() : void - { - $id = $this->sh->create_sid(); - self::assertTrue($this->sh->write($id, 'test')); - self::assertEquals('test', $this->sh->read($id)); - - $this->sh->destroy($id); - self::assertEquals('', $this->sh->read($id)); - } - - /** - * @testdox Sessions can be removed based on a timeout - * @group framework - */ - public function testSessionTimeoutDestroy() : void - { - $id = $this->sh->create_sid(); - self::assertTrue($this->sh->write($id, 'test')); - self::assertEquals('test', $this->sh->read($id)); - - \sleep(2); - - $this->sh->gc(0); - self::assertEquals('', $this->sh->read($id)); - } -} diff --git a/tests/DataStorage/Session/FileSessionTest.php b/tests/DataStorage/Session/FileSessionTest.php deleted file mode 100755 index bf001141a..000000000 --- a/tests/DataStorage/Session/FileSessionTest.php +++ /dev/null @@ -1,159 +0,0 @@ -session = new FileSession(); - } - - /** - * @testdox The session has the expected default values after initialization - * @group framework - */ - public function testDefault() : void - { - self::assertNull($this->session->get('key')); - self::assertFalse($this->session->isLocked()); - } - - /** - * @testdox Session data can be set and returned - * @group framework - */ - public function testInputOutput() : void - { - self::assertTrue($this->session->set('test', 'value')); - self::assertEquals('value', $this->session->get('test')); - } - - /** - * @testdox Session data cannot be overwritten - * @group framework - */ - public function testInvalidOverwrite() : void - { - $this->session->set('test', 'value'); - self::assertFalse($this->session->set('test', 'value2')); - self::assertEquals('value', $this->session->get('test')); - } - - /** - * @testdox Session data can be forced to overwrite - * @group framework - */ - public function testOverwrite() : void - { - $this->session->set('test', 'value'); - self::assertTrue($this->session->set('test', 'value2', true)); - self::assertEquals('value2', $this->session->get('test')); - } - - /** - * @testdox Session data can be removed - * @group framework - */ - public function testRemove() : void - { - $this->session->set('test', 'value'); - self::assertTrue($this->session->remove('test')); - } - - /** - * @testdox None-existing session data cannot be removed - * @group framework - */ - public function testInvalidRemove() : void - { - $this->session->set('test', 'value'); - $this->session->remove('test'); - - self::assertFalse($this->session->remove('test')); - } - - /** - * @testdox A session id can be set and returned - * @group framework - */ - public function testSessionIdInputOutput() : void - { - $this->session->setSID('abc'); - self::assertEquals('abc', $this->session->getSID()); - } - - /** - * @testdox A session can be locked - * @group framework - */ - public function testLockInputOutput() : void - { - $this->session->lock(); - self::assertTrue($this->session->isLocked()); - } - - /** - * @testdox Session data can be saved - * @group framework - */ - public function testSave() : void - { - self::assertTrue($this->session->save()); - } - - /** - * @testdox Locked sessions cannot be saved - * @group framework - */ - public function testInvalidLockSave() : void - { - $this->session->lock(); - self::assertFalse($this->session->save()); - } - - /** - * @testdox A locked session cannot add or change data - * @group framework - */ - public function testLockInvalidSet() : void - { - $this->session->lock(); - self::assertFalse($this->session->set('test', 'value')); - } - - /** - * @testdox A locked session cannot remove data - * @group framework - */ - public function testLockInvalidRemove() : void - { - self::assertTrue($this->session->set('test', 'value')); - $this->session->lock(); - self::assertFalse($this->session->remove('test')); - } -}