diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml index adb87160f..75cb75972 100755 --- a/.github/workflows/greetings.yml +++ b/.github/workflows/greetings.yml @@ -9,5 +9,5 @@ jobs: - uses: actions/first-interaction@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - issue-message: 'Thank you for createing this issue. We will check it as soon as possible.' + issue-message: 'Thank you for creating this issue. We will check it as soon as possible.' pr-message: 'Thank you for your pull request. We will check it as soon as possible.' diff --git a/Account/Account.php b/Account/Account.php index 0e051d9ed..40714ce2c 100755 --- a/Account/Account.php +++ b/Account/Account.php @@ -392,9 +392,32 @@ class Account implements \JsonSerializable 'permissions' => $this->permissions, 'type' => $this->type, 'status' => $this->status, + 'l11n' => $this->l11n, ]; } + public function from (array $account) : void + { + $this->id = $account['id']; + $this->name1 = $account['name'][0]; + $this->name2 = $account['name'][1]; + $this->name3 = $account['name'][2]; + $this->email = $account['email']; + $this->login = $account['login']; + $this->type = $account['type']; + $this->status = $account['status']; + + $this->l11n = Localization::fromJson($account['l11n']); + + foreach (($account['groups'] ?? []) as $group) { + $this->groups[] = Group::fromJson($group); + } + + foreach (($account['permissions'] ?? []) as $permission) { + $this->permissions[] = PermissionAbstract::fromJson($permission); + } + } + /** * {@inheritdoc} */ diff --git a/Account/Group.php b/Account/Group.php index 4c93edf9e..30f08eac9 100755 --- a/Account/Group.php +++ b/Account/Group.php @@ -109,6 +109,8 @@ class Group implements \JsonSerializable 'description' => $this->description, 'permissions' => $this->permissions, 'members' => $this->members, + 'parents' => $this->parents, + 'status' => $this->status, ]; } @@ -127,4 +129,22 @@ class Group implements \JsonSerializable { return $this->toArray(); } + + public static function fromJson(array $group) : self + { + $new = new self(); + + $new->id = $group['id']; + $new->name = $group['name']; + $new->description = $group['description']; + $new->members = $group['members']; + $new->parents = $group['parents']; + $new->status = $group['status']; + + foreach (($group['permissions'] ?? []) as $permission) { + $new->permissions[] = PermissionAbstract::fromJson($permission); + } + + return $new; + } } diff --git a/Account/PermissionAbstract.php b/Account/PermissionAbstract.php index 5e1f52bcd..0e5b442e8 100755 --- a/Account/PermissionAbstract.php +++ b/Account/PermissionAbstract.php @@ -360,7 +360,36 @@ class PermissionAbstract implements \JsonSerializable 'category' => $this->category, 'element' => $this->element, 'component' => $this->component, - 'permission' => $this->getPermission(), + 'hasRead' => $this->hasRead, + 'hasModify' => $this->hasModify, + 'hasCreate' => $this->hasCreate, + 'hasDelete' => $this->hasDelete, + 'defaultCPermissions' => $this->defaultCPermissions, + 'hasPermission' => $this->hasPermission, + 'defaultPPermissions' => $this->defaultPPermissions, ]; } + + public static function fromJson(array $permission) : self + { + $new = new self(); + + $new->id = $permission['id']; + $new->unit = $permission['unit']; + $new->app = $permission['app']; + $new->module = $permission['module']; + $new->from = $permission['from']; + $new->category = $permission['category']; + $new->element = $permission['element']; + $new->component = $permission['component']; + $new->hasRead = $permission['hasRead']; + $new->hasModify = $permission['hasModify']; + $new->hasCreate = $permission['hasCreate']; + $new->hasDelete = $permission['hasDelete']; + $new->defaultCPermissions = $permission['defaultCPermissions']; + $new->hasPermission = $permission['hasPermission']; + $new->defaultPPermissions = $permission['defaultPPermissions']; + + return $new; + } } diff --git a/DataStorage/Cache/CachePool.php b/DataStorage/Cache/CachePool.php index b3bacc825..2cb9a102a 100755 --- a/DataStorage/Cache/CachePool.php +++ b/DataStorage/Cache/CachePool.php @@ -14,6 +14,7 @@ declare(strict_types=1); namespace phpOMS\DataStorage\Cache; +use phpOMS\DataStorage\Cache\Connection\ConnectionAbstract; use phpOMS\DataStorage\Cache\Connection\ConnectionFactory; use phpOMS\DataStorage\Cache\Connection\NullCache; use phpOMS\DataStorage\DataStorageConnectionInterface; @@ -85,11 +86,11 @@ final class CachePool implements DataStoragePoolInterface * * @param string $key Cache to request * - * @return DataStorageConnectionInterface + * @return ConnectionAbstract * * @since 1.0.0 */ - public function get(string $key = '') : DataStorageConnectionInterface + public function get(string $key = '') : ConnectionAbstract { if ((!empty($key) && !isset($this->pool[$key])) || empty($this->pool)) { return new NullCache(); diff --git a/DataStorage/Cache/Connection/ConnectionFactory.php b/DataStorage/Cache/Connection/ConnectionFactory.php index c08a87b3a..1dafbb888 100755 --- a/DataStorage/Cache/Connection/ConnectionFactory.php +++ b/DataStorage/Cache/Connection/ConnectionFactory.php @@ -53,9 +53,9 @@ class ConnectionFactory case CacheType::FILE: return new FileCache($cacheData['path'] ?? ''); case CacheType::REDIS: - return new RedisCache($cacheData['data'] ?? []); + return new RedisCache($cacheData ?? []); case CacheType::MEMCACHED: - return new MemCached($cacheData['data'] ?? []); + return new MemCached($cacheData ?? []); default: throw new \InvalidArgumentException('Cache "' . ($cacheData['type'] ?? '') . '" is not supported.'); } diff --git a/DataStorage/Cache/Connection/RedisCache.php b/DataStorage/Cache/Connection/RedisCache.php index 82a153cbe..784490055 100755 --- a/DataStorage/Cache/Connection/RedisCache.php +++ b/DataStorage/Cache/Connection/RedisCache.php @@ -113,7 +113,7 @@ final class RedisCache extends ConnectionAbstract } if ($expire > 0) { - $this->con->setEx((string) $key, $expire, $this->build($value)); + $this->con->set((string) $key, $expire, $this->build($value)); return; } @@ -131,10 +131,10 @@ final class RedisCache extends ConnectionAbstract } if ($expire > 0) { - return $this->con->setNx((string) $key, $this->build($value), $expire); + return $this->con->set((string) $key, $this->build($value), $expire); } - return $this->con->setNx((string) $key, $this->build($value)); + return $this->con->set((string) $key, $this->build($value)); } /** diff --git a/DataStorage/Database/BuilderAbstract.php b/DataStorage/Database/BuilderAbstract.php index c7b0ef109..cde979c5f 100755 --- a/DataStorage/Database/BuilderAbstract.php +++ b/DataStorage/Database/BuilderAbstract.php @@ -35,6 +35,14 @@ abstract class BuilderAbstract */ protected bool $isReadOnly = false; + /** + * Use prepared statement. + * + * @var bool + * @since 1.0.0 + */ + public bool $usePreparedStmt = true; + /** * Database connection. * @@ -59,6 +67,14 @@ abstract class BuilderAbstract */ public string $raw = ''; + /** + * Binds. + * + * @var array + * @since 1.0.0 + */ + public array $binds = []; + /** * Get connection * @@ -85,6 +101,27 @@ abstract class BuilderAbstract return $this->connection->con->quote($value); } + /** + * Bind parameter. + * + * @param array $binds Binds + * @param ?string $key Key + * + * @return Builder + * + * @since 1.0.0 + */ + public function bind(array $binds, ?string $key = null) : self + { + if ($key === null) { + $this->binds[] = $binds; + } else { + $this->binds[$key] = $binds; + } + + return $this; + } + /** * Get query type. * diff --git a/DataStorage/Database/Connection/SQLiteConnection.php b/DataStorage/Database/Connection/SQLiteConnection.php index bd7f01e55..77a0f043e 100755 --- a/DataStorage/Database/Connection/SQLiteConnection.php +++ b/DataStorage/Database/Connection/SQLiteConnection.php @@ -86,14 +86,14 @@ final class SQLiteConnection extends ConnectionAbstract $this->close(); try { - if (!\is_file($this->dbdata['database'])) { - throw new \PDOException(); - } - $this->con = new \PDO($this->dbdata['db'] . ':' . $this->dbdata['database']); $this->con->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); $this->con->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + if (!\is_file($this->dbdata['database'])) { + throw new \PDOException(); + } + $this->status = DatabaseStatus::OK; } catch (\PDOException $_) { $this->con = new NullPDO(); diff --git a/DataStorage/Database/GrammarAbstract.php b/DataStorage/Database/GrammarAbstract.php index f9b6acf63..54d171c1f 100755 --- a/DataStorage/Database/GrammarAbstract.php +++ b/DataStorage/Database/GrammarAbstract.php @@ -139,8 +139,9 @@ abstract class GrammarAbstract /** * Expressionize elements. * - * @param array $elements Elements - * @param bool $column Is column? + * @param array $elements Elements + * @param BuilderAbstract $query Builder + * @param bool $column Is column? * * @return string * @@ -148,7 +149,7 @@ abstract class GrammarAbstract * * @since 1.0.0 */ - public function expressionizeTableColumn(array $elements, bool $column = true) : string + public function expressionizeTableColumn(array $elements, BuilderAbstract $query = null, bool $column = true) : string { $expression = ''; @@ -160,6 +161,13 @@ abstract class GrammarAbstract $expression .= $element . ', '; } elseif ($element instanceof BuilderAbstract) { $expression .= $element->toSql() . (\is_string($key) ? ' AS ' . $key : '') . ', '; + + if ($query->usePreparedStmt && $element->usePreparedStmt && $query !== null) { + // If we have a subquery, we need to copy the binds over + foreach ($element->binds as $bind) { + $query->bind($bind); + }; + } } elseif ($element instanceof \Closure) { $expression .= $element() . (\is_string($key) ? ' AS ' . $key : '') . ', '; } else { @@ -219,6 +227,10 @@ abstract class GrammarAbstract /** * Compile value. * + * If the query builder uses prepared statements it will use these prepared statements + data binds. + * If the query builder doesn't use prepared statements but the setting usePreparedStmt is set to true + * this function will try and convert every value that can be prepared to a prepared statement + * * @param BuilderAbstract $query Query builder * @param mixed $value Value * @@ -230,10 +242,23 @@ abstract class GrammarAbstract */ protected function compileValue(BuilderAbstract $query, mixed $value) : string { + $compiled = ''; + $type = -1; + if (\is_string($value)) { - return $query->quote($value); + if ($query->usePreparedStmt) { + $type = \PDO::PARAM_STR; + $compiled = $value; + } else { + $compiled = $query->quote($value); + } } elseif (\is_int($value)) { - return (string) $value; + if ($query->usePreparedStmt) { + $type = \PDO::PARAM_INT; + $compiled = $value; + } else { + $compiled = (string) $value; + } } elseif (\is_array($value)) { $value = \array_values($value); $count = \count($value) - 1; @@ -243,29 +268,65 @@ abstract class GrammarAbstract $values .= $this->compileValue($query, $value[$i]) . ', '; } - return $values . $this->compileValue($query, $value[$count]) . ')'; - } elseif ($value instanceof \DateTime || $value instanceof \DateTimeImmutable) { - return $query->quote($value->format($this->datetimeFormat)); + $compiled = $values . $this->compileValue($query, $value[$count]) . ')'; + } elseif ($value instanceof \DateTimeInterface) { + if ($query->usePreparedStmt) { + $type = \PDO::PARAM_STR; + $compiled = $value->format($this->datetimeFormat); + } else { + $compiled = $query->quote($value->format($this->datetimeFormat)); + } } elseif ($value === null) { - return 'NULL'; + $compiled = 'NULL'; } elseif (\is_bool($value)) { - return (string) ((int) $value); + if ($query->usePreparedStmt) { + $type = \PDO::PARAM_BOOL; + $compiled = $value; + } else { + $compiled = (string) ((int) $value); + } } elseif (\is_float($value)) { - return \rtrim(\rtrim(\number_format($value, 5, '.', ''), '0'), '.'); + $compiled = \rtrim(\rtrim(\number_format($value, 5, '.', ''), '0'), '.'); } elseif ($value instanceof ColumnName) { - return $this->compileSystem($value->name); + $compiled = $this->compileSystem($value->name); } elseif ($value instanceof BuilderAbstract) { - return '(' . \rtrim($value->toSql(), ';') . ')'; + $compiled = '(' . \rtrim($value->toSql(), ';') . ')'; + if ($query->usePreparedStmt && $value->usePreparedStmt) { + $type = -2; + } } elseif ($value instanceof \JsonSerializable) { $encoded = \json_encode($value); - return $encoded ? $encoded : 'NULL'; + if ($query->usePreparedStmt) { + $type = $encoded ? \PDO::PARAM_STR : \PDO::PARAM_STR; + $compiled = $encoded ? $value : null; + } else { + $compiled = $encoded ? $query->quote($encoded) : 'NULL'; + } } elseif ($value instanceof SerializableInterface) { - return $value->serialize(); + $compiled = $value->serialize(); } elseif ($value instanceof Parameter) { - return $value->__toString(); + $compiled = $value->__toString(); } else { throw new \InvalidArgumentException(\gettype($value)); } + + if ($query->usePreparedStmt && $type !== -1) { + if ($type === -2) { + // If we have a subquery, we need to copy the binds over + foreach ($value->binds as $bind) { + $query->bind($bind); + } + } else { + $query->bind([ + 'value' => $compiled, + 'type' => $type, + ]); + + $compiled = '?'; + } + } + + return $compiled; } } diff --git a/DataStorage/Database/Mapper/DeleteMapper.php b/DataStorage/Database/Mapper/DeleteMapper.php index a880c9e19..31fce5569 100755 --- a/DataStorage/Database/Mapper/DeleteMapper.php +++ b/DataStorage/Database/Mapper/DeleteMapper.php @@ -102,10 +102,7 @@ final class DeleteMapper extends DataMapperAbstract ->from($this->mapper::TABLE) ->where($this->mapper::TABLE . '.' . $this->mapper::PRIMARYFIELD, '=', $objId); - $sth = $this->db->con->prepare($query->toSql()); - if ($sth !== false) { - $sth->execute(); - } + $query->execute(); } /** @@ -250,9 +247,6 @@ final class DeleteMapper extends DataMapperAbstract $relQuery->where($this->mapper::HAS_MANY[$member]['table'] . '.' . $this->mapper::HAS_MANY[$member]['external'], 'IN', $objIds); } - $sth = $this->db->con->prepare($relQuery->toSql()); - if ($sth !== false) { - $sth->execute(); - } + $relQuery->execute(); } } diff --git a/DataStorage/Database/Mapper/ReadMapper.php b/DataStorage/Database/Mapper/ReadMapper.php index 35fd31247..ec33ab99e 100755 --- a/DataStorage/Database/Mapper/ReadMapper.php +++ b/DataStorage/Database/Mapper/ReadMapper.php @@ -347,9 +347,8 @@ final class ReadMapper extends DataMapperAbstract $results = false; try { - $sth = $this->db->con->prepare($query->toSql()); - if ($sth !== false) { - $sth->execute(); + $sth = $query->execute(); + if ($sth !== null) { $results = $sth->fetchAll(\PDO::FETCH_ASSOC); } } catch (\Throwable $t) { @@ -381,15 +380,13 @@ final class ReadMapper extends DataMapperAbstract $query ??= $this->getQuery(); try { - $sth = $this->db->con->prepare($query->toSql()); - if ($sth === false) { + $sth = $query->execute(); + if ($sth === null) { yield []; return; } - $sth->execute(); - while ($row = $sth->fetch(\PDO::FETCH_ASSOC)) { yield $row; } diff --git a/DataStorage/Database/Mapper/UpdateMapper.php b/DataStorage/Database/Mapper/UpdateMapper.php index a292880b3..a4ac34ce8 100755 --- a/DataStorage/Database/Mapper/UpdateMapper.php +++ b/DataStorage/Database/Mapper/UpdateMapper.php @@ -162,8 +162,8 @@ final class UpdateMapper extends DataMapperAbstract } } - $sth = $this->db->con->prepare($query->toSql()); - if ($sth === false) { + $sth = $query->prepare(); + if ($sth === null) { throw new \Exception(); } @@ -442,12 +442,11 @@ final class UpdateMapper extends DataMapperAbstract ->on($many['table'] . '.' . $src, '=', $many['mapper']::TABLE . '.' . $many['mapper']::PRIMARYFIELD); } - $sth = $this->db->con->prepare($query->toSql()); - if ($sth === false) { + $sth = $query->execute(); + if ($sth === null) { continue; } - $sth->execute(); $result = $sth->fetchAll(\PDO::FETCH_COLUMN); if ($result === false) { diff --git a/DataStorage/Database/Mapper/WriteMapper.php b/DataStorage/Database/Mapper/WriteMapper.php index 80aa8a249..05094c4e1 100755 --- a/DataStorage/Database/Mapper/WriteMapper.php +++ b/DataStorage/Database/Mapper/WriteMapper.php @@ -162,8 +162,8 @@ final class WriteMapper extends DataMapperAbstract $query->insert($this->mapper::PRIMARYFIELD)->value(0); } - $sth = $this->db->con->prepare($query->toSql()); - if ($sth === false) { + $sth = $query->prepare(); + if ($sth === null) { throw new \Exception(); } @@ -438,8 +438,8 @@ final class WriteMapper extends DataMapperAbstract $relQuery->values($src, $objId); } - $sth = $this->db->con->prepare($relQuery->toSql()); - if ($sth === false) { + $sth = $relQuery->prepare(); + if ($sth === null) { throw new \Exception(); } diff --git a/DataStorage/Database/Query/Builder.php b/DataStorage/Database/Query/Builder.php index fbd9a403e..e774febc0 100755 --- a/DataStorage/Database/Query/Builder.php +++ b/DataStorage/Database/Query/Builder.php @@ -189,14 +189,6 @@ class Builder extends BuilderAbstract */ public ?int $offset = null; - /** - * Binds. - * - * @var array - * @since 1.0.0 - */ - private array $binds = []; - /** * Union. * @@ -341,26 +333,6 @@ class Builder extends BuilderAbstract return $this; } - /** - * Bind parameter. - * - * @param string|array $binds Binds - * - * @return Builder - * - * @since 1.0.0 - */ - public function bind(string | array $binds) : self - { - if (\is_array($binds)) { - $this->binds += $binds; - } else { - $this->binds[] = $binds; - } - - return $this; - } - /** * {@inheritdoc} */ @@ -1373,6 +1345,42 @@ class Builder extends BuilderAbstract * {@inheritdoc} */ public function execute() : ?\PDOStatement + { + $sth = null; + try { + $sth = $this->prepare(); + if ($sth !== null) { + $sth->execute(); + } + } catch (\Throwable $t) { + // @codeCoverageIgnoreStart + \phpOMS\Log\FileLogger::getInstance()->error( + \phpOMS\Log\FileLogger::MSG_FULL, [ + 'message' => $t->getMessage() . ':' . $this->toSql(), + 'line' => __LINE__, + 'file' => self::class, + ] + ); + + \phpOMS\Log\FileLogger::getInstance()->error( + \phpOMS\Log\FileLogger::MSG_FULL, [ + 'message' => \json_encode($this->binds), + 'line' => __LINE__, + 'file' => self::class, + ] + ); + + $sth = null; + // @codeCoverageIgnoreEnd + } + + return $sth; + } + + /** + * {@inheritdoc} + */ + public function prepare() : ?\PDOStatement { $sth = null; $sql = ''; @@ -1384,12 +1392,12 @@ class Builder extends BuilderAbstract } foreach ($this->binds as $key => $bind) { - $type = self::getBindParamType($bind); + if (!isset($bind['type'])) { + $bind['type'] = self::getBindParamType($bind['value']); + } - $sth->bindParam($key, $bind, $type); + $sth->bindParam(\is_int($key) ? $key + 1 : $key, $bind['value'], $bind['type']); } - - $sth->execute(); } catch (\Throwable $t) { // @codeCoverageIgnoreStart \phpOMS\Log\FileLogger::getInstance()->error( @@ -1400,6 +1408,14 @@ class Builder extends BuilderAbstract ] ); + \phpOMS\Log\FileLogger::getInstance()->error( + \phpOMS\Log\FileLogger::MSG_FULL, [ + 'message' => \json_encode($this->binds), + 'line' => __LINE__, + 'file' => self::class, + ] + ); + $sth = null; // @codeCoverageIgnoreEnd } @@ -1447,7 +1463,12 @@ class Builder extends BuilderAbstract } elseif ($column instanceof SerializableInterface) { return $column->serialize(); } elseif ($column instanceof self) { - return \md5($column->toSql()); + $tmp = $column->usePreparedStmt; + $column->usePreparedStmt = false; + $hash = \md5($column->toSql()); + $column->usePreparedStmt = $tmp; + + return $hash; } throw new \Exception(); diff --git a/DataStorage/Database/Query/Grammar/Grammar.php b/DataStorage/Database/Query/Grammar/Grammar.php index 3293c382f..ebed6718d 100755 --- a/DataStorage/Database/Query/Grammar/Grammar.php +++ b/DataStorage/Database/Query/Grammar/Grammar.php @@ -156,7 +156,7 @@ class Grammar extends GrammarAbstract */ protected function compileSelects(Builder $query, array $columns) : string { - $expression = $this->expressionizeTableColumn($columns, false); + $expression = $this->expressionizeTableColumn($columns, $query, false); if ($expression === '') { $expression = '*'; @@ -207,7 +207,7 @@ class Grammar extends GrammarAbstract */ protected function compileUpdates(Builder $query, array $table) : string { - $expression = $this->expressionizeTableColumn($table); + $expression = $this->expressionizeTableColumn($table, $query); if ($expression === '') { return ''; @@ -243,7 +243,7 @@ class Grammar extends GrammarAbstract */ protected function compileFrom(Builder $query, array $table) : string { - $expression = $this->expressionizeTableColumn($table); + $expression = $this->expressionizeTableColumn($table, $query); if ($expression === '') { return ''; @@ -265,76 +265,73 @@ class Grammar extends GrammarAbstract */ public function compileWheres(Builder $query, array $wheres, bool $first = true) : string { - $expression = ''; + $outer = ''; foreach ($wheres as $where) { foreach ($where as $element) { - $expression .= $this->compileWhereElement($element, $query, $first); - $first = false; + $expression = ''; + $prefix = ''; + + if (!$first) { + $prefix = ' ' . \strtoupper($element['boolean']) . ' '; + } + + if (\is_string($element['column'])) { + $expression .= $this->compileSystem($element['column']); + } elseif ($element['column'] instanceof Builder) { + $expression .= '(' . \rtrim($element['column']->toSql(), ';') . ')'; + + if ($query->usePreparedStmt && $element['column']->usePreparedStmt) { + // If we have a subquery, we need to copy the binds over + // BUT we are only allowed to do this once per wheres builder + foreach ($element['column']->binds as $bind) { + $query->bind($bind); + }; + } + } elseif ($element['column'] instanceof \Closure) { + $expression .= $element['column'](); + } + + // Handle null for IN (...) + // This is not allowed and must be written as (IN (...) OR IS NULL) + $isArray = \is_array($element['value']); + $hasNull = false; + if ($isArray && ($key = \array_search(null, $element['value'], true)) !== false) { + $hasNull = true; + unset($element['value'][$key]); + + if (empty($element['value'])) { + $element['operator'] = '='; + $element['value'] = null; + } + } + + if (isset($element['value']) && (!empty($element['value']) || !$isArray)) { + if ($isArray && \count($element['value']) === 1) { + $element['value'] = \reset($element['value']); + $element['operator'] = '='; + } + + $expression .= ' ' . \strtoupper($element['operator']) . ' ' . $this->compileValue($query, $element['value']); + + if ($hasNull) { + $expression = '(' . $expression . ' OR ' . $this->compileSystem($element['column']) . ' IS NULL)'; + } + } elseif ($element['value'] === null && !($element['column'] instanceof Builder)) { + $operator = $element['operator'] === '=' ? 'IS' : 'IS NOT'; + $expression .= ' ' . $operator . ' ' . $this->compileValue($query, $element['value']); + } + + $outer .= $prefix . $expression; + $first = false; } } - if ($expression === '') { + if ($outer === '') { return ''; } - return 'WHERE ' . $expression; - } - - /** - * Compile where element. - * - * @param array $element Element data - * @param Builder $query Query builder - * @param bool $first Is first element (useful for nesting) - * - * @return string - * - * @since 1.0.0 - */ - protected function compileWhereElement(array $element, Builder $query, bool $first = true) : string - { - $expression = ''; - $prefix = ''; - - if (!$first) { - $prefix = ' ' . \strtoupper($element['boolean']) . ' '; - } - - if (\is_string($element['column'])) { - $expression .= $this->compileSystem($element['column']); - } elseif ($element['column'] instanceof Builder) { - $expression .= '(' . \rtrim($element['column']->toSql(), ';') . ')'; - } elseif ($element['column'] instanceof \Closure) { - $expression .= $element['column'](); - } - - // Handle null for IN (...) - // This is not allowed and must be written as (IN (...) OR IS NULL) - $isArray = \is_array($element['value']); - $hasNull = false; - if ($isArray && ($key = \array_search(null, $element['value'], true)) !== false) { - $hasNull = true; - unset($element['value'][$key]); - - if (empty($element['value'])) { - $element['operator'] = '='; - $element['value'] = null; - } - } - - if (isset($element['value']) && (!empty($element['value']) || !$isArray)) { - $expression .= ' ' . \strtoupper($element['operator']) . ' ' . $this->compileValue($query, $element['value']); - - if ($hasNull) { - $expression = '(' . $expression . ' OR ' . $this->compileSystem($element['column']) . ' IS NULL)'; - } - } elseif ($element['value'] === null && !($element['column'] instanceof Builder)) { - $operator = $element['operator'] === '=' ? 'IS' : 'IS NOT'; - $expression .= ' ' . $operator . ' ' . $this->compileValue($query, $element['value']); - } - - return $prefix . $expression; + return 'WHERE ' . $outer; } /** @@ -349,6 +346,16 @@ class Grammar extends GrammarAbstract */ protected function compileLimit(Builder $query, int $limit) : string { + if ($query->usePreparedStmt) { + + $query->bind([ + 'value' => $limit, + 'type' => \PDO::PARAM_INT, + ]); + + $limit = '?'; + } + return 'LIMIT ' . $limit; } @@ -364,6 +371,16 @@ class Grammar extends GrammarAbstract */ protected function compileOffset(Builder $query, int $offset) : string { + if ($query->usePreparedStmt) { + + $query->bind([ + 'value' => $offset, + 'type' => \PDO::PARAM_INT, + ]); + + $offset = '?'; + } + return 'OFFSET ' . $offset; } @@ -390,6 +407,13 @@ class Grammar extends GrammarAbstract $expression .= $join['table']() . (\is_string($join['alias']) ? ' as ' . $join['alias'] : ''); } elseif ($join['table'] instanceof Builder) { $expression .= '(' . \rtrim($join['table']->toSql(), ';') . ')' . (\is_string($join['alias']) ? ' as ' . $join['alias'] : ''); + + if ($query->usePreparedStmt && $join['table']->usePreparedStmt) { + // If we have a subquery, we need to copy the binds over + foreach ($join['table']->binds as $bind) { + $query->bind($bind); + }; + } } $expression .= $this->compileOn($query, $query->ons[$join['alias'] ?? $table]) . ' '; @@ -453,13 +477,20 @@ class Grammar extends GrammarAbstract $expression .= $this->compileSystem($element['column']); } elseif ($element['column'] instanceof Builder) { $expression .= '(' . $element['column']->toSql() . ')'; + + if ($query->usePreparedStmt && $element['column']->usePreparedStmt) { + // If we have a subquery, we need to copy the binds over + foreach ($element['column']->binds as $bind) { + $query->bind($bind); + }; + } } elseif ($element['column'] instanceof \Closure) { $expression .= $element['column'](); } // @bug The on part of a join doesn't allow string values because they conflict with column name // Other data types are possible because they don't conflict with the data type of columns (string) - // Consider to create a ColumnName() class. + // Consider to create a ColumnName() class or maybe StringValue() since we use string values less often. // https://github.com/Karaka-Management/phpOMS/issues/369 if (isset($element['value'])) { $expression .= ' ' . \strtoupper($element['operator']) . ' ' @@ -628,7 +659,7 @@ class Grammar extends GrammarAbstract $vals = ''; foreach ($values as $column => $value) { - $expression = $this->expressionizeTableColumn([$column], false); + $expression = $this->expressionizeTableColumn([$column], $query, false); $vals .= $expression . ' = ' . $this->compileValue($query, $value) . ', '; } diff --git a/DataStorage/Database/Schema/Builder.php b/DataStorage/Database/Schema/Builder.php index f3e2080fd..f3a331ee2 100755 --- a/DataStorage/Database/Schema/Builder.php +++ b/DataStorage/Database/Schema/Builder.php @@ -120,6 +120,12 @@ class Builder extends BuilderAbstract */ public array $alterAdd = []; + /** + * Has post query to run. + * + * @var bool + * @since 1.0.0 + */ public bool $hasPostQuery = false; /** @@ -151,6 +157,32 @@ class Builder extends BuilderAbstract $this->grammar = $connection->getSchemaGrammar(); } + /** + * Get the column type from a variable value. + * + * @param mixed $value Variable value + * + * @return string + * + * @since 1.0.0 + */ + public static function getTypeFromVariable(mixed $value) : string + { + if (\is_string($value)) { + return 'TEXT'; + } elseif (\is_int($value)) { + return 'INT'; + } elseif (\is_bool($value)) { + return 'TINYINT(1)'; + } elseif (\is_float($value)) { + return 'DECIMAL(10,6)'; + } elseif ($value instanceof \DateTimeInterface) { + return 'DATETIME'; + } + + return 'TEXT'; + } + /** * Create schema builder from schema definition. * @@ -168,6 +200,7 @@ class Builder extends BuilderAbstract public static function createFromSchema(array $definition, ConnectionAbstract $connection) : self { $builder = new self($connection); + $builder->usePreparedStmt = false; $builder->createTable($definition['name'] ?? ''); foreach ($definition['fields'] as $name => $def) { diff --git a/DataStorage/Database/Schema/Grammar/SQLiteGrammar.php b/DataStorage/Database/Schema/Grammar/SQLiteGrammar.php index 150810b38..d4504665c 100755 --- a/DataStorage/Database/Schema/Grammar/SQLiteGrammar.php +++ b/DataStorage/Database/Schema/Grammar/SQLiteGrammar.php @@ -129,14 +129,14 @@ class SQLiteGrammar extends Grammar $fieldQuery .= ' ' . ($field['null'] ? '' : 'NOT ') . 'NULL'; } - if ($field['autoincrement'] ?? false) { - $fieldQuery .= ' AUTOINCREMENT'; - } - if ($field['primary'] ?? false) { $fieldQuery .= ' PRIMARY KEY'; } + if ($field['autoincrement'] ?? false) { + $fieldQuery .= ' AUTOINCREMENT'; + } + $fieldQuery .= ','; if ($field['unique'] ?? false) { diff --git a/Message/Http/HttpRequest.php b/Message/Http/HttpRequest.php index 11614d31e..34ec93a0b 100755 --- a/Message/Http/HttpRequest.php +++ b/Message/Http/HttpRequest.php @@ -512,7 +512,9 @@ final class HttpRequest extends RequestAbstract { $useragent = $_SERVER['HTTP_USER_AGENT'] ?? ''; - if (\preg_match('/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i', $useragent) || \preg_match('/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i', $useragent)) { + if (\preg_match('/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i', $useragent) + || \preg_match('/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i', $useragent) + ) { return true; // @codeCoverageIgnore } diff --git a/Message/Mail/Email.php b/Message/Mail/Email.php index 6d638439d..a58a65641 100755 --- a/Message/Mail/Email.php +++ b/Message/Mail/Email.php @@ -1783,7 +1783,7 @@ class Email $lookBack = 0; do { - $offset = $avgLength - $lookBack; + $offset = (int) ($avgLength - $lookBack); $chunk = \mb_substr($str, $i, $offset, $this->charset); $chunk = \base64_encode($chunk); ++$lookBack; diff --git a/Module/ModuleAbstract.php b/Module/ModuleAbstract.php index 61e5f9426..aa1143c30 100755 --- a/Module/ModuleAbstract.php +++ b/Module/ModuleAbstract.php @@ -526,6 +526,39 @@ abstract class ModuleAbstract ]; } + /** + * Create duplicate model create response. + * + * The response object contains the following data: + * + * * status = Response status + * * title = Response title (e.g. for frontend reporting) + * * message = Response message (e.g. for frontend reporting) + * * response = Response object (e.g. for validation/frontend reporting/form validation) + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $obj Response object + * + * @return void + * + * @since 1.0.0 + */ + public function createDuplicateCreateResponse( + RequestAbstract $request, + ResponseAbstract $response, + mixed $obj + ) : void + { + $response->header->set('Content-Type', MimeType::M_JSON . '; charset=utf-8', true); + $response->data[$request->uri->__toString()] = [ + 'status' => NotificationLevel::WARNING, + 'title' => '', + 'message' => $this->app->l11nManager->getText($response->header->l11n->language, '0', '0', 'DuplicateCreate'), + 'response' => $obj, + ]; + } + /** * Create invalid model update response. * diff --git a/Security/Guard.php b/Security/Guard.php index 1b55e5309..eb0e2dc0d 100755 --- a/Security/Guard.php +++ b/Security/Guard.php @@ -117,11 +117,15 @@ final class Guard public static function isSafeFile(string $path) : bool { + if (!\str_ends_with($path, '.exe') && !self::isSafeNoneExecutable($path)) { + return false; + } + $tmp = \strtolower($path); if (\str_ends_with($tmp, '.csv')) { - return self::isSafeXml($path); - } elseif (\str_ends_with($tmp, '.xml')) { return self::isSafeCsv($path); + } elseif (\str_ends_with($tmp, '.xml')) { + return self::isSafeXml($path); } return true; @@ -175,13 +179,15 @@ final class Guard return true; } + static $specialChars = ['=', '+', '-', '@']; + while (($row = \fgetcsv($input)) !== false) { foreach ($row as &$cell) { - if (\preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x1F]+/', $cell) !== false) { + if (\preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x1F]+/', $cell) === 1) { return false; } - if (\in_array($cell[0] ?? '', ['=', '+', '-', '@'])) { + if (\in_array($cell[0] ?? '', $specialChars)) { return false; } } @@ -191,4 +197,32 @@ final class Guard return true; } + + public static function isSafeNoneExecutable(string $path) : bool + { + $input = \fopen($path, 'r'); + if (!$input) { + return true; + } + + static $specialSignatures = [ + "\x4D\x5A", + "\x7F\x45\x4C\x46", + ]; + + $line = \fgets($input, 256); + \fclose($input); + + if ($line === false) { + return true; + } + + foreach ($specialSignatures as $sig) { + if (\mb_stripos($line, $sig) !== false) { + return false; + } + } + + return true; + } } diff --git a/Stdlib/Base/Address.php b/Stdlib/Base/Address.php index ec960a7fa..2da189a52 100755 --- a/Stdlib/Base/Address.php +++ b/Stdlib/Base/Address.php @@ -62,11 +62,24 @@ class Address extends Location public function toArray() : array { return \array_merge( + parent::toArray(), [ + 'id' => $this->id, 'name' => $this->name, 'fao' => $this->fao, - ], - parent::toArray() + ] ); } + + public static function fromJson(array $address) : self + { + $new = new self(); + $new->from($address); + + $new->id = $address['id']; + $new->name = $address['name']; + $new->fao = $address['fao']; + + return $new; + } } diff --git a/Stdlib/Base/FloatInt.php b/Stdlib/Base/FloatInt.php index a76100506..bc88e7cc6 100755 --- a/Stdlib/Base/FloatInt.php +++ b/Stdlib/Base/FloatInt.php @@ -372,6 +372,19 @@ class FloatInt implements SerializableInterface $this->setInt((int) $value); } + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return \json_encode($this->getInt()); + } + + public static function fromJson(string $json) : self + { + return new self((int) $json); + } + /** * Set money value. * diff --git a/Stdlib/Base/Location.php b/Stdlib/Base/Location.php index f1f0ed6b8..a99c16501 100755 --- a/Stdlib/Base/Location.php +++ b/Stdlib/Base/Location.php @@ -157,6 +157,8 @@ class Location implements \JsonSerializable, SerializableInterface public function toArray() : array { return [ + 'id' => $this->id, + 'type' => $this->type, 'postal' => $this->postal, 'city' => $this->city, 'country' => $this->country, @@ -167,6 +169,19 @@ class Location implements \JsonSerializable, SerializableInterface ]; } + public function from(array $location) : void + { + $this->id = $location['id']; + $this->type = $location['type']; + $this->postal = $location['postal']; + $this->city = $location['city']; + $this->country = $location['country']; + $this->address = $location['address']; + $this->state = $location['state']; + $this->lat = $location['lat']; + $this->lon = $location['lon']; + } + /** * {@inheritdoc} */ diff --git a/System/File/SearchUtils.php b/System/File/SearchUtils.php new file mode 100644 index 000000000..29fcebd2f --- /dev/null +++ b/System/File/SearchUtils.php @@ -0,0 +1,170 @@ + $pos, + 'end' => $pos, + 'distance' => 0, + ]; + + foreach ($positions as $keyword => $found) { + $closestStart = null; + $closestEnd = null; + $inBetween = null; + + foreach ($found as $pos2) { + if ($pos2 < 0) { + continue 2; + } + + if ($pos2 >= $distance['start'] && $pos2 <= $distance['end']) { + $inBetween = $pos2; + + break; + } + + if ($closestStart === null + || \abs($pos2 - $distance['start']) < \abs($closestStart - $distance['start']) + ) { + $closestStart = $pos2; + } + + if ($closestEnd === null + || \abs($pos2 - $distance['end']) < \abs($closestEnd - $distance['end']) + ) { + $closestEnd = $pos2; + } + } + + // The following is only perfect for inBetween + // For the other cases there could be a scenario where the closer value is actually bad + // because for the next keyword the farther one would be inBetween + if ($inBetween !== null) { + continue; // Perfect + } elseif ($closestStart < $distance['start'] + && (\abs($closestStart - $distance['start']) <= \abs($closestEnd - $distance['end']) || $closestEnd > $distance['end'])) { + $distance['start'] = \min($distance['start'], $closestStart); + } else { + $distance['end'] = \max($distance['end'], $closestEnd); + } + } + + $distance['distance'] = $distance['end'] - $distance['start']; + $distances[] = $distance; + } + + if (empty($distances)) { + return []; + } + + \uasort($distances, function (array $a, array $b) { + return $a['distance'] <=> $b['distance']; + }); + + return $distances; + } + + public static function getTextExtract(string $path, int $pos, string $start, string $end) : string + { + $fp = \fopen($path, "r"); + if ($fp === false) { + return []; + } + + $startPos = -1; + while (($line = \fgets($fp)) !== false && \ftell($fp) < $pos) { + if (\stripos($line, $start) !== false) { + $startPos = \ftell($fp); + } + } + + \fseek($fp, $pos); + + $endPos = -1; + while (($line = \fgets($fp)) !== false) { + if (\stripos($line, $end) !== false) { + $endPos = \ftell($fp); + break; + } + } + + if ($startPos < 0 || $endPos < 0) { + \fclose($fp); + return ''; + } + + \fseek($fp, $startPos); + $extract = \fread($fp, $endPos - $startPos); + + \fclose($fp); + + return $extract; + } +} diff --git a/Uri/UriFactory.php b/Uri/UriFactory.php index 6e53c78e8..6050faabc 100755 --- a/Uri/UriFactory.php +++ b/Uri/UriFactory.php @@ -196,7 +196,7 @@ final class UriFactory { $success = false; - foreach (self::$uri as $key => $value) { + foreach (self::$uri as $key => $_) { if (((bool) \preg_match('~^' . $pattern . '$~', $key))) { unset(self::$uri[$key]); $success = true; diff --git a/Utils/IO/Csv/CsvDatabaseMapper.php b/Utils/IO/Csv/CsvDatabaseMapper.php index 17a13ef94..f606436b8 100644 --- a/Utils/IO/Csv/CsvDatabaseMapper.php +++ b/Utils/IO/Csv/CsvDatabaseMapper.php @@ -16,6 +16,7 @@ namespace phpOMS\Utils\IO\Csv; use phpOMS\DataStorage\Database\Connection\ConnectionAbstract; use phpOMS\DataStorage\Database\Query\Builder; +use phpOMS\DataStorage\Database\Schema\Builder as SchemaBuilder; use phpOMS\Utils\IO\IODatabaseMapper; /** @@ -36,58 +37,60 @@ final class CsvDatabaseMapper implements IODatabaseMapper */ private ConnectionAbstract $con; - /** - * Path to source or destination - * - * @var string - * @since 1.0.0 - */ - private string $path = ''; - /** * Constructor. * * @param ConnectionAbstract $con Database connection - * @param string $path File path * * @since 1.0.0 */ - public function __construct(ConnectionAbstract $con, string $path) + public function __construct(ConnectionAbstract $con) { - $this->con = $con; - $this->path = $path; + $this->con = $con; } /** * {@inheritdoc} */ - public function insert() : void + public function createSchema(string $path, string $table = '') : void { - $fp = \fopen($this->path, 'r'); + $fp = \fopen($path, 'r'); if ($fp === false) { return; } - $table = \basename($this->path, '.csv'); + $table = \strtr(empty($table) ? \basename($path, '.csv') : $table, ' ', '_'); $titles = []; + $delim = CsvSettings::getFileDelimiter($fp, 2); + // get column titles - $titles = \fgetcsv($fp, 4096); + $titles = \fgetcsv($fp, 4096, $delim); if ($titles === false) { + \fclose($fp); return; } - $columns = \count($titles); - if ($columns === 0) { + $titles = \array_map(function(string $title) : string { + $title = \strtr(\trim($title), ' ', '_'); + $title = \preg_replace('/[^a-zA-Z0-9_]/', '', $title); + + return \strtr($title, ' ', '_'); + }, $titles); + + $cells = \fgetcsv($fp, null, $delim); + if ($cells === false) { + \fclose($fp); return; } - // insert data - $query = new Builder($this->con); - $query->insert(...$titles)->into($table); + $query = new SchemaBuilder($this->con); + $query->createTable($table); - while (($cells = \fgetcsv($fp)) !== false) { - $query->values(...$cells); + foreach ($cells as $idx => $cell) { + $datatype = SchemaBuilder::getTypeFromVariable($cell); + + $query->field($titles[$idx], $datatype); } $query->execute(); @@ -98,9 +101,78 @@ final class CsvDatabaseMapper implements IODatabaseMapper /** * {@inheritdoc} */ - public function select(array $queries) : void + public function import(string $path, string $table = '', ?\Closure $transform = null) : void { - $fp = \fopen($this->path, 'r+'); + $fp = \fopen($path, 'r'); + if ($fp === false) { + return; + } + + $table = \strtr(empty($table) ? \basename($path, '.csv') : $table, ' ', '_'); + $titles = []; + + $delim = CsvSettings::getFileDelimiter($fp, 2); + + // get column titles + $titles = \fgetcsv($fp, 4096, $delim); + if ($titles === false) { + \fclose($fp); + return; + } + + $columns = \count($titles); + if ($columns === 0) { + \fclose($fp); + return; + } + + $titles = \array_map(function(string $title) : string { + $title = \strtr(\trim($title), ' ', '_'); + $title = \preg_replace('/[^a-zA-Z0-9_]/', '', $title); + + return \strtr($title, ' ', '_'); + }, $titles); + + $titleCount = \count($titles); + + do { + $counter = 0; + + // insert data + $query = new Builder($this->con); + $query->insert(...$titles)->into($table); + + while (($cells = \fgetcsv($fp, null, $delim)) !== false) { + if (\count($cells) !== $titleCount) { + continue; + } + + if ($transform !== null) { + foreach ($cells as $idx => $cell) { + $cells[$idx] = $transform($titles[$idx], $cell); + } + } + + $query->values(...$cells); + ++$counter; + + if ($counter > 250) { + break; + } + } + + $query->execute(); + } while ($cells !== false); + + \fclose($fp); + } + + /** + * {@inheritdoc} + */ + public function export(string $path, array $queries) : void + { + $fp = \fopen($path, 'r+'); if ($fp === false) { return; } @@ -112,6 +184,7 @@ final class CsvDatabaseMapper implements IODatabaseMapper } if ($key > 0) { + \fclose($fp); return; } @@ -140,27 +213,36 @@ final class CsvDatabaseMapper implements IODatabaseMapper /** * {@inheritdoc} */ - public function update() : void + public function update(string $path, string $table = '') : void { - $fp = \fopen($this->path, 'r+'); + $fp = \fopen($path, 'r+'); if ($fp === false) { return; } - $table = \basename($this->path, '.csv'); + $table = \strtr(empty($table) ? \basename($path, '.csv') : $table, ' ', '_'); $titles = []; // get column titles $titles = \fgetcsv($fp, 4096); if ($titles === false) { + \fclose($fp); return; } $columns = \count($titles); if ($columns === 0) { + \fclose($fp); return; } + $titles = \array_map(function(string $title) : string { + $title = \strtr(\trim($title), ' ', '_'); + $title = \preg_replace('/[^a-zA-Z0-9_]/', '', $title); + + return \strtr($title, ' ', '_'); + }, $titles); + $idCol = (string) \array_shift($titles); // update data diff --git a/Utils/IO/IODatabaseMapper.php b/Utils/IO/IODatabaseMapper.php index 55821843c..1a4b14144 100755 --- a/Utils/IO/IODatabaseMapper.php +++ b/Utils/IO/IODatabaseMapper.php @@ -27,29 +27,49 @@ interface IODatabaseMapper /** * Insert data from excel sheet into database * + * @param string $path File path + * @param string $table Table name (empty = sheet name) + * @param null|\Closure $transform Transform data before import + * * @return void * * @since 1.0.0 */ - public function insert() : void; + public function import(string $path, string $table = '', ?\Closure $transform = null) : void; /** * Select data from database and store in excel sheet * + * @param string $path Output path * @param \phpOMS\DataStorage\Database\Query\Builder[] $queries Queries to execute * * @return void * * @since 1.0.0 */ - public function select(array $queries) : void; + public function export(string $path, array $queries) : void; /** * Update data from excel sheet into database * + * @param string $path File path + * @param string $table Table name (empty = sheet name) + * * @return void * * @since 1.0.0 */ - public function update() : void; + public function update(string $path, string $table = '') : void; + + /** + * Create database schema + * + * @param string $path File path + * @param string $table Table name (empty = sheet name) + * + * @return void + * + * @since 1.0.0 + */ + public function createSchema(string $path, string $table = '') : void; } diff --git a/Utils/IO/Spreadsheet/SpreadsheetDatabaseMapper.php b/Utils/IO/Spreadsheet/SpreadsheetDatabaseMapper.php index baeb81223..b108e37c0 100755 --- a/Utils/IO/Spreadsheet/SpreadsheetDatabaseMapper.php +++ b/Utils/IO/Spreadsheet/SpreadsheetDatabaseMapper.php @@ -16,6 +16,7 @@ namespace phpOMS\Utils\IO\Spreadsheet; use phpOMS\DataStorage\Database\Connection\ConnectionAbstract; use phpOMS\DataStorage\Database\Query\Builder; +use phpOMS\DataStorage\Database\Schema\Builder as SchemaBuilder; use phpOMS\Utils\IO\IODatabaseMapper; use phpOMS\Utils\StringUtils; @@ -37,57 +38,50 @@ final class SpreadsheetDatabaseMapper implements IODatabaseMapper */ private ConnectionAbstract $con; - /** - * Path to source or destination - * - * @var string - * @since 1.0.0 - */ - private string $path = ''; - /** * Constructor. * * @param ConnectionAbstract $con Database connection - * @param string $path File path * * @since 1.0.0 */ - public function __construct(ConnectionAbstract $con, string $path) + public function __construct(ConnectionAbstract $con) { $this->con = $con; - $this->path = $path; } /** * {@inheritdoc} */ - public function insert() : void + public function createSchema(string $path, string $table = '') : void { $reader = null; - if (StringUtils::endsWith($this->path, '.xlsx')) { + if (StringUtils::endsWith($path, '.xlsx')) { $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); - } elseif (StringUtils::endsWith($this->path, '.ods')) { + } elseif (StringUtils::endsWith($path, '.ods')) { $reader = new \PhpOffice\PhpSpreadsheet\Reader\Ods(); } else { $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); } $reader->setReadDataOnly(true); - $sheet = $reader->load($this->path); + $sheet = $reader->load($path); $tables = $sheet->getSheetCount(); for ($i = 0; $i < $tables; ++$i) { $sheet->setActiveSheetIndex($i); $workSheet = $sheet->getSheet($i); - $table = $workSheet->getTitle(); + $table = \strtr(empty($table) ? $workSheet->getTitle() : $table, ' ', '_'); $titles = []; // get column titles $column = 1; while (!empty($value = $workSheet->getCell(StringUtils::intToAlphabet($column) . 1)->getCalculatedValue())) { + $value = \strtr(\trim($value), ' ', '_'); + $value = \preg_replace('/[^a-zA-Z0-9_]/', '', $value); $titles[] = $value; + ++$column; } @@ -96,20 +90,22 @@ final class SpreadsheetDatabaseMapper implements IODatabaseMapper continue; } - // insert data - $query = new Builder($this->con); - $query->insert(...$titles)->into($table); + $query = new SchemaBuilder($this->con); + $query->createTable($table); $line = 2; - while (!empty($workSheet->getCell('A' . $line)->getCalculatedValue())) { - $cells = []; - for ($j = 1; $j <= $columns; ++$j) { - $cells[] = $workSheet->getCell(StringUtils::intToAlphabet($j) . $line)->getCalculatedValue(); - } + if (empty($workSheet->getCell('A' . $line)->getCalculatedValue())) { + continue; + } - ++$line; + for ($j = 1; $j <= $columns; ++$j) { + $cells[] = $workSheet->getCell(StringUtils::intToAlphabet($j) . $line)->getCalculatedValue(); + } - $query->values(...$cells); + foreach ($cells as $idx => $cell) { + $datatype = SchemaBuilder::getTypeFromVariable($cell); + + $query->field($titles[$idx], $datatype); } $query->execute(); @@ -119,7 +115,83 @@ final class SpreadsheetDatabaseMapper implements IODatabaseMapper /** * {@inheritdoc} */ - public function select(array $queries) : void + public function import(string $path, string $table = '', ?\Closure $transform = null) : void + { + $reader = null; + if (StringUtils::endsWith($path, '.xlsx')) { + $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); + } elseif (StringUtils::endsWith($path, '.ods')) { + $reader = new \PhpOffice\PhpSpreadsheet\Reader\Ods(); + } else { + $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); + } + + $reader->setReadDataOnly(true); + $sheet = $reader->load($path); + + $tables = $sheet->getSheetCount(); + for ($i = 0; $i < $tables; ++$i) { + $sheet->setActiveSheetIndex($i); + + $workSheet = $sheet->getSheet($i); + $table = \strtr(empty($table) ? $workSheet->getTitle() : $table, ' ', '_'); + $titles = []; + + // get column titles + $column = 1; + while (!empty($value = $workSheet->getCell(StringUtils::intToAlphabet($column) . 1)->getCalculatedValue())) { + $value = \strtr(\trim($value), ' ', '_'); + $value = \preg_replace('/[^a-zA-Z0-9_]/', '', $value); + $titles[] = $value; + + ++$column; + } + + $columns = \count($titles); + if ($columns === 0) { + continue; + } + + $line = 2; + + do { + $counter = 0; + + // insert data + $query = new Builder($this->con); + $query->insert(...$titles)->into($table); + + while ($hasData = !empty($workSheet->getCell('A' . $line)->getCalculatedValue())) { + $cells = []; + for ($j = 1; $j <= $columns; ++$j) { + $cells[] = $workSheet->getCell(StringUtils::intToAlphabet($j) . $line)->getCalculatedValue(); + } + + if ($transform !== null) { + foreach ($cells as $idx => $cell) { + $cells[$idx] = $transform($titles[$idx], $cell); + } + } + + ++$line; + + $query->values(...$cells); + ++$counter; + + if ($counter > 250) { + break; + } + } + + $query->execute(); + } while ($hasData); + } + } + + /** + * {@inheritdoc} + */ + public function export(string $path, array $queries) : void { $sheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $sheet->getProperties() @@ -169,44 +241,47 @@ final class SpreadsheetDatabaseMapper implements IODatabaseMapper } } - if (StringUtils::endsWith($this->path, '.xlsx')) { - (new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($sheet))->save($this->path); - } elseif (StringUtils::endsWith($this->path, '.ods')) { - (new \PhpOffice\PhpSpreadsheet\Writer\Ods($sheet))->save($this->path); + if (StringUtils::endsWith($path, '.xlsx')) { + (new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($sheet))->save($path); + } elseif (StringUtils::endsWith($path, '.ods')) { + (new \PhpOffice\PhpSpreadsheet\Writer\Ods($sheet))->save($path); } else { - (new \PhpOffice\PhpSpreadsheet\Writer\Xls($sheet))->save($this->path); + (new \PhpOffice\PhpSpreadsheet\Writer\Xls($sheet))->save($path); } } /** * {@inheritdoc} */ - public function update() : void + public function update(string $path, string $table = '') : void { $reader = null; - if (StringUtils::endsWith($this->path, '.xlsx')) { + if (StringUtils::endsWith($path, '.xlsx')) { $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); - } elseif (StringUtils::endsWith($this->path, '.ods')) { + } elseif (StringUtils::endsWith($path, '.ods')) { $reader = new \PhpOffice\PhpSpreadsheet\Reader\Ods(); } else { $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); } $reader->setReadDataOnly(true); - $sheet = $reader->load($this->path); + $sheet = $reader->load($path); $tables = $sheet->getSheetCount(); for ($i = 0; $i < $tables; ++$i) { $sheet->setActiveSheetIndex($i); $workSheet = $sheet->getSheet($i); - $table = $workSheet->getTitle(); + $table = \strtr(empty($table) ? $workSheet->getTitle() : $table, ' ', '_'); $titles = []; // get column titles $column = 1; while (!empty($value = $workSheet->getCell(StringUtils::intToAlphabet($column) . 1)->getCalculatedValue())) { + $value = \strtr(\trim($value), ' ', '_'); + $value = \preg_replace('/[^a-zA-Z0-9_]/', '', $value); $titles[] = $value; + ++$column; } diff --git a/Utils/Parser/Markdown/Markdown.php b/Utils/Parser/Markdown/Markdown.php index f4c982f1b..c7df8daba 100755 --- a/Utils/Parser/Markdown/Markdown.php +++ b/Utils/Parser/Markdown/Markdown.php @@ -3510,7 +3510,7 @@ class Markdown * * @since 1.0.0 */ - protected function insertAbreviation(array $element) : array + protected function insertAbbreviation(array $element) : array { if (!isset($element['text'])) { return $element; @@ -3570,7 +3570,7 @@ class Markdown $this->currentMeaning = $meaning; $inline['element'] = $this->elementApplyRecursiveDepthFirst( - 'insertAbreviation', + 'insertAbbreviation', $inline['element'] ); } @@ -3727,7 +3727,8 @@ class Markdown $dom = new \DOMDocument(); // http://stackoverflow.com/q/11309194/200145 - $elementMarkup = \mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8'); + // $elementMarkup = \mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8'); // Deprecated + $elementMarkup = \mb_encode_numericentity($elementMarkup, [0x80, 0x10FFFF, 0, ~0], 'UTF-8' ); // http://stackoverflow.com/q/4879946/200145 $dom->loadHTML($elementMarkup); @@ -4494,31 +4495,6 @@ class Markdown return $element; } - /** - * Handle element recursively - * - * @param string|\Closure $closure Closure for handling element - * @param array $element Element to handle - * - * @return array - * - * @since 1.0.0 - */ - protected function elementApplyRecursive(string|\Closure $closure, array $element) : array - { - $element = \is_string($closure) ? $this->{$closure}($element) : $closure($element); - - if (isset($element['elements'])) { - foreach ($element['elements'] as &$e) { - $e = $this->elementApplyRecursive($closure, $e); - } - } elseif (isset($element['element'])) { - $element['element'] = $this->elementApplyRecursive($closure, $element['element']); - } - - return $element; - } - /** * Handle element recursively * diff --git a/tests/DataStorage/Database/Query/BuilderTest.php b/tests/DataStorage/Database/Query/BuilderTest.php index cd6d009bc..09906588e 100755 --- a/tests/DataStorage/Database/Query/BuilderTest.php +++ b/tests/DataStorage/Database/Query/BuilderTest.php @@ -14,6 +14,8 @@ declare(strict_types=1); namespace phpOMS\tests\DataStorage\Database\Query; +include_once __DIR__ . '/../../../Autoloader.php'; + use phpOMS\DataStorage\Database\Connection\MysqlConnection; use phpOMS\DataStorage\Database\Connection\PostgresConnection; use phpOMS\DataStorage\Database\Connection\SQLiteConnection; @@ -29,6 +31,10 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase { public static function dbConnectionProvider() : array { + if (!isset($GLOBALS['CONFIG'])) { + $GLOBALS['CONFIG'] = include __DIR__ . '/../../../config.php'; + } + $cons = [ [new MysqlConnection($GLOBALS['CONFIG']['db']['core']['masters']['admin'])], [new PostgresConnection($GLOBALS['CONFIG']['db']['core']['postgresql']['admin'])], @@ -59,26 +65,31 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] AS t FROM [a] AS b WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->selectAs('a.test', 't')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT DISTINCT [a].[test] FROM [a] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->distinct()->from('a')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test], [b].[test] FROM [a], [b] WHERE [a].[test] = \'abc\';'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test', 'b.test')->from('a', 'b')->where('a.test', '=', 'abc')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $datetime = new \DateTime('now'); $sql = 'SELECT [a].[test], [b].[test] FROM [a], [b] WHERE [a].[test] = \'' . $datetime->format('Y-m-d H:i:s') . '\';'; @@ -86,6 +97,7 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase self::assertEquals($sql, $query->select('a.test', 'b.test')->from('a', 'b')->where('a.test', '=', $datetime)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test], [b].[test] FROM [a], [b] WHERE [a].[test] = \'abc\' ORDER BY [a].[test] ASC, [b].[test] DESC;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, @@ -97,6 +109,7 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase ); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test], [b].[test] FROM [a], [b] WHERE [a].[test] = :abcValue ORDER BY [a].[test] ASC, [b].[test] DESC;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, @@ -118,6 +131,7 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] AS b WHERE [a].[test] = 1 ORDER BY RAND() LIMIT 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->random('a.test')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); @@ -131,6 +145,7 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] AS b ORDER BY RANDOM() LIMIT 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->random('a.test')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); @@ -144,6 +159,7 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] AS b ORDER BY RANDOM() LIMIT 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->random('a.test')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); @@ -157,6 +173,7 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT TOP 1 [a].[test] FROM [a] AS b ORDER BY IDX FETCH FIRST 1 ROWS ONLY;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->random('a.test')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); @@ -177,31 +194,37 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 ORDER BY [a].[test] DESC;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->newest('a.test')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 ORDER BY [a].[test] ASC;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->oldest('a.test')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 ORDER BY [a].[test] DESC;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orderBy('a.test', 'DESC')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 ORDER BY [a].[test] ASC;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orderBy('a.test', 'ASC')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 ORDER BY [a].[test] DESC, [a].[test2] DESC;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orderBy(['a.test', 'a.test2'], ['DESC', 'DESC'])->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 ORDER BY [a].[test] ASC, [a].[test2] ASC;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orderBy(['a.test', 'a.test2'], 'ASC')->toSql()); @@ -222,11 +245,13 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 LIMIT 3;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->limit(3)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OFFSET 3;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->offset(3)->toSql()); @@ -247,19 +272,23 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 GROUP BY [a];'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->groupBy('a')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 GROUP BY [a], [b];'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->groupBy('a')->groupBy('b')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->groupBy('a', 'b')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = :test GROUP BY [a], [b];'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', new Parameter('test'))->groupBy('a', 'b')->toSql()); @@ -280,71 +309,85 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 0;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', false)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', true)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = \'string\';'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 'string')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1.23;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1.23)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 AND [a].[test2] = 2;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->where('a.test2', '=', 2, 'and')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 AND [a].[test2] = 2;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->andWhere('a.test2', '=', 2)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OR [a].[test2] = 2;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->where('a.test2', '=', 2, 'or')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OR [a].[test2] = 2;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orWhere('a.test2', '=', 2)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OR [a].[test2] IS NULL;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->whereNull('a.test2', 'or')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OR [a].[test2] IS NOT NULL;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->whereNotNull('a.test2', 'or')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OR [a].[test2] IN (1, 2, 3);'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->whereIn('a.test2', [1, 2, 3], 'or')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OR [a].[test2] IN (\'a\', \'b\', \'c\');'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->whereIn('a.test2', ['a', 'b', 'c'], 'or')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = :testWhere OR [a].[test2] IN (\'a\', :bValue, \'c\');'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', new Parameter('testWhere'))->whereIn('a.test2', ['a', new Parameter('bValue'), 'c'], 'or')->toSql()); @@ -365,71 +408,85 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->join('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] JOIN [b] ON [a].[id] = [b].[id] OR [a].[id2] = [b].[id2] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->join('b')->on('a.id', '=', 'b.id')->orOn('a.id2', '=', 'b.id2')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] JOIN [b] ON [a].[id] = [b].[id] AND [a].[id2] = [b].[id2] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->join('b')->on('a.id', '=', 'b.id')->andOn('a.id2', '=', 'b.id2')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] LEFT JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->leftJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] LEFT OUTER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->leftOuterJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] LEFT INNER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->leftInnerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] RIGHT JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->rightJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] RIGHT OUTER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->rightOuterJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] RIGHT INNER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->rightInnerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] OUTER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->outerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] INNER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->innerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] CROSS JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->crossJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] FULL JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->fullJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'SELECT [a].[test] FROM [a] FULL OUTER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->select('a.test')->from('a')->fullOuterJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); @@ -450,27 +507,32 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'INSERT INTO [a] VALUES (1, \'test\');'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->insert()->into('a')->values(1, 'test')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'INSERT INTO [a] VALUES (1, \'test\');'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->insert()->into('a')->value([1, 'test'])->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'INSERT INTO [a] ([test], [test2]) VALUES (1, \'test\');'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->insert('test', 'test2')->into('a')->values(1, 'test')->toSql()); self::assertEquals([[1, 'test']], $query->getValues()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'INSERT INTO [a] ([test], [test2]) VALUES (1, \'test\'), (2, \'test2\');'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->insert('test', 'test2')->into('a')->values(1, 'test')->values(2, 'test2')->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'INSERT INTO [a] ([test], [test2]) VALUES (:test, :test2);'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->insert('test', 'test2')->into('a')->values(new Parameter('test'), new Parameter('test2'))->toSql()); @@ -491,11 +553,13 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'DELETE FROM [a] WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->delete()->from('a')->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'DELETE FROM [a] WHERE [a].[test] = :testVal;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->delete()->from('a')->where('a.test', '=', new Parameter('testVal'))->toSql()); @@ -516,21 +580,571 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase $iE = $con->getGrammar()->systemIdentifierEnd; $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'UPDATE [a] SET [test] = 1, [test2] = 2 WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->update('a')->set(['test' => 1])->set(['test2' => 2])->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'UPDATE [a] SET [test] = 1, [test2] = 2 WHERE [a].[test] = 1;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->update('a')->sets('test', 1)->sets('test2', 2)->where('a.test', '=', 1)->toSql()); $query = new Builder($con); + $query->usePreparedStmt = false; $sql = 'UPDATE [a] SET [test] = 1, [test2] = :test2 WHERE [a].[test] = :test3;'; $sql = \strtr($sql, '[]', $iS . $iE); self::assertEquals($sql, $query->update('a')->set(['test' => 1])->set(['test2' => new Parameter('test2')])->where('a.test', '=', new Parameter('test3'))->toSql()); } + #[\PHPUnit\Framework\Attributes\DataProvider('dbConnectionProvider')] + #[\PHPUnit\Framework\Attributes\Group('framework')] + #[\PHPUnit\Framework\Attributes\TestDox('Mysql selects form a valid query')] + public function testSelectPrepared($con) : void + { + if (!$con->isInitialized()) { + self::markTestSkipped(); + + return; + } + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] AS t FROM [a] AS b WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->selectAs('a.test', 't')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT DISTINCT [a].[test] FROM [a] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->distinct()->from('a')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test], [b].[test] FROM [a], [b] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test', 'b.test')->from('a', 'b')->where('a.test', '=', 'abc')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $datetime = new \DateTime('now'); + $sql = 'SELECT [a].[test], [b].[test] FROM [a], [b] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test', 'b.test')->from('a', 'b')->where('a.test', '=', $datetime)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test], [b].[test] FROM [a], [b] WHERE [a].[test] = ? ORDER BY [a].[test] ASC, [b].[test] DESC;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, + $query->select('a.test', 'b.test') + ->from('a', 'b') + ->where('a.test', '=', 'abc') + ->orderBy(['a.test', 'b.test', ], ['ASC', 'DESC', ]) + ->toSql() + ); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test], [b].[test] FROM [a], [b] WHERE [a].[test] = :abcValue ORDER BY [a].[test] ASC, [b].[test] DESC;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, + $query->select('a.test', 'b.test') + ->from('a', 'b') + ->where('a.test', '=', new Parameter('abcValue')) + ->orderBy(['a.test', 'b.test', ], ['ASC', 'DESC', ]) + ->toSql() + ); + + self::assertEquals($query->toSql(), $query->__toString()); + } + + public function testRandomMysqlPrepared() : void + { + $con = new MysqlConnection($GLOBALS['CONFIG']['db']['core']['masters']['admin']); + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] AS b WHERE [a].[test] = ? ORDER BY RAND() LIMIT ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->random('a.test')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); + } + + public function testRandomPostgresqlPrepared() : void + { + $con = new PostgresConnection($GLOBALS['CONFIG']['db']['core']['postgresql']['admin']); + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] AS b ORDER BY RANDOM() LIMIT ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->random('a.test')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); + } + + public function testRandomSQLitePrepared() : void + { + $con = new SQLiteConnection($GLOBALS['CONFIG']['db']['core']['sqlite']['admin']); + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] AS b ORDER BY RANDOM() LIMIT ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->random('a.test')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); + } + + public function testRandomSqlServerPrepared() : void + { + $con = new SqlServerConnection($GLOBALS['CONFIG']['db']['core']['mssql']['admin']); + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT TOP 1 [a].[test] FROM [a] AS b ORDER BY IDX FETCH FIRST 1 ROWS ONLY;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->random('a.test')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dbConnectionProvider')] + #[\PHPUnit\Framework\Attributes\Group('framework')] + #[\PHPUnit\Framework\Attributes\TestDox('Mysql orders form a valid query')] + public function testOrderPrepared($con) : void + { + if (!$con->isInitialized()) { + self::markTestSkipped(); + + return; + } + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? ORDER BY [a].[test] DESC;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->newest('a.test')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? ORDER BY [a].[test] ASC;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->oldest('a.test')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? ORDER BY [a].[test] DESC;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orderBy('a.test', 'DESC')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? ORDER BY [a].[test] ASC;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orderBy('a.test', 'ASC')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? ORDER BY [a].[test] DESC, [a].[test2] DESC;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orderBy(['a.test', 'a.test2'], ['DESC', 'DESC'])->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? ORDER BY [a].[test] ASC, [a].[test2] ASC;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orderBy(['a.test', 'a.test2'], 'ASC')->toSql()); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dbConnectionProvider')] + #[\PHPUnit\Framework\Attributes\Group('framework')] + #[\PHPUnit\Framework\Attributes\TestDox('Mysql offsets and limits form a valid query')] + public function testOffsetLimitPrepared($con) : void + { + if (!$con->isInitialized()) { + self::markTestSkipped(); + + return; + } + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? LIMIT ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->limit(3)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? OFFSET ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->offset(3)->toSql()); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dbConnectionProvider')] + #[\PHPUnit\Framework\Attributes\Group('framework')] + #[\PHPUnit\Framework\Attributes\TestDox('Mysql groupings form a valid query')] + public function testGroupPrepared($con) : void + { + if (!$con->isInitialized()) { + self::markTestSkipped(); + + return; + } + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? GROUP BY [a];'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->groupBy('a')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? GROUP BY [a], [b];'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->groupBy('a')->groupBy('b')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->groupBy('a', 'b')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = :test GROUP BY [a], [b];'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', new Parameter('test'))->groupBy('a', 'b')->toSql()); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dbConnectionProvider')] + #[\PHPUnit\Framework\Attributes\Group('framework')] + #[\PHPUnit\Framework\Attributes\TestDox('Mysql wheres form a valid query')] + public function testWheresPrepared($con) : void + { + if (!$con->isInitialized()) { + self::markTestSkipped(); + + return; + } + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', false)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', true)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 'string')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1.23;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1.23)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? AND [a].[test2] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->where('a.test2', '=', 2, 'and')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? AND [a].[test2] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->andWhere('a.test2', '=', 2)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? OR [a].[test2] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->where('a.test2', '=', 2, 'or')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? OR [a].[test2] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orWhere('a.test2', '=', 2)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? OR [a].[test2] IS NULL;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->whereNull('a.test2', 'or')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? OR [a].[test2] IS NOT NULL;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->whereNotNull('a.test2', 'or')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? OR [a].[test2] IN (?, ?, ?);'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->whereIn('a.test2', [1, 2, 3], 'or')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = ? OR [a].[test2] IN (?, ?, ?);'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->whereIn('a.test2', ['a', 'b', 'c'], 'or')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = :testWhere OR [a].[test2] IN (?, :bValue, ?);'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', new Parameter('testWhere'))->whereIn('a.test2', ['a', new Parameter('bValue'), 'c'], 'or')->toSql()); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dbConnectionProvider')] + #[\PHPUnit\Framework\Attributes\Group('framework')] + #[\PHPUnit\Framework\Attributes\TestDox('Mysql joins form a valid query')] + public function testJoinsPrepared($con) : void + { + if (!$con->isInitialized()) { + self::markTestSkipped(); + + return; + } + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->join('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] JOIN [b] ON [a].[id] = [b].[id] OR [a].[id2] = [b].[id2] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->join('b')->on('a.id', '=', 'b.id')->orOn('a.id2', '=', 'b.id2')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] JOIN [b] ON [a].[id] = [b].[id] AND [a].[id2] = [b].[id2] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->join('b')->on('a.id', '=', 'b.id')->andOn('a.id2', '=', 'b.id2')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] LEFT JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->leftJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] LEFT OUTER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->leftOuterJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] LEFT INNER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->leftInnerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] RIGHT JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->rightJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] RIGHT OUTER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->rightOuterJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] RIGHT INNER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->rightInnerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] OUTER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->outerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] INNER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->innerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] CROSS JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->crossJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] FULL JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->fullJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'SELECT [a].[test] FROM [a] FULL OUTER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->select('a.test')->from('a')->fullOuterJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dbConnectionProvider')] + #[\PHPUnit\Framework\Attributes\Group('framework')] + #[\PHPUnit\Framework\Attributes\TestDox('Mysql inserts form a valid query')] + public function testInsertPrepared($con) : void + { + if (!$con->isInitialized()) { + self::markTestSkipped(); + + return; + } + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'INSERT INTO [a] VALUES (?, ?);'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->insert()->into('a')->values(1, 'test')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'INSERT INTO [a] VALUES (?, ?);'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->insert()->into('a')->value([1, 'test'])->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'INSERT INTO [a] ([test], [test2]) VALUES (?, ?);'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->insert('test', 'test2')->into('a')->values(1, 'test')->toSql()); + self::assertEquals([[1, 'test']], $query->getValues()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'INSERT INTO [a] ([test], [test2]) VALUES (?, ?), (?, ?);'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->insert('test', 'test2')->into('a')->values(1, 'test')->values(2, 'test2')->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'INSERT INTO [a] ([test], [test2]) VALUES (:test, :test2);'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->insert('test', 'test2')->into('a')->values(new Parameter('test'), new Parameter('test2'))->toSql()); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dbConnectionProvider')] + #[\PHPUnit\Framework\Attributes\Group('framework')] + #[\PHPUnit\Framework\Attributes\TestDox('Mysql deletes form a valid query')] + public function testDeletePrepared($con) : void + { + if (!$con->isInitialized()) { + self::markTestSkipped(); + + return; + } + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'DELETE FROM [a] WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->delete()->from('a')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'DELETE FROM [a] WHERE [a].[test] = :testVal;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->delete()->from('a')->where('a.test', '=', new Parameter('testVal'))->toSql()); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dbConnectionProvider')] + #[\PHPUnit\Framework\Attributes\Group('framework')] + #[\PHPUnit\Framework\Attributes\TestDox('Mysql updates form a valid query')] + public function testUpdatePrepared($con) : void + { + if (!$con->isInitialized()) { + self::markTestSkipped(); + + return; + } + + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'UPDATE [a] SET [test] = ?, [test2] = ? WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->update('a')->set(['test' => 1])->set(['test2' => 2])->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'UPDATE [a] SET [test] = ?, [test2] = ? WHERE [a].[test] = ?;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->update('a')->sets('test', 1)->sets('test2', 2)->where('a.test', '=', 1)->toSql()); + + $query = new Builder($con); + $query->usePreparedStmt = true; + $sql = 'UPDATE [a] SET [test] = ?, [test2] = :test2 WHERE [a].[test] = :test3;'; + $sql = \strtr($sql, '[]', $iS . $iE); + self::assertEquals($sql, $query->update('a')->set(['test' => 1])->set(['test2' => new Parameter('test2')])->where('a.test', '=', new Parameter('test3'))->toSql()); + } + #[\PHPUnit\Framework\Attributes\DataProvider('dbConnectionProvider')] #[\PHPUnit\Framework\Attributes\Group('framework')] #[\PHPUnit\Framework\Attributes\TestDox('Raw queries get output as defined')] diff --git a/tests/Utils/Parser/Markdown/MarkdownTest.php b/tests/Utils/Parser/Markdown/MarkdownTest.php index 390b603e9..4f3f0c3ec 100755 --- a/tests/Utils/Parser/Markdown/MarkdownTest.php +++ b/tests/Utils/Parser/Markdown/MarkdownTest.php @@ -34,7 +34,7 @@ final class MarkdownTest extends \PHPUnit\Framework\TestCase $data = \explode('.', $file); if ($data[1] === 'md' - && (\file_get_contents(__DIR__ . '/data/' . $data[0] . '.html') !== ($parsed = Markdown::parse(\file_get_contents(__DIR__ . '/data/' . $data[0] . '.md')))) + && (($expected = \file_get_contents(__DIR__ . '/data/' . $data[0] . '.html')) !== ($parsed = Markdown::parse(\file_get_contents(__DIR__ . '/data/' . $data[0] . '.md')))) ) { self::assertTrue(false, $file . "\n\n" . $parsed); } diff --git a/tests/config.php b/tests/config.php new file mode 100644 index 000000000..684f71d97 --- /dev/null +++ b/tests/config.php @@ -0,0 +1,299 @@ + [ + 'core' => [ + 'masters' => [ + 'admin' => [ + 'db' => 'mysql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '3306', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'insert' => [ + 'db' => 'mysql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '3306', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'select' => [ + 'db' => 'mysql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '3306', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'update' => [ + 'db' => 'mysql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '3306', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'delete' => [ + 'db' => 'mysql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '3306', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'schema' => [ + 'db' => 'mysql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '3306', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + ], + 'postgresql' => [ + 'admin' => [ + 'db' => 'pgsql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '5432', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'insert' => [ + 'db' => 'pgsql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '5432', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'select' => [ + 'db' => 'pgsql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '5432', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'update' => [ + 'db' => 'pgsql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '5432', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'delete' => [ + 'db' => 'pgsql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '5432', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'schema' => [ + 'db' => 'pgsql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '5432', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + ], + 'sqlite' => [ + 'admin' => [ + 'db' => 'sqlite', /* db type */ + 'database' => __DIR__ . '/../Localization/Defaults/localization.sqlite', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'insert' => [ + 'db' => 'sqlite', /* db type */ + 'database' => __DIR__ . '/../Localization/Defaults/localization.sqlite', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'select' => [ + 'db' => 'sqlite', /* db type */ + 'database' => __DIR__ . '/../Localization/Defaults/localization.sqlite', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'update' => [ + 'db' => 'sqlite', /* db type */ + 'database' => __DIR__ . '/../Localization/Defaults/localization.sqlite', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'delete' => [ + 'db' => 'sqlite', /* db type */ + 'database' => __DIR__ . '/../Localization/Defaults/localization.sqlite', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'schema' => [ + 'db' => 'sqlite', /* db type */ + 'database' => __DIR__ . '/../Localization/Defaults/localization.sqlite', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + ], + 'mssql' => [ + 'admin' => [ + 'db' => 'mssql', /* db type */ + 'host' => 'localhost', /* db host address */ + 'port' => '1433', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'insert' => [ + 'db' => 'mssql', /* db type */ + 'host' => 'localhost', /* db host address */ + 'port' => '1433', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'select' => [ + 'db' => 'mssql', /* db type */ + 'host' => 'localhost', /* db host address */ + 'port' => '1433', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'update' => [ + 'db' => 'mssql', /* db type */ + 'host' => 'localhost', /* db host address */ + 'port' => '1433', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'delete' => [ + 'db' => 'mssql', /* db type */ + 'host' => 'localhost', /* db host address */ + 'port' => '1433', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + 'schema' => [ + 'db' => 'mssql', /* db type */ + 'host' => 'localhost', /* db host address */ + 'port' => '1433', /* db host port */ + 'login' => 'test', /* db login name */ + 'password' => 'orange', /* db login password */ + 'database' => 'omt', /* db name */ + 'weight' => 1000, /* db table prefix */ + 'datetimeformat' => 'Y-m-d H:i:s', + ], + ], + ], + ], + 'cache' => [ + 'redis' => [ + 'db' => 1, + 'host' => '127.0.0.1', + 'port' => 6379, + ], + 'memcached' => [ + 'host' => '127.0.0.1', + 'port' => 11211, + ], + ], + 'mail' => [ + 'imap' => [ + 'host' => '127.0.0.1', + 'port' => 143, + 'ssl' => false, + 'user' => 'test', + 'password' => '123456', + ], + 'pop3' => [ + 'host' => '127.0.0.1', + 'port' => 25, + 'ssl' => false, + 'user' => 'test', + 'password' => '123456', + ], + ], + 'log' => [ + 'file' => [ + 'path' => __DIR__ . '/Logs', + ], + ], + 'page' => [ + 'root' => '/', + 'https' => false, + ], + 'app' => [ + 'path' => __DIR__, + 'default' => [ + 'app' => 'Backend', + 'id' => 'backend', + 'lang' => 'en', + 'theme' => 'Backend', + 'org' => 1, + ], + 'domains' => [ + '127.0.0.1' => [ + 'app' => 'Backend', + 'id' => 'backend', + 'lang' => 'en', + 'theme' => 'Backend', + 'org' => 1, + ], + ], + ], + 'socket' => [ + 'master' => [ + 'host' => '127.0.0.1', + 'limit' => 300, + 'port' => 4310, + ], + ], + 'language' => [ + 'en', + ], + 'apis' => [ + ], +]; \ No newline at end of file