diff --git a/.gitignore b/.gitignore index bf0824e59..954fa8953 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -*.log \ No newline at end of file +*.log +.directory +vendor \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..857485eed --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: php +php: + - '7.2' + - nightly +services: + - mysql +before_script: + - pecl install ast + - composer install + - git clone -b develop https://github.com/Orange-Management/Build.git + - chmod -R 755 Build/Hooks +addons: + code_climate: + repo_token: + secure: "zXoveZL6QPXXcdyGxfgjlIRGSFbTgwi2NU6V9tROypLMA+5IWRd0CvvQkl4vx2lIAF5Pn3jOGqG6p6qinqwSzX2tE/RsdDEDz8LPKpN+AShyxLzzYu2ohDSqm3rSXysdgbjQBB1YoUIumhUGABfYIdZk+7g1Yjc3RLFdAs3YzB7CdG4zeBA1Xe82KHUDwZb2M/MG/Bf9jsi9HmjrBeMTmCiW8v6wclYG9rbkIoXY4mv4H0ywiTv4lHuyRWs19kJn4kKI3oLqmv0UcUmLv0wNgPrww2STtN/7ghlaiLNSrdKQV1EuVi/nRxxNlH2z+h8cMPAcksqZ29TT9RYkK32433QIUHYzN6t4LB8PgNkFPMc6zI6uzD9We4oYAw4bVIxkEgHXJBInfYSNRfoGPGHZ9WU+Ph9zt2nyB4mS6014gHgjBZSF+DV/r1mHa9XtXPDzxrbHJF7rIp99iOBnpGBcn4ERVhLP6onuam86TSgXjgyUrnTGazwKoc6cfwvb9RswpVtTvNJIYgzfJG6+QFAsltOSo3iRvKq+F2X3Lh/3dOAAcqh3TOZaSPee2sHCDFYq5d0qpNFJq/o6tEcPP9J6PmKsD7Ql9Zu1qNJ2MevheE/U/3sh2dy/5rhDSFCig2TRMy/53QATcCm2ngIfBJkeVyLQ2VMUsMXOk5v2uF16E6k=" +script: + - vendor/bin/phpunit --version && vendor/bin/phpunit --configuration tests/phpunit_no_coverage.xml + - pwd + - ./Build/Hooks/travis.sh +after_script: + - vendor/bin/test-reporter +notifications: + email: false \ No newline at end of file diff --git a/Account/Account.php b/Account/Account.php index 597ea510c..5c53f8cc7 100644 --- a/Account/Account.php +++ b/Account/Account.php @@ -453,11 +453,11 @@ class Account implements ArrayableInterface, \JsonSerializable */ public function setEmail(string $email) : void { - if (!Email::isValid($email)) { + if ($email !== '' && !Email::isValid($email)) { throw new \InvalidArgumentException(); } - $this->email = mb_strtolower($email); + $this->email = \mb_strtolower($email); } /** @@ -561,11 +561,13 @@ class Account implements ArrayableInterface, \JsonSerializable */ public function generatePassword(string $password) : void { - $this->password = \password_hash($password, \PASSWORD_DEFAULT); + $temp = \password_hash($password, \PASSWORD_DEFAULT); - if ($this->password === false) { + if ($temp === false) { throw new \Exception(); } + + $this->password = $temp; } /** @@ -603,7 +605,7 @@ class Account implements ArrayableInterface, \JsonSerializable */ public function __toString() : string { - return \json_encode($this->toArray()); + return (string) \json_encode($this->toArray()); } /** diff --git a/Account/Group.php b/Account/Group.php index 8bcd3e429..a22a52e2d 100644 --- a/Account/Group.php +++ b/Account/Group.php @@ -189,7 +189,7 @@ class Group implements ArrayableInterface, \JsonSerializable */ public function __toString() : string { - return \json_encode($this->toArray()); + return (string) \json_encode($this->toArray()); } /** diff --git a/ApplicationAbstract.php b/ApplicationAbstract.php index 5bddb2f24..9a134a294 100644 --- a/ApplicationAbstract.php +++ b/ApplicationAbstract.php @@ -22,6 +22,7 @@ namespace phpOMS; * and afterwards read only. * * @property string $appName + * @property int $orgId * @property \phpOMS\DataStorage\Database\DatabasePool $dbPool * @property \phpOMS\Localization\L11nManager $l11nManager * @property \phpOMS\Router\Router $router @@ -50,6 +51,14 @@ class ApplicationAbstract */ protected $appName = ''; + /** + * Organization id. + * + * @var int + * @since 1.0.0 + */ + protected $orgId = 1; + /** * Database object. * diff --git a/Autoloader.php b/Autoloader.php index 9ee328ee4..d05b6c2d0 100644 --- a/Autoloader.php +++ b/Autoloader.php @@ -81,7 +81,7 @@ final class Autoloader $class = \str_replace(['_', '\\'], '/', $class); foreach (self::$paths as $path) { - if (file_exists($file = $path . $class . '.php')) { + if (\file_exists($file = $path . $class . '.php')) { include_once $file; return; @@ -106,7 +106,7 @@ final class Autoloader $class = \str_replace(['_', '\\'], '/', $class); foreach (self::$paths as $path) { - if (file_exists($file = $path . $class . '.php')) { + if (\file_exists($file = $path . $class . '.php')) { return true; } } diff --git a/Business/Finance/FinanceFormulas.php b/Business/Finance/FinanceFormulas.php index ba21fee44..3f80c7ef3 100644 --- a/Business/Finance/FinanceFormulas.php +++ b/Business/Finance/FinanceFormulas.php @@ -48,7 +48,7 @@ final class FinanceFormulas */ public static function getAnnualPercentageYield(float $r, int $n) : float { - return (float) pow(1 + $r / $n, $n) - 1; + return pow(1 + $r / $n, $n) - 1; } /** @@ -65,7 +65,7 @@ final class FinanceFormulas */ public static function getStateAnnualInterestRateOfAPY(float $apy, int $n) : float { - return (float) (pow($apy + 1, 1 / $n) - 1) * $n; + return (pow($apy + 1, 1 / $n) - 1) * $n; } /** @@ -922,7 +922,7 @@ final class FinanceFormulas */ public static function getFutureValueFactor(float $r, int $n) : float { - return (float) pow(1 + $r, $n); + return pow(1 + $r, $n); } /** diff --git a/Business/Finance/StockBonds.php b/Business/Finance/StockBonds.php index 40ae2024a..54f6d1721 100644 --- a/Business/Finance/StockBonds.php +++ b/Business/Finance/StockBonds.php @@ -369,6 +369,6 @@ final class StockBonds */ public static function getZeroCouponBondEffectiveYield(float $F, float $PV, int $n) : float { - return (float) pow($F / $PV, 1 / $n) - 1; + return pow($F / $PV, 1 / $n) - 1; } } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b0f7afa67..8b995dab4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,27 +1,27 @@ -# Contributing - -## Code Style & Best Practices - -For the code style and best practices please have a look at the developer-guide [https://github.com/Orange-Management/Developer-Guide](https://github.com/Orange-Management/Developer-Guide). Especially the `standards` should be followed for a successful pull request. - -## How Can I Contribute - -### Suggestions - -If you have a good idea for improvement feel free to create a new issue with all the relevant information. - -### Issues - -Feel free to grab any open issue implement it and create a new pull request. - -### Code Style - -Not always are the defined coding style standards upheld, if you notice that simply create a new pull request with the fix. - -### Code Coverage - -While code coverage is just a metric it is still appreciated to cover as many files and code paths as possible. Just have a look at the code coverage and implement missing unit tests. - -### Freatures - +# Contributing + +## Code Style & Best Practices + +For the code style and best practices please have a look at the developer-guide [https://github.com/Orange-Management/Developer-Guide](https://github.com/Orange-Management/Developer-Guide). Especially the `standards` should be followed for a successful pull request. + +## How Can I Contribute + +### Suggestions + +If you have a good idea for improvement feel free to create a new issue with all the relevant information. + +### Issues + +Feel free to grab any open issue implement it and create a new pull request. + +### Code Style + +Not always are the defined coding style standards upheld, if you notice that simply create a new pull request with the fix. + +### Code Coverage + +While code coverage is just a metric it is still appreciated to cover as many files and code paths as possible. Just have a look at the code coverage and implement missing unit tests. + +### Freatures + You have a good idea for a feature and can implement it yourself, go ahead and create a new pull request. \ No newline at end of file diff --git a/Config/SettingsAbstract.php b/Config/SettingsAbstract.php index 042805f76..f23ecd39a 100644 --- a/Config/SettingsAbstract.php +++ b/Config/SettingsAbstract.php @@ -105,6 +105,11 @@ abstract class SettingsAbstract implements OptionsInterface $sth->execute(); $options = $sth->fetchAll(\PDO::FETCH_KEY_PAIR); + + if ($options === false) { + return []; + } + $this->setOptions($options); break; } diff --git a/Console/CommandManager.php b/Console/CommandManager.php deleted file mode 100644 index a76251c97..000000000 --- a/Console/CommandManager.php +++ /dev/null @@ -1,129 +0,0 @@ -commands[$cmd])) { - $this->commands[$cmd] = [$callback, $source]; - $this->count++; - - return true; - } - - return false; - } - - /** - * Detach existing command. - * - * @param string $cmd Command ID - * @param mixed $source Provider - * - * @return bool - * - * @since 1.0.0 - */ - public function detach(string $cmd, $source) : bool - { - if (array_key_exists($cmd, $this->commands)) { - unset($this->commands[$cmd]); - $this->count--; - - return true; - } - - return false; - } - - /** - * Trigger command. - * - * @param string $cmd Command ID - * @param mixed $para Parameters to pass - * - * @return mixed|bool - * - * @since 1.0.0 - */ - public function trigger(string $cmd, $para) - { - if (array_key_exists($cmd, $this->commands)) { - return $this->commands[$cmd][0]($para); - } - - return false; - } - - /** - * Count commands. - * - * @return int - * - * @since 1.0.0 - */ - public function count() : int - { - return $this->count; - } -} diff --git a/DataStorage/Cache/Connection/ConnectionAbstract.php b/DataStorage/Cache/Connection/ConnectionAbstract.php index 8f5135a82..8baac3b3b 100644 --- a/DataStorage/Cache/Connection/ConnectionAbstract.php +++ b/DataStorage/Cache/Connection/ConnectionAbstract.php @@ -15,6 +15,7 @@ declare(strict_types=1); namespace phpOMS\DataStorage\Cache\Connection; use phpOMS\DataStorage\Cache\CacheStatus; +use phpOMS\DataStorage\Cache\CacheType; /** * Cache handler. @@ -38,7 +39,7 @@ abstract class ConnectionAbstract implements ConnectionInterface * @var mixed * @since 1.0.0 */ - private $con = null; + protected $con = null; /** * Database prefix. @@ -64,7 +65,7 @@ abstract class ConnectionAbstract implements ConnectionInterface * @var string * @since 1.0.0 */ - protected $type = CacheStatus::UNDEFINED; + protected $type = CacheType::UNDEFINED; /** * Database status. diff --git a/DataStorage/Cache/Connection/FileCache.php b/DataStorage/Cache/Connection/FileCache.php index e924953d2..2ba18837d 100644 --- a/DataStorage/Cache/Connection/FileCache.php +++ b/DataStorage/Cache/Connection/FileCache.php @@ -218,7 +218,7 @@ class FileCache extends ConnectionAbstract if ($type === CacheValueType::_INT || $type === CacheValueType::_FLOAT || $type === CacheValueType::_STRING || $type === CacheValueType::_BOOL) { return (string) $value; } elseif ($type === CacheValueType::_ARRAY) { - return \json_encode($value); + return (string) \json_encode($value); } elseif ($type === CacheValueType::_SERIALIZABLE) { return \get_class($value) . self::DELIM . $value->serialize(); } elseif ($type === CacheValueType::_JSONSERIALIZABLE) { @@ -241,8 +241,8 @@ class FileCache extends ConnectionAbstract */ private function getExpire(string $raw) : int { - $expireStart = \strpos($raw, self::DELIM); - $expireEnd = \strpos($raw, self::DELIM, $expireStart + 1); + $expireStart = (int) \strpos($raw, self::DELIM); + $expireEnd = (int) \strpos($raw, self::DELIM, $expireStart + 1); return (int) \substr($raw, $expireStart + 1, $expireEnd - ($expireStart + 1)); } @@ -257,7 +257,6 @@ class FileCache extends ConnectionAbstract } $path = $this->getPath($key); - if (!File::exists($path)) { return null; } @@ -269,12 +268,21 @@ class FileCache extends ConnectionAbstract return null; } - $raw = File::get($path); - $type = (int) $raw[0]; + $raw = \file_get_contents($path); + if ($raw === false) { + return null; + } + + $type = (int) $raw[0]; + $expireStart = (int) \strpos($raw, self::DELIM); + $expireEnd = (int) \strpos($raw, self::DELIM, $expireStart + 1); + + if ($expireStart < 0 || $expireEnd < 0) { + return null; + } - $expireStart = \strpos($raw, self::DELIM); - $expireEnd = \strpos($raw, self::DELIM, $expireStart + 1); $cacheExpire = \substr($raw, $expireStart + 1, $expireEnd - ($expireStart + 1)); + $cacheExpire = ($cacheExpire === false) ? $created : (int) $cacheExpire; if ($cacheExpire >= 0 && $created + $cacheExpire < $now) { $this->delete($key); @@ -314,18 +322,23 @@ class FileCache extends ConnectionAbstract $value = \substr($raw, $expireEnd + 1); break; case CacheValueType::_ARRAY: - $value = \json_decode(substr($raw, $expireEnd + 1)); + $array = \substr($raw, $expireEnd + 1); + $value = \json_decode($array === false ? '[]' : $array, true); break; case CacheValueType::_NULL: $value = null; break; case CacheValueType::_SERIALIZABLE: case CacheValueType::_JSONSERIALIZABLE: - $namespaceStart = \strpos($raw, self::DELIM, $expireEnd); - $namespaceEnd = \strpos($raw, self::DELIM, $namespaceStart + 1); + $namespaceStart = (int) \strpos($raw, self::DELIM, $expireEnd); + $namespaceEnd = (int) \strpos($raw, self::DELIM, $namespaceStart + 1); $namespace = \substr($raw, $namespaceStart, $namespaceEnd); - $value = $namespace::unserialize(substr($raw, $namespaceEnd + 1)); + if ($namespace === false) { + return null; + } + + $value = $namespace::unserialize(\substr($raw, $namespaceEnd + 1)); break; } @@ -342,7 +355,6 @@ class FileCache extends ConnectionAbstract } $path = $this->getPath($key); - if ($expire < 0 && File::exists($path)) { File::delete($path); @@ -350,12 +362,29 @@ class FileCache extends ConnectionAbstract } if ($expire >= 0) { - $created = Directory::created(Directory::sanitize($key, self::SANITIZE))->getTimestamp(); - $now = \time(); - $raw = \file_get_contents($path); - $expireStart = \strpos($raw, self::DELIM); - $expireEnd = \strpos($raw, self::DELIM, $expireStart + 1); + $created = Directory::created(Directory::sanitize($key, self::SANITIZE))->getTimestamp(); + $now = \time(); + $raw = \file_get_contents($path); + + if ($raw === false) { + return false; + } + + $expireStart = (int) \strpos($raw, self::DELIM); + $expireEnd = (int) \strpos($raw, self::DELIM, $expireStart + 1); + + if ($expireStart < 0 || $expireEnd < 0) { + return false; + } + $cacheExpire = \substr($raw, $expireStart + 1, $expireEnd - ($expireStart + 1)); + $cacheExpire = ($cacheExpire === false) ? $created : (int) $cacheExpire; + + if ($cacheExpire >= 0 && $created + $cacheExpire < $now) { + $this->delete($key); + + return false; + } if ($cacheExpire >= 0 && $created + $cacheExpire > $now) { File::delete($path); diff --git a/DataStorage/DataStorageConnectionInterface.php b/DataStorage/DataStorageConnectionInterface.php index 3f79866a3..a69f3421e 100644 --- a/DataStorage/DataStorageConnectionInterface.php +++ b/DataStorage/DataStorageConnectionInterface.php @@ -25,6 +25,15 @@ namespace phpOMS\DataStorage; interface DataStorageConnectionInterface { + /** + * Get prefix. + * + * @return string + * + * @since 1.0.0 + */ + public function getPrefix() : string; + /** * Connect to datastorage. * diff --git a/DataStorage/DataStoragePoolInterface.php b/DataStorage/DataStoragePoolInterface.php index bc3bd597f..d55c14d72 100644 --- a/DataStorage/DataStoragePoolInterface.php +++ b/DataStorage/DataStoragePoolInterface.php @@ -44,7 +44,7 @@ interface DataStoragePoolInterface * * @param string $key Connection key * - * @return mixed + * @return DataStorageConnectionInterface * * @since 1.0.0 */ diff --git a/DataStorage/Database/BuilderAbstract.php b/DataStorage/Database/BuilderAbstract.php index f5f81816b..e391224ab 100644 --- a/DataStorage/Database/BuilderAbstract.php +++ b/DataStorage/Database/BuilderAbstract.php @@ -14,7 +14,7 @@ declare(strict_types=1); namespace phpOMS\DataStorage\Database; -use phpOMS\DataStorage\Database\Connection\ConnectionAbstract; +use phpOMS\DataStorage\DataStorageConnectionInterface; use phpOMS\DataStorage\Database\Query\QueryType; /** @@ -38,7 +38,7 @@ abstract class BuilderAbstract /** * Database connection. * - * @var ConnectionAbstract + * @var DataStorageConnectionInterface * @since 1.0.0 */ protected $connection = null; @@ -49,7 +49,7 @@ abstract class BuilderAbstract * @var int * @since 1.0.0 */ - protected $type = QueryType::EMPTY; + protected $type = QueryType::NONE; /** * Prefix. @@ -68,15 +68,9 @@ abstract class BuilderAbstract public $raw = ''; /** - * Set prefix. - * - * @param string $prefix Prefix - * - * @return BuilderAbstract - * - * @since 1.0.0 + * {@inheritdoc} */ - public function prefix(string $prefix) : BuilderAbstract + public function prefix(string $prefix) : self { $this->prefix = $prefix; diff --git a/DataStorage/Database/Connection/ConnectionFactory.php b/DataStorage/Database/Connection/ConnectionFactory.php index 06016d28d..3d89957ea 100644 --- a/DataStorage/Database/Connection/ConnectionFactory.php +++ b/DataStorage/Database/Connection/ConnectionFactory.php @@ -44,13 +44,13 @@ final class ConnectionFactory * * @param string[] $dbdata the basic database information for establishing a connection * - * @return ConnectionInterface + * @return ConnectionAbstract * * @throws \InvalidArgumentException Throws this exception if the database is not supported. * * @since 1.0.0 */ - public static function create(array $dbdata) : ConnectionInterface + public static function create(array $dbdata) : ConnectionAbstract { switch ($dbdata['db']) { case DatabaseType::MYSQL: diff --git a/DataStorage/Database/Connection/MysqlConnection.php b/DataStorage/Database/Connection/MysqlConnection.php index a05fc19b3..92676ab56 100644 --- a/DataStorage/Database/Connection/MysqlConnection.php +++ b/DataStorage/Database/Connection/MysqlConnection.php @@ -59,7 +59,7 @@ final class MysqlConnection extends ConnectionAbstract $this->dbdata = isset($dbdata) ? $dbdata : $this->dbdata; if (!isset($this->dbdata['db'], $this->dbdata['host'], $this->dbdata['port'], $this->dbdata['database'], $this->dbdata['login'], $this->dbdata['password'])) { - throw new InvalidConnectionConfigException(\json_encode($this->dbdata)); + throw new InvalidConnectionConfigException((string) \json_encode($this->dbdata)); } $this->close(); diff --git a/DataStorage/Database/Connection/PostgresConnection.php b/DataStorage/Database/Connection/PostgresConnection.php index 33fa94d48..ae61b9338 100644 --- a/DataStorage/Database/Connection/PostgresConnection.php +++ b/DataStorage/Database/Connection/PostgresConnection.php @@ -58,7 +58,7 @@ final class PostgresConnection extends ConnectionAbstract $this->dbdata = isset($dbdata) ? $dbdata : $this->dbdata; if (!isset($this->dbdata['db'], $this->dbdata['host'], $this->dbdata['port'], $this->dbdata['database'], $this->dbdata['login'], $this->dbdata['password'])) { - throw new InvalidConnectionConfigException(\json_encode($this->dbdata)); + throw new InvalidConnectionConfigException((string) \json_encode($this->dbdata)); } $this->close(); diff --git a/DataStorage/Database/Connection/SQLiteConnection.php b/DataStorage/Database/Connection/SQLiteConnection.php index 5e45f345f..23374e332 100644 --- a/DataStorage/Database/Connection/SQLiteConnection.php +++ b/DataStorage/Database/Connection/SQLiteConnection.php @@ -16,7 +16,7 @@ namespace phpOMS\DataStorage\Database\Connection; use phpOMS\DataStorage\Database\DatabaseStatus; use phpOMS\DataStorage\Database\DatabaseType; -use phpOMS\DataStorage\Database\Query\Grammar\SqliteGrammar; +use phpOMS\DataStorage\Database\Query\Grammar\SQLiteGrammar; /** * Database handler. @@ -29,7 +29,7 @@ use phpOMS\DataStorage\Database\Query\Grammar\SqliteGrammar; * @link http://website.orange-management.de * @since 1.0.0 */ -final class SqliteConnection extends ConnectionAbstract +final class SQLiteConnection extends ConnectionAbstract { /** @@ -43,8 +43,8 @@ final class SqliteConnection extends ConnectionAbstract */ public function __construct(array $dbdata) { - $this->type = DatabaseType::MYSQL; - $this->grammar = new SqliteGrammar(); + $this->type = DatabaseType::SQLITE; + $this->grammar = new SQLiteGrammar(); $this->connect($dbdata); } @@ -65,9 +65,10 @@ final class SqliteConnection extends ConnectionAbstract $this->status = DatabaseStatus::OK; } catch (\PDOException $e) { - var_dump($e->getMessage()); $this->status = DatabaseStatus::MISSING_DATABASE; $this->con = null; + } finally { + $this->dbdata['password'] = '****'; } } } diff --git a/DataStorage/Database/DataMapperAbstract.php b/DataStorage/Database/DataMapperAbstract.php index bf0331ea1..ce749e46e 100644 --- a/DataStorage/Database/DataMapperAbstract.php +++ b/DataStorage/Database/DataMapperAbstract.php @@ -14,7 +14,7 @@ declare(strict_types=1); namespace phpOMS\DataStorage\Database; -use phpOMS\DataStorage\DataStorageConnectionInterface; +use phpOMS\DataStorage\Database\Connection\ConnectionAbstract; use phpOMS\DataStorage\Database\Query\Builder; use phpOMS\DataStorage\DataMapperInterface; use phpOMS\Message\RequestAbstract; @@ -37,7 +37,7 @@ class DataMapperAbstract implements DataMapperInterface /** * Database connection. * - * @var DataStorageConnectionInterface + * @var ConnectionAbstract * @since 1.0.0 */ protected static $db = null; @@ -149,7 +149,7 @@ class DataMapperAbstract implements DataMapperInterface /** * Highest mapper to know when to clear initialized objects * - * @var string + * @var null|string * @since 1.0.0 */ protected static $parentMapper = null; @@ -195,13 +195,13 @@ class DataMapperAbstract implements DataMapperInterface /** * Set database connection. * - * @param DataStorageConnectionInterface $con Database connection + * @param ConnectionAbstract $con Database connection * * @return void * * @since 1.0.0 */ - public static function setConnection(DataStorageConnectionInterface $con) : void + public static function setConnection(ConnectionAbstract $con) : void { self::$db = $con; } @@ -389,20 +389,20 @@ class DataMapperAbstract implements DataMapperInterface /** * Create base model. * - * @param Object $obj Model to create + * @param object $obj Model to create * @param \ReflectionClass $refClass Reflection class * * @return mixed * * @since 1.0.0 */ - private static function createModel($obj, \ReflectionClass $refClass) + private static function createModel(object $obj, \ReflectionClass $refClass) { $query = new Builder(self::$db); $query->prefix(self::$db->getPrefix())->into(static::$table); foreach (static::$columns as $key => $column) { - $propertyName = \stripos($column['internal'], '/') !== false ? explode('/', $column['internal'])[0] : $column['internal']; + $propertyName = \stripos($column['internal'], '/') !== false ? \explode('/', $column['internal'])[0] : $column['internal']; if (isset(static::$hasMany[$propertyName]) || isset(static::$hasOne[$propertyName])) { continue; } @@ -425,11 +425,11 @@ class DataMapperAbstract implements DataMapperInterface $query->insert($column['name'])->value($value, $column['type']); } elseif ($column['name'] !== static::$primaryField) { $tValue = $property->getValue($obj); - if (stripos($column['internal'], '/') !== false) { + if (\stripos($column['internal'], '/') !== false) { $path = \explode('/', $column['internal']); - array_shift($path); - $path = implode('/', $path); + \array_shift($path); + $path = \implode('/', $path); $tValue = ArrayUtils::getArray($path, $tValue, '/'); } @@ -444,7 +444,7 @@ class DataMapperAbstract implements DataMapperInterface } // if a table only has a single column = primary key column. This must be done otherwise the query is empty - if ($query->getType() === QueryType::EMPTY) { + if ($query->getType() === QueryType::NONE) { $query->insert(static::$primaryField)->value(0, static::$columns[static::$primaryField]['type']); } @@ -475,11 +475,11 @@ class DataMapperAbstract implements DataMapperInterface } $path = $column['internal']; - if (stripos($column['internal'], '/') !== false) { + if (\stripos($column['internal'], '/') !== false) { $path = \explode('/', $column['internal']); - array_shift($path); - $path = implode('/', $path); + \array_shift($path); + $path = \implode('/', $path); } $property = ArrayUtils::getArray($path, $obj, '/'); @@ -512,14 +512,14 @@ class DataMapperAbstract implements DataMapperInterface /** * Get id of object * - * @param Object $obj Model to create + * @param object $obj Model to create * @param \ReflectionClass $refClass Reflection class * * @return mixed * * @since 1.0.0 */ - private static function getObjectId($obj, \ReflectionClass $refClass = null) + private static function getObjectId(object $obj, \ReflectionClass $refClass = null) { $refClass = $refClass ?? new \ReflectionClass($obj); $refProp = $refClass->getProperty(static::$columns[static::$primaryField]['internal']); @@ -541,14 +541,14 @@ class DataMapperAbstract implements DataMapperInterface * Set id to model * * @param \ReflectionClass $refClass Reflection class - * @param Object $obj Object to create + * @param object $obj Object to create * @param mixed $objId Id to set * * @return void * * @since 1.0.0 */ - private static function setObjectId(\ReflectionClass $refClass, $obj, $objId) : void + private static function setObjectId(\ReflectionClass $refClass, object $obj, $objId) : void { $refProp = $refClass->getProperty(static::$columns[static::$primaryField]['internal']); @@ -568,7 +568,7 @@ class DataMapperAbstract implements DataMapperInterface * Create has many * * @param \ReflectionClass $refClass Reflection class - * @param Object $obj Object to create + * @param object $obj Object to create * @param mixed $objId Id to set * * @return void @@ -577,7 +577,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - private static function createHasMany(\ReflectionClass $refClass, $obj, $objId) : void + private static function createHasMany(\ReflectionClass $refClass, object $obj, $objId) : void { foreach (static::$hasMany as $propertyName => $rel) { $property = $refClass->getProperty($propertyName); @@ -602,7 +602,7 @@ class DataMapperAbstract implements DataMapperInterface $relReflectionClass = null; foreach ($values as $key => &$value) { - if (!is_object($value)) { + if (!\is_object($value)) { // Is scalar => already in database $objsIds[$key] = $value; @@ -672,7 +672,7 @@ class DataMapperAbstract implements DataMapperInterface $objsIds = []; foreach ($values as $key => &$value) { - if (!is_object($value)) { + if (!\is_array($value)) { // Is scalar => already in database $objsIds[$key] = $value; @@ -706,14 +706,14 @@ class DataMapperAbstract implements DataMapperInterface * Create has one * * @param \ReflectionClass $refClass Property name to initialize - * @param Object $obj Object to create + * @param object $obj Object to create * * @return mixed * @todo implement??? * * @since 1.0.0 */ - private static function createHasOne(\ReflectionClass $refClass, $obj) + private static function createHasOne(\ReflectionClass $refClass, object $obj) { throw new \Exception(); } @@ -724,7 +724,7 @@ class DataMapperAbstract implements DataMapperInterface * The reference is stored in the main model * * @param string $propertyName Property name to initialize - * @param Object $obj Object to create + * @param mixed $obj Object to create * * @return mixed * @@ -732,7 +732,7 @@ class DataMapperAbstract implements DataMapperInterface */ private static function createOwnsOne(string $propertyName, $obj) { - if (is_object($obj)) { + if (\is_object($obj)) { $mapper = static::$ownsOne[$propertyName]['mapper']; $primaryKey = $mapper::getObjectId($obj); @@ -760,7 +760,7 @@ class DataMapperAbstract implements DataMapperInterface */ private static function createOwnsOneArray(string $propertyName, array &$obj) { - if (is_array($obj)) { + if (\is_array($obj)) { $mapper = static::$ownsOne[$propertyName]['mapper']; $primaryKey = $obj[static::$columns[static::$primaryField]['internal']]; @@ -780,7 +780,7 @@ class DataMapperAbstract implements DataMapperInterface * The reference is stored in the main model * * @param string $propertyName Property name to initialize - * @param Object $obj Object to create + * @param mixed $obj Object to create * * @return mixed * @@ -788,7 +788,7 @@ class DataMapperAbstract implements DataMapperInterface */ private static function createBelongsTo(string $propertyName, $obj) { - if (is_object($obj)) { + if (\is_object($obj)) { /** @var string $mapper */ $mapper = static::$belongsTo[$propertyName]['mapper']; $primaryKey = $mapper::getObjectId($obj); @@ -817,7 +817,7 @@ class DataMapperAbstract implements DataMapperInterface */ private static function createBelongsToArray(string $propertyName, array $obj) { - if (is_array($obj)) { + if (\is_array($obj)) { /** @var string $mapper */ $mapper = static::$belongsTo[$propertyName]['mapper']; $primaryKey = $obj[static::$columns[static::$primaryField]['internal']]; @@ -890,12 +890,12 @@ class DataMapperAbstract implements DataMapperInterface } elseif ($type === 'DateTime') { return $value->format('Y-m-d H:i:s'); } elseif ($type === 'Json' || $type === 'jsonSerializable') { - return \json_encode($value); + return (string) \json_encode($value); } elseif ($type === 'Serializable') { return $value->serialize(); } elseif ($value instanceof \JsonSerializable) { - return \json_encode($value->jsonSerialize()); - } elseif (is_object($value) && \method_exists($value, 'getId')) { + return (string) \json_encode($value->jsonSerialize()); + } elseif (\is_object($value) && method_exists($value, 'getId')) { return $value->getId(); } @@ -905,9 +905,11 @@ class DataMapperAbstract implements DataMapperInterface /** * Update has many * - * @param \ReflectionClass $refClass Reflection class - * @param Object $obj Object to create - * @param mixed $objId Id to set + * @param \ReflectionClass $refClass Reflection class + * @param object $obj Object to create + * @param mixed $objId Id to set + * @param int $relations Create all relations as well + * @param int $depth Depth of relations to update (default = 1 = none) * * @return void * @@ -915,7 +917,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - private static function updateHasMany(\ReflectionClass $refClass, $obj, $objId) : void + private static function updateHasMany(\ReflectionClass $refClass, object $obj, $objId, int $relations = RelationType::ALL, $depth = 1) : void { $objsIds = []; @@ -942,7 +944,7 @@ class DataMapperAbstract implements DataMapperInterface $objsIds[$propertyName] = []; foreach ($values as $key => &$value) { - if (!is_object($value)) { + if (!\is_object($value)) { // Is scalar => already in database $objsIds[$propertyName][$key] = $value; @@ -957,7 +959,7 @@ class DataMapperAbstract implements DataMapperInterface // already in db if (!empty($primaryKey)) { - $mapper::update($value); + $mapper::update($value, $relations, $depth); $objsIds[$propertyName][$key] = $value; @@ -1055,24 +1057,22 @@ class DataMapperAbstract implements DataMapperInterface * The reference is stored in the main model * * @param string $propertyName Property name to initialize - * @param Object $obj Object to update + * @param object $obj Object to update + * @param int $relations Create all relations as well + * @param int $depth Depth of relations to update (default = 1 = none) * * @return mixed * * @since 1.0.0 */ - private static function updateOwnsOne(string $propertyName, $obj) + private static function updateOwnsOne(string $propertyName, object $obj, int $relations = RelationType::ALL, int $depth = 1) { - if (is_object($obj)) { - /** @var string $mapper */ - $mapper = static::$ownsOne[$propertyName]['mapper']; + /** @var string $mapper */ + $mapper = static::$ownsOne[$propertyName]['mapper']; - // todo: delete owned one object is not recommended since it can be owned by by something else? or does owns one mean that nothing else can have a relation to this one? + // todo: delete owned one object is not recommended since it can be owned by by something else? or does owns one mean that nothing else can have a relation to this one? - return $mapper::update($obj); - } - - return $obj; + return $mapper::update($obj, $relations, $depth); } /** @@ -1081,19 +1081,21 @@ class DataMapperAbstract implements DataMapperInterface * The reference is stored in the main model * * @param string $propertyName Property name to initialize - * @param Object $obj Object to update + * @param mixed $obj Object to update + * @param int $relations Create all relations as well + * @param int $depth Depth of relations to update (default = 1 = none) * * @return mixed * * @since 1.0.0 */ - private static function updateBelongsTo(string $propertyName, $obj) + private static function updateBelongsTo(string $propertyName, $obj, int $relations = RelationType::ALL, int $depth = 1) { - if (is_object($obj)) { + if (\is_object($obj)) { /** @var string $mapper */ $mapper = static::$belongsTo[$propertyName]['mapper']; - return $mapper::update($obj); + return $mapper::update($obj, $relations, $depth); } return $obj; @@ -1102,15 +1104,17 @@ class DataMapperAbstract implements DataMapperInterface /** * Update object in db. * - * @param Object $obj Model to update - * @param mixed $objId Model id - * @param \ReflectionClass $refClass Reflection class + * @param object $obj Model to update + * @param mixed $objId Model id + * @param \ReflectionClass $refClass Reflection class + * @param int $relations Create all relations as well + * @param int $depth Depth of relations to update (default = 1 = none) * * @return void * * @since 1.0.0 */ - private static function updateModel($obj, $objId, \ReflectionClass $refClass = null) : void + private static function updateModel(object $obj, $objId, \ReflectionClass $refClass = null, int $relations = RelationType::ALL, int $depth = 1) : void { $query = new Builder(self::$db); $query->prefix(self::$db->getPrefix()) @@ -1118,7 +1122,7 @@ class DataMapperAbstract implements DataMapperInterface ->where(static::$table . '.' . static::$primaryField, '=', $objId); foreach (static::$columns as $key => $column) { - $propertyName = \stripos($column['internal'], '/') !== false ? explode('/', $column['internal'])[0] : $column['internal']; + $propertyName = \stripos($column['internal'], '/') !== false ? \explode('/', $column['internal'])[0] : $column['internal']; if (isset(static::$hasMany[$propertyName]) || isset(static::$hasOne[$propertyName]) || $column['internal'] === static::$primaryField @@ -1126,6 +1130,7 @@ class DataMapperAbstract implements DataMapperInterface continue; } + $refClass = $refClass ?? new \ReflectionClass($obj); $property = $refClass->getProperty($propertyName); if (!($isPublic = $property->isPublic())) { @@ -1133,24 +1138,24 @@ class DataMapperAbstract implements DataMapperInterface } if (isset(static::$ownsOne[$propertyName])) { - $id = self::updateOwnsOne($propertyName, $property->getValue($obj)); + $id = self::updateOwnsOne($propertyName, $property->getValue($obj), $relations, $depth); $value = self::parseValue($column['type'], $id); // todo: should not be done if the id didn't change. but for now don't know if id changed $query->set([static::$table . '.' . $column['name'] => $value], $column['type']); } elseif (isset(static::$belongsTo[$propertyName])) { - $id = self::updateBelongsTo($propertyName, $property->getValue($obj)); + $id = self::updateBelongsTo($propertyName, $property->getValue($obj), $relations, $depth); $value = self::parseValue($column['type'], $id); // todo: should not be done if the id didn't change. but for now don't know if id changed $query->set([static::$table . '.' . $column['name'] => $value], $column['type']); } elseif ($column['name'] !== static::$primaryField) { $tValue = $property->getValue($obj); - if (stripos($column['internal'], '/') !== false) { + if (\stripos($column['internal'], '/') !== false) { $path = \explode('/', $column['internal']); - array_shift($path); - $path = implode('/', $path); + \array_shift($path); + $path = \implode('/', $path); $tValue = ArrayUtils::getArray($path, $tValue, '/'); } $value = self::parseValue($column['type'], $tValue); @@ -1171,12 +1176,13 @@ class DataMapperAbstract implements DataMapperInterface * * @param mixed $obj Object reference (gets filled with insert id) * @param int $relations Create all relations as well + * @param int $depth Depth of relations to update (default = 1 = none) * * @return mixed * * @since 1.0.0 */ - public static function update($obj, int $relations = RelationType::ALL) + public static function update($obj, int $relations = RelationType::ALL, int $depth = 1) { self::extend(__CLASS__); @@ -1188,8 +1194,11 @@ class DataMapperAbstract implements DataMapperInterface $objId = self::getObjectId($obj, $refClass); $update = true; - // todo: maybe don't remove obj and just update cache... ? since it might have to be loaded again - self::removeInitialized(static::class, $objId); + if ($depth < 1) { + return $objId; + } + + self::addInitialized(static::class, $objId, $obj); if (empty($objId)) { $update = false; @@ -1197,11 +1206,11 @@ class DataMapperAbstract implements DataMapperInterface } if ($relations === RelationType::ALL) { - self::updateHasMany($refClass, $obj, $objId); + self::updateHasMany($refClass, $obj, $objId, --$depth); } if ($update) { - self::updateModel($obj, $objId, $refClass); + self::updateModel($obj, $objId, $refClass, --$depth); } return $objId; @@ -1211,7 +1220,7 @@ class DataMapperAbstract implements DataMapperInterface * Delete has many * * @param \ReflectionClass $refClass Reflection class - * @param Object $obj Object to create + * @param object $obj Object to create * @param mixed $objId Id to set * @param int $relations Delete all relations as well * @@ -1221,7 +1230,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - private static function deleteHasMany(\ReflectionClass $refClass, $obj, $objId, int $relations) : void + private static function deleteHasMany(\ReflectionClass $refClass, object $obj, $objId, int $relations) : void { foreach (static::$hasMany as $propertyName => $rel) { $property = $refClass->getProperty($propertyName); @@ -1246,7 +1255,7 @@ class DataMapperAbstract implements DataMapperInterface $relReflectionClass = null; foreach ($values as $key => &$value) { - if (!is_object($value)) { + if (!\is_object($value)) { // Is scalar => already in database $objsIds[$key] = $value; @@ -1284,7 +1293,7 @@ class DataMapperAbstract implements DataMapperInterface * The reference is stored in the main model * * @param string $propertyName Property name to initialize - * @param Object $obj Object to delete + * @param mixed $obj Object to delete * * @return mixed * @@ -1292,7 +1301,7 @@ class DataMapperAbstract implements DataMapperInterface */ private static function deleteOwnsOne(string $propertyName, $obj) { - if (is_object($obj)) { + if (\is_object($obj)) { /** @var string $mapper */ $mapper = static::$ownsOne[$propertyName]['mapper']; @@ -1309,7 +1318,7 @@ class DataMapperAbstract implements DataMapperInterface * The reference is stored in the main model * * @param string $propertyName Property name to initialize - * @param Object $obj Object to delete + * @param mixed $obj Object to delete * * @return mixed * @@ -1317,7 +1326,7 @@ class DataMapperAbstract implements DataMapperInterface */ private static function deleteBelongsTo(string $propertyName, $obj) { - if (is_object($obj)) { + if (\is_object($obj)) { /** @var string $mapper */ $mapper = static::$belongsTo[$propertyName]['mapper']; @@ -1330,7 +1339,7 @@ class DataMapperAbstract implements DataMapperInterface /** * Delete object in db. * - * @param Object $obj Model to delete + * @param object $obj Model to delete * @param mixed $objId Model id * @param int $relations Delete all relations as well * @param \ReflectionClass $refClass Reflection class @@ -1339,7 +1348,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - private static function deleteModel($obj, $objId, int $relations = RelationType::REFERENCE, \ReflectionClass $refClass = null) : void + private static function deleteModel(object $obj, $objId, int $relations = RelationType::REFERENCE, \ReflectionClass $refClass = null) : void { $query = new Builder(self::$db); $query->prefix(self::$db->getPrefix()) @@ -1347,6 +1356,7 @@ class DataMapperAbstract implements DataMapperInterface ->from(static::$table) ->where(static::$table . '.' . static::$primaryField, '=', $objId); + $refClass = $refClass ?? new \ReflectionClass($obj); $properties = $refClass->getProperties(); if ($relations === RelationType::ALL) { @@ -1486,7 +1496,7 @@ class DataMapperAbstract implements DataMapperInterface $parts = \explode('\\', $class); $name = $parts[$c = (count($parts) - 1)]; $parts[$c] = 'Null' . $name; - $class = implode('\\', $parts); + $class = \implode('\\', $parts); } if (!isset($obj)) { @@ -1507,7 +1517,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function populateManyToMany(array $result, &$obj, int $depth = null) : void + public static function populateManyToMany(array $result, &$obj, int $depth = 3) : void { // todo: maybe pass reflectionClass as optional parameter for performance increase $refClass = new \ReflectionClass($obj); @@ -1523,7 +1533,7 @@ class DataMapperAbstract implements DataMapperInterface } $objects = $mapper::get($values, RelationType::ALL, null, $depth); - $refProp->setValue($obj, !is_array($objects) ? [$objects->getId() => $objects] : $objects); + $refProp->setValue($obj, !\is_array($objects) ? [$objects->getId() => $objects] : $objects); if (!$accessible) { $refProp->setAccessible(false); @@ -1543,7 +1553,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function populateManyToManyArray(array $result, array &$obj, int $depth = null) : void + public static function populateManyToManyArray(array $result, array &$obj, int $depth = 3) : void { foreach ($result as $member => $values) { if (!empty($values)) { @@ -1568,7 +1578,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function populateHasOne(&$obj, int $depth = null) : void + public static function populateHasOne(&$obj, int $depth = 3) : void { $refClass = new \ReflectionClass($obj); @@ -1589,7 +1599,7 @@ class DataMapperAbstract implements DataMapperInterface continue; } - $id = is_object($id) ? self::getObjectId($id) : $id; + $id = \is_object($id) ? self::getObjectId($id) : $id; $value = self::getInitialized($mapper, $id) ?? $mapper::get($id, RelationType::ALL, null, $depth); $refProp->setValue($obj, $value); @@ -1613,7 +1623,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function populateHasOneArray(array &$obj, int $depth = null) : void + public static function populateHasOneArray(array &$obj, int $depth = 3) : void { foreach (static::$hasOne as $member => $one) { /** @var string $mapper */ @@ -1634,7 +1644,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function populateOwnsOne(&$obj, int $depth = null) : void + public static function populateOwnsOne(&$obj, int $depth = 3) : void { $refClass = new \ReflectionClass($obj); @@ -1655,7 +1665,7 @@ class DataMapperAbstract implements DataMapperInterface continue; } - $id = is_object($id) ? self::getObjectId($id) : $id; + $id = \is_object($id) ? self::getObjectId($id) : $id; $value = self::getInitialized($mapper, $id) ?? $mapper::get($id, RelationType::ALL, null, $depth); $refProp->setValue($obj, $value); @@ -1679,7 +1689,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function populateOwnsOneArray(array &$obj, int $depth = null) : void + public static function populateOwnsOneArray(array &$obj, int $depth = 3) : void { foreach (static::$ownsOne as $member => $one) { /** @var string $mapper */ @@ -1700,7 +1710,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function populateBelongsTo(&$obj, int $depth = null) : void + public static function populateBelongsTo(&$obj, int $depth = 3) : void { $refClass = new \ReflectionClass($obj); @@ -1721,7 +1731,7 @@ class DataMapperAbstract implements DataMapperInterface continue; } - $id = is_object($id) ? self::getObjectId($id) : $id; + $id = \is_object($id) ? self::getObjectId($id) : $id; $value = self::getInitialized($mapper, $id) ?? $mapper::get($id, RelationType::ALL, null, $depth); $refProp->setValue($obj, $value); @@ -1745,7 +1755,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function populateBelongsToArray(array &$obj, int $depth = null) : void + public static function populateBelongsToArray(array &$obj, int $depth = 3) : void { foreach (static::$belongsTo as $member => $one) { /** @var string $mapper */ @@ -1775,9 +1785,11 @@ class DataMapperAbstract implements DataMapperInterface continue; } - $hasPath = false; + $hasPath = false; + $aValue = []; + $arrayPath = ''; - if (stripos(static::$columns[$column]['internal'], '/') !== false) { + if (\stripos(static::$columns[$column]['internal'], '/') !== false) { $hasPath = true; $path = \explode('/', static::$columns[$column]['internal']); $refProp = $refClass->getProperty($path[0]); @@ -1786,9 +1798,9 @@ class DataMapperAbstract implements DataMapperInterface $refProp->setAccessible(true); } - array_shift($path); - $path = implode('/', $path); - $aValue = $refProp->getValue($obj); + \array_shift($path); + $arrayPath = \implode('/', $path); + $aValue = $refProp->getValue($obj); } else { $refProp = $refClass->getProperty(static::$columns[$column]['internal']); @@ -1804,20 +1816,20 @@ class DataMapperAbstract implements DataMapperInterface } if ($hasPath) { - $value = ArrayUtils::setArray($path, $aValue, $value, '/', true); + $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); } $refProp->setValue($obj, $value); } elseif (static::$columns[$column]['type'] === 'DateTime') { $value = new \DateTime($value ?? ''); if ($hasPath) { - $value = ArrayUtils::setArray($path, $aValue, $value, '/', true); + $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); } $refProp->setValue($obj, $value); } elseif (static::$columns[$column]['type'] === 'Json') { if ($hasPath) { - $value = ArrayUtils::setArray($path, $aValue, $value, '/', true); + $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); } $refProp->setValue($obj, \json_decode($value, true)); @@ -1851,11 +1863,11 @@ class DataMapperAbstract implements DataMapperInterface foreach ($result as $column => $value) { if (isset(static::$columns[$column]['internal'])) { $path = static::$columns[$column]['internal']; - if (stripos($path, '/') !== false) { + if (\stripos($path, '/') !== false) { $path = \explode('/', $path); - array_shift($path); - $path = implode('/', $path); + \array_shift($path); + $path = \implode('/', $path); } if (\in_array(static::$columns[$column]['type'], ['string', 'int', 'float', 'bool'])) { @@ -1887,9 +1899,9 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function get($primaryKey, int $relations = RelationType::ALL, $fill = null, int $depth = null) + public static function get($primaryKey, int $relations = RelationType::ALL, $fill = null, int $depth = 3) { - if (isset($depth) && $depth < 1) { + if ($depth < 1) { return $primaryKey; } @@ -1925,7 +1937,7 @@ class DataMapperAbstract implements DataMapperInterface self::addInitialized(static::class, $value, $obj[$value]); } - self::fillRelations($obj, $relations, isset($depth) ? --$depth : null); + self::fillRelations($obj, $relations, --$depth); self::clear(); $countResulsts = count($obj); @@ -1953,7 +1965,7 @@ class DataMapperAbstract implements DataMapperInterface $parts = \explode('\\', $class); $name = $parts[$c = (count($parts) - 1)]; $parts[$c] = 'Null' . $name; - $class = implode('\\', $parts); + $class = \implode('\\', $parts); return new $class(); } @@ -1969,9 +1981,9 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function getArray($primaryKey, int $relations = RelationType::ALL, int $depth = null) : array + public static function getArray($primaryKey, int $relations = RelationType::ALL, int $depth = 3) : array { - if (isset($depth) && $depth < 1) { + if ($depth < 1) { return $primaryKey; } @@ -1995,7 +2007,7 @@ class DataMapperAbstract implements DataMapperInterface self::addInitialized(static::class, $value, $obj[$value]); } - self::fillRelationsArray($obj, $relations, isset($depth) ? --$depth : null); + self::fillRelationsArray($obj, $relations, --$depth); self::clear(); return count($obj) === 1 ? reset($obj) : $obj; @@ -2014,9 +2026,9 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function getFor($refKey, string $ref, int $relations = RelationType::ALL, $fill = null, int $depth = null) + public static function getFor($refKey, string $ref, int $relations = RelationType::ALL, $fill = null, int $depth = 3) { - if (isset($depth) && $depth < 1) { + if ($depth < 1) { return $refKey; } @@ -2038,7 +2050,7 @@ class DataMapperAbstract implements DataMapperInterface $toLoad = self::getPrimaryKeysBy($value, self::getColumnByMember($ref)); } - $obj[$value] = self::get($toLoad, $relations, $fill, isset($depth) ? --$depth : null); + $obj[$value] = self::get($toLoad, $relations, $fill, --$depth); } $countResulsts = count($obj); @@ -2101,10 +2113,10 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function getAll(int $relations = RelationType::ALL, int $depth = null, string $lang = '') : array + public static function getAll(int $relations = RelationType::ALL, int $depth = 3, string $lang = '') : array { - if (isset($depth) && $depth < 1) { - return null; + if ($depth < 1) { + return []; } if (!isset(self::$parentMapper)) { @@ -2112,7 +2124,7 @@ class DataMapperAbstract implements DataMapperInterface } $obj = self::populateIterable(self::getAllRaw($lang)); - self::fillRelations($obj, $relations, isset($depth) ? --$depth : null); + self::fillRelations($obj, $relations, --$depth); self::clear(); return $obj; @@ -2129,10 +2141,10 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function getAllArray(int $relations = RelationType::ALL, int $depth = null, string $lang = '') : array + public static function getAllArray(int $relations = RelationType::ALL, int $depth = 3, string $lang = '') : array { - if (isset($depth) && $depth < 1) { - return null; + if ($depth < 1) { + return []; } if (!isset(self::$parentMapper)) { @@ -2140,7 +2152,7 @@ class DataMapperAbstract implements DataMapperInterface } $obj = self::populateIterableArray(self::getAllRaw($lang)); - self::fillRelationsArray($obj, $relations, isset($depth) ? --$depth : null); + self::fillRelationsArray($obj, $relations, --$depth); self::clear(); return $obj; @@ -2160,7 +2172,13 @@ class DataMapperAbstract implements DataMapperInterface $sth = self::$db->con->prepare($query->toSql()); $sth->execute(); - return self::populateIterable($sth->fetchAll(\PDO::FETCH_ASSOC)); + $result = $sth->fetchAll(\PDO::FETCH_ASSOC); + + if ($result === false) { + return []; + } + + return self::populateIterable($result); } /** @@ -2174,14 +2192,14 @@ class DataMapperAbstract implements DataMapperInterface * @param int $depth Relation depth * @param string $lang Language * - * @return mixed + * @return array * * @since 1.0.0 */ - public static function getNewest(int $limit = 1, Builder $query = null, int $relations = RelationType::ALL, int $depth = null, string $lang = '') : array + public static function getNewest(int $limit = 1, Builder $query = null, int $relations = RelationType::ALL, int $depth = 3, string $lang = '') : array { - if (isset($depth) && $depth < 1) { - return null; + if ($depth < 1) { + return []; } self::extend(__CLASS__); @@ -2204,13 +2222,12 @@ class DataMapperAbstract implements DataMapperInterface $sth->execute(); $results = $sth->fetchAll(\PDO::FETCH_ASSOC); - $obj = self::populateIterable(is_bool($results) ? [] : $results); + $obj = self::populateIterable($results === false ? [] : $results); - self::fillRelations($obj, $relations, isset($depth) ? --$depth : null); + self::fillRelations($obj, $relations, --$depth); self::clear(); return $obj; - } /** @@ -2224,20 +2241,20 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function getAllByQuery(Builder $query, int $relations = RelationType::ALL, int $depth = null) : array + public static function getAllByQuery(Builder $query, int $relations = RelationType::ALL, int $depth = 3) : array { - if (isset($depth) && $depth < 1) { - return null; + if ($depth < 1) { + return []; } $sth = self::$db->con->prepare($query->toSql()); $sth->execute(); $results = $sth->fetchAll(\PDO::FETCH_ASSOC); - $results = is_bool($results) ? [] : $results; + $results = $results === false ? [] : $results; $obj = self::populateIterable($results); - self::fillRelations($obj, $relations, isset($depth) ? --$depth : null); + self::fillRelations($obj, $relations, --$depth); self::clear(); return $obj; @@ -2254,9 +2271,9 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function getRandom(int $amount = 1, int $relations = RelationType::ALL, int $depth = null) + public static function getRandom(int $amount = 1, int $relations = RelationType::ALL, int $depth = 3) { - if (isset($depth) && $depth < 1) { + if ($depth < 1) { return null; } @@ -2275,7 +2292,7 @@ class DataMapperAbstract implements DataMapperInterface /** * Fill object with relations * - * @param mixed $obj Objects to fill + * @param array $obj Objects to fill * @param int $relations Relations type * @param int $depth Relation depth * @@ -2283,9 +2300,9 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function fillRelations(array &$obj, int $relations = RelationType::ALL, int $depth = null) : void + public static function fillRelations(array &$obj, int $relations = RelationType::ALL, int $depth = 3) : void { - if (isset($depth) && $depth < 1) { + if ($depth < 1) { return; } @@ -2325,7 +2342,7 @@ class DataMapperAbstract implements DataMapperInterface /** * Fill object with relations * - * @param mixed $obj Objects to fill + * @param array $obj Objects to fill * @param int $relations Relations type * @param int $depth Relation depth * @@ -2333,9 +2350,9 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - public static function fillRelationsArray(array &$obj, int $relations = RelationType::ALL, int $depth = null) : void + public static function fillRelationsArray(array &$obj, int $relations = RelationType::ALL, int $depth = 3) : void { - if (isset($depth) && $depth < 1) { + if ($depth < 1) { return; } @@ -2377,7 +2394,7 @@ class DataMapperAbstract implements DataMapperInterface * * @param mixed $primaryKey Key * - * @return mixed + * @return array * * @since 1.0.0 */ @@ -2391,7 +2408,7 @@ class DataMapperAbstract implements DataMapperInterface $results = $sth->fetch(\PDO::FETCH_ASSOC); - return is_bool($results) ? [] : $results; + return $results === false ? [] : $results; } /** @@ -2415,9 +2432,12 @@ class DataMapperAbstract implements DataMapperInterface $sth = self::$db->con->prepare($query->toSql()); $sth->execute(); - $results = array_column($sth->fetchAll(\PDO::FETCH_NUM) ?? [], 0); + $result = $sth->fetchAll(\PDO::FETCH_NUM); + if ($result === false) { + return []; + } - return $results; + return \array_column($result, 0); } /** @@ -2441,9 +2461,12 @@ class DataMapperAbstract implements DataMapperInterface $sth = self::$db->con->prepare($query->toSql()); $sth->execute(); - $results = array_column($sth->fetchAll(\PDO::FETCH_NUM) ?? [], 0); + $result = $sth->fetchAll(\PDO::FETCH_NUM); + if ($result === false) { + return []; + } - return $results; + return \array_column($result, 0); } /** @@ -2468,7 +2491,7 @@ class DataMapperAbstract implements DataMapperInterface $results = $sth->fetchAll(\PDO::FETCH_ASSOC); - return is_bool($results) ? [] : $results; + return $results === false ? [] : $results; } /** @@ -2510,7 +2533,7 @@ class DataMapperAbstract implements DataMapperInterface ->leftOuterJoin($value['table']) ->on(new And('1', new And(new Or('d1', 'd2'), 'id'))) ->where($value['table'] . '.' . $value['dst'], '=', 'NULL'); - + }*/ $sth = self::$db->con->prepare($query->toSql()); @@ -2608,7 +2631,7 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - private static function addInitialized(string $mapper, $id, $obj = null) : void + private static function addInitialized(string $mapper, $id, object $obj = null) : void { if (!isset(self::$initObjects[$mapper])) { self::$initObjects[$mapper] = []; @@ -2686,7 +2709,7 @@ class DataMapperAbstract implements DataMapperInterface $results = $sth->fetchAll(\PDO::FETCH_ASSOC); - return count($results) === 0; + return $results && count($results) === 0; } /** @@ -2714,7 +2737,7 @@ class DataMapperAbstract implements DataMapperInterface /** * Test if object is null object * - * @param object $obj Object to check + * @param mixed $obj Object to check * * @return bool * @@ -2722,6 +2745,6 @@ class DataMapperAbstract implements DataMapperInterface */ private static function isNullObject($obj) : bool { - return is_object($obj) && strpos(get_class($obj), '\Null') !== false; + return \is_object($obj) && \strpos(\get_class($obj), '\Null') !== false; } } diff --git a/DataStorage/Database/DatabaseType.php b/DataStorage/Database/DatabaseType.php index 2999a3ace..8fa1a2491 100644 --- a/DataStorage/Database/DatabaseType.php +++ b/DataStorage/Database/DatabaseType.php @@ -30,7 +30,7 @@ abstract class DatabaseType extends Enum { public const MYSQL = 'mysql'; /* MySQL */ public const SQLITE = 'sqlite'; /* SQLITE */ - public const PGSQL = 2; /* PostgreSQL */ + public const PGSQL = 'postgresql'; /* PostgreSQL */ public const ORACLE = 3; /* Oracle */ public const SQLSRV = 'mssql'; /* Microsoft SQL Server */ } diff --git a/DataStorage/Database/GrammarAbstract.php b/DataStorage/Database/GrammarAbstract.php index ecd7c91c0..7b85dfcd9 100644 --- a/DataStorage/Database/GrammarAbstract.php +++ b/DataStorage/Database/GrammarAbstract.php @@ -95,7 +95,7 @@ abstract class GrammarAbstract { return trim( implode(' ', - array_filter( + \array_filter( $this->compileComponents($query), function ($value) { return (string) $value !== ''; @@ -169,13 +169,13 @@ abstract class GrammarAbstract $expression = ''; foreach ($elements as $key => $element) { - if (is_string($element) && $element !== '*') { - if (strpos($element, '.') === false) { + if (\is_string($element) && $element !== '*') { + if (\strpos($element, '.') === false) { $prefix = ''; } $expression .= $this->compileSystem($element, $prefix) . ', '; - } elseif (is_string($element) && $element === '*') { + } elseif (\is_string($element) && $element === '*') { $expression .= '*, '; } elseif ($element instanceof \Closure) { $expression .= $element() . ', '; @@ -204,9 +204,9 @@ abstract class GrammarAbstract $expression = ''; foreach ($elements as $key => $element) { - if (is_string($element) && $element !== '*') { + if (\is_string($element) && $element !== '*') { $expression .= $this->compileSystem($element, $prefix) . ', '; - } elseif (is_string($element) && $element === '*') { + } elseif (\is_string($element) && $element === '*') { $expression .= '*, '; } elseif ($element instanceof \Closure) { $expression .= $element() . ', '; @@ -225,20 +225,20 @@ abstract class GrammarAbstract * * A system is a table, a sub query or special keyword. * - * @param array|string $system System - * @param string $prefix Prefix for table + * @param string $system System + * @param string $prefix Prefix for table * * @return string * * @since 1.0.0 */ - protected function compileSystem($system, string $prefix = '') : string + protected function compileSystem(string $system, string $prefix = '') : string { // todo: this is a bad way to handle select count(*) which doesn't need a prefix. Maybe remove prefixes in total? $identifier = $this->systemIdentifier; foreach ($this->specialKeywords as $keyword) { - if (strrpos($system, $keyword, -strlen($system)) !== false) { + if (\strrpos($system, $keyword, -\strlen($system)) !== false) { $prefix = ''; $identifier = ''; } diff --git a/DataStorage/Database/Query/Builder.php b/DataStorage/Database/Query/Builder.php index 404e75e57..291f91b3f 100644 --- a/DataStorage/Database/Query/Builder.php +++ b/DataStorage/Database/Query/Builder.php @@ -38,7 +38,7 @@ final class Builder extends BuilderAbstract /** * Columns. * - * @var array> + * @var array * @since 1.0.0 */ public $selects = []; @@ -46,7 +46,7 @@ final class Builder extends BuilderAbstract /** * Columns. * - * @var array> + * @var array * @since 1.0.0 */ public $updates = []; @@ -293,7 +293,7 @@ final class Builder extends BuilderAbstract /** * Bind parameter. * - * @param string|array|\Closure $binds Binds + * @param mixed $binds Binds * * @return Builder * @@ -336,18 +336,6 @@ final class Builder extends BuilderAbstract return $this->grammar->compileQuery($this); } - /** - * Parsing to prepared string. - * - * @return string - * - * @since 1.0.0 - */ - public function toPrepared() : string - { - return $this->grammar->compilePreparedQuery($this); - } - /** * Set raw query. * @@ -435,7 +423,7 @@ final class Builder extends BuilderAbstract /** * From. * - * @param string|array ...$tables Tables + * @param array ...$tables Tables * * @return Builder * @@ -486,7 +474,7 @@ final class Builder extends BuilderAbstract */ public function where($columns, $operator = null, $values = null, $boolean = 'and') : Builder { - if ($operator !== null && !\is_array($operator) && !\in_array(strtolower($operator), self::OPERATORS)) { + if ($operator !== null && !\is_array($operator) && !\in_array(\strtolower($operator), self::OPERATORS)) { throw new \InvalidArgumentException('Unknown operator.'); } @@ -499,7 +487,7 @@ final class Builder extends BuilderAbstract $i = 0; foreach ($columns as $key => $column) { - if (isset($operator[$i]) && !\in_array(strtolower($operator[$i]), self::OPERATORS)) { + if (isset($operator[$i]) && !\in_array(\strtolower($operator[$i]), self::OPERATORS)) { throw new \InvalidArgumentException('Unknown operator.'); } @@ -1214,9 +1202,9 @@ final class Builder extends BuilderAbstract */ public static function getBindParamType($value) { - if (is_int($value)) { + if (\is_int($value)) { return \PDO::PARAM_INT; - } elseif (\is_string($value) || is_float($value)) { + } elseif (\is_string($value) || \is_float($value)) { return \PDO::PARAM_STR; } @@ -1239,7 +1227,7 @@ final class Builder extends BuilderAbstract if (\is_string($column)) { return $column; } elseif ($column instanceof Column) { - return $column->getPublicName(); + return $column->getColumn(); } elseif ($column instanceof \Closure) { return $column(); } elseif ($column instanceof \Serializable) { diff --git a/DataStorage/Database/Query/Grammar/Grammar.php b/DataStorage/Database/Query/Grammar/Grammar.php index ed8e1f3f7..2552e7684 100644 --- a/DataStorage/Database/Query/Grammar/Grammar.php +++ b/DataStorage/Database/Query/Grammar/Grammar.php @@ -284,7 +284,12 @@ class Grammar extends GrammarAbstract $expression = ' ' . \strtoupper($element['boolean']) . ' '; } - if (is_string($element['column'])) { + if (\is_string($element['column'])) { + // handle bug when no table is specified in the where column + if (count($query->from) === 1 && \stripos($element['column'], '.') === false) { + $element['column'] = $query->from[0] . '.' . $element['column']; + } + $expression .= $this->compileSystem($element['column'], $query->getPrefix()); } elseif ($element['column'] instanceof \Closure) { $expression .= $element['column'](); @@ -327,15 +332,15 @@ class Grammar extends GrammarAbstract */ protected function compileValue(Builder $query, $value, string $prefix = '') : string { - if (is_string($value)) { - if (strpos($value, ':') === 0) { + if (\is_string($value)) { + if (\strpos($value, ':') === 0) { return $value; } return $query->quote($value); - } elseif (is_int($value)) { + } elseif (\is_int($value)) { return (string) $value; - } elseif (is_array($value)) { + } elseif (\is_array($value)) { $values = ''; foreach ($value as $val) { @@ -347,9 +352,9 @@ class Grammar extends GrammarAbstract return $query->quote($value->format('Y-m-d H:i:s')); } elseif ($value === null) { return 'NULL'; - } elseif (is_bool($value)) { + } elseif (\is_bool($value)) { return (string) ((int) $value); - } elseif (is_float($value)) { + } elseif (\is_float($value)) { return (string) $value; } elseif ($value instanceof Column) { return $this->compileSystem($value->getColumn(), $prefix); diff --git a/DataStorage/Database/Query/Grammar/SQLiteGrammar.php b/DataStorage/Database/Query/Grammar/SQLiteGrammar.php index d77483f85..056a8d6c5 100644 --- a/DataStorage/Database/Query/Grammar/SQLiteGrammar.php +++ b/DataStorage/Database/Query/Grammar/SQLiteGrammar.php @@ -24,7 +24,7 @@ use phpOMS\DataStorage\Database\Query\Builder; * @link http://website.orange-management.de * @since 1.0.0 */ -class SqliteGrammar extends Grammar +class SQLiteGrammar extends Grammar { /** diff --git a/DataStorage/Database/Query/QueryType.php b/DataStorage/Database/Query/QueryType.php index ff3a77dfd..3d1727cb5 100644 --- a/DataStorage/Database/Query/QueryType.php +++ b/DataStorage/Database/Query/QueryType.php @@ -32,5 +32,5 @@ abstract class QueryType extends Enum public const DELETE = 3; public const RANDOM = 4; public const RAW = 5; - public const EMPTY = 6; + public const NONE = 6; } diff --git a/DataStorage/Database/Schema/Exception/TableException.php b/DataStorage/Database/Schema/Exception/TableException.php index ecd54500e..98f82d800 100644 --- a/DataStorage/Database/Schema/Exception/TableException.php +++ b/DataStorage/Database/Schema/Exception/TableException.php @@ -49,18 +49,20 @@ class TableException extends \PDOException */ public static function findTable(string $message) : string { - $pos1 = strpos($message, '\''); + $pos1 = \strpos($message, '\''); if ($pos1 === false) { return $message; } - $pos2 = strpos($message, '\'', $pos1 + 1); + $pos2 = \strpos($message, '\'', $pos1 + 1); if ($pos2 === false) { return $message; } - return substr($message, $pos1 + 1, $pos2 - $pos1 - 1); + $table = \substr($message, $pos1 + 1, $pos2 - $pos1 - 1); + + return $table === false ? $message : $table; } } diff --git a/DataStorage/Database/Schema/Grammar/MysqlGrammar.php b/DataStorage/Database/Schema/Grammar/MysqlGrammar.php index ea86d288e..43e2e9270 100644 --- a/DataStorage/Database/Schema/Grammar/MysqlGrammar.php +++ b/DataStorage/Database/Schema/Grammar/MysqlGrammar.php @@ -67,7 +67,7 @@ class MysqlGrammar extends Grammar */ protected function compileFrom(Builder $query, array $table) : string { - $expression = $this->expressionizeTableColumn('information_schema.tables'); + $expression = $this->expressionizeTableColumn(['information_schema.tables']); return 'FROM ' . $expression; } diff --git a/DataStorage/Database/Schema/Grammar/PostgresGrammar.php b/DataStorage/Database/Schema/Grammar/PostgresGrammar.php index 51ae131e8..6dfe5424f 100644 --- a/DataStorage/Database/Schema/Grammar/PostgresGrammar.php +++ b/DataStorage/Database/Schema/Grammar/PostgresGrammar.php @@ -14,7 +14,7 @@ declare(strict_types=1); namespace phpOMS\DataStorage\Database\Schema\Grammar; -class PostgresGrammar +class PostgresGrammar extends Grammar { } diff --git a/DataStorage/Database/Schema/Grammar/SQLiteGrammar.php b/DataStorage/Database/Schema/Grammar/SQLiteGrammar.php index da4a7dd52..448fe9a91 100644 --- a/DataStorage/Database/Schema/Grammar/SQLiteGrammar.php +++ b/DataStorage/Database/Schema/Grammar/SQLiteGrammar.php @@ -14,7 +14,7 @@ declare(strict_types=1); namespace phpOMS\DataStorage\Database\Schema\Grammar; -class SQLiteGrammar +class SQLiteGrammar extends Grammar { } diff --git a/DataStorage/Session/HttpSession.php b/DataStorage/Session/HttpSession.php index 2d66dc3a2..443fb5e38 100644 --- a/DataStorage/Session/HttpSession.php +++ b/DataStorage/Session/HttpSession.php @@ -50,7 +50,7 @@ class HttpSession implements SessionInterface /** * Session ID. * - * @var string|int + * @var string|int|null * @since 1.0.0 */ private $sid = null; @@ -85,7 +85,7 @@ class HttpSession implements SessionInterface } if (!\is_bool($sid)) { - \session_id($sid); + \session_id((string) $sid); } $this->inactivityInterval = $inactivityInterval; diff --git a/DataStorage/Session/SessionInterface.php b/DataStorage/Session/SessionInterface.php index 98d3b68a5..2e8e6eb77 100644 --- a/DataStorage/Session/SessionInterface.php +++ b/DataStorage/Session/SessionInterface.php @@ -74,14 +74,14 @@ interface SessionInterface public function save() : void; /** - * @return int|string + * @return int|string|null * * @since 1.0.0 */ public function getSID(); /** - * @param int|string $sid Session id + * @param int|string|null $sid Session id * * @return void * diff --git a/Dispatcher/Dispatcher.php b/Dispatcher/Dispatcher.php index b8ffed67c..04d5285b4 100644 --- a/Dispatcher/Dispatcher.php +++ b/Dispatcher/Dispatcher.php @@ -63,7 +63,7 @@ final class Dispatcher * Dispatch controller. * * @param string|array|\Closure $controller Controller string - * @param array|null ...$data Data + * @param array|null|mixed ...$data Data * * @return array * @@ -111,7 +111,12 @@ final class Dispatcher if (($c = count($dispatch)) === 3) { /* Handling static functions */ - $function = $dispatch[0] . '::' . $dispatch[2]; + $function = $dispatch[0] . '::' . $dispatch[2]; + + if (!\is_callable($function)) { + throw new \Exception(); + } + $views[$controller] = $function(...$data); } elseif ($c === 2) { $this->getController($dispatch[0]); @@ -135,9 +140,14 @@ final class Dispatcher */ private function dispatchArray(array $controller, array $data = null) : array { + $views = []; foreach ($controller as $controllerSingle) { - $views += $this->dispatch($controllerSingle, ...$data); + if ($data === null) { + $views += $this->dispatch($controllerSingle); + } else { + $views += $this->dispatch($controllerSingle, ...$data); + } } return $views; diff --git a/Event/EventManager.php b/Event/EventManager.php index d2aab8c11..84d02ec8a 100644 --- a/Event/EventManager.php +++ b/Event/EventManager.php @@ -162,7 +162,10 @@ final class EventManager */ public function detach(string $group) : bool { - return $this->detachCallback($group) || $this->detachGroup($group); + $result1 = $this->detachCallback($group); + $result2 = $this->detachGroup($group); + + return $result1 || $result2; } /** diff --git a/Localization/Defaults/en_US.json b/Localization/Defaults/Definitions/en_US.json similarity index 96% rename from Localization/Defaults/en_US.json rename to Localization/Defaults/Definitions/en_US.json index 6ba33326d..77af7f449 100644 --- a/Localization/Defaults/en_US.json +++ b/Localization/Defaults/Definitions/en_US.json @@ -1,6 +1,6 @@ { - "language": "EN", - "country": "USA", + "language": "en", + "country": "US", "currency": { "code": "USD", "position": 0 diff --git a/Localization/Defaults/Iban.php b/Localization/Defaults/Iban.php index b84c55c07..c008a6535 100644 --- a/Localization/Defaults/Iban.php +++ b/Localization/Defaults/Iban.php @@ -43,7 +43,7 @@ final class Iban /** * Iban chars. * - * @var string + * @var int * @since 1.0.0 */ private $chars = 2; diff --git a/Localization/L11nManager.php b/Localization/L11nManager.php index 28759f624..0f9d6541d 100644 --- a/Localization/L11nManager.php +++ b/Localization/L11nManager.php @@ -106,7 +106,7 @@ final class L11nManager public function loadLanguageFromFile(string $language, string $from, string $file) : void { $lang = []; - if (file_exists($file)) { + if (\file_exists($file)) { /** @noinspection PhpIncludeInspection */ $lang = include $file; } @@ -158,7 +158,7 @@ final class L11nManager if (!isset($this->language[$code][$module][$translation])) { return 'ERROR'; } - } catch (\Exception $e) { + } catch (\Throwable $e) { FileLogger::getInstance()->warning(FileLogger::MSG_FULL, [ 'message' => 'Undefined translation for \'' . $code . '/' . $module . '/' . $translation . '\'.', ]); @@ -184,6 +184,6 @@ final class L11nManager */ public function getHtml(string $code, string $module, string $theme, $translation) : string { - return htmlspecialchars($this->getText($code, $module, $theme, $translation)); + return \htmlspecialchars($this->getText($code, $module, $theme, $translation)); } } diff --git a/Localization/Localization.php b/Localization/Localization.php index 9be968ddc..7dc372e46 100644 --- a/Localization/Localization.php +++ b/Localization/Localization.php @@ -17,6 +17,7 @@ namespace phpOMS\Localization; use phpOMS\Stdlib\Base\Exception\InvalidEnumValue; use phpOMS\Utils\Converter\AngleType; use phpOMS\Utils\Converter\TemperatureType; +use phpOMS\System\File\Local\Directory; /** * Localization class. @@ -91,10 +92,10 @@ final class Localization /** * Time format. * - * @var string + * @var array * @since 1.0.0 */ - private $datetime = 'Y-m-d H:i:s'; + private $datetime = []; /** * Weight. @@ -137,12 +138,68 @@ final class Localization private $volume = []; /** - * Constructor. + * Load localization from language code + * + * @param string $langCode Language code + * + * @return void * * @since 1.0.0 */ - public function __construct() + public function loadFromLanguage(string $langCode) : void { + $langCode = \strtolower($langCode); + + if (!ISO639x1Enum::isValidValue($langCode)) { + throw new InvalidEnumValue($langCode); + } + + $files = Directory::list(__DIR__ . '/../Localization/Defaults/Definitions'); + + foreach ($files as $file) { + if (\stripos($file, $langCode) === 0) { + $this->importLocale( + json_decode( + \file_get_contents(__DIR__ . '/../Localization/Defaults/Definitions/' . $file), + true + ) + ); + return; + } + } + + $this->importLocale( + json_decode( + \file_get_contents(__DIR__ . '/../Localization/Defaults/Definitions/en_US.json'), + true + ) + ); + } + + /** + * Load localization from locale + * + * @param array $locale Locale data + * + * @return void + * + * @since 1.0.0 + */ + public function importLocale(array $locale) : void + { + $this->setLanguage($locale['language'] ?? 'en'); + $this->setCountry($locale['country'] ?? 'US'); + $this->setCurrency($locale['currency']['code'] ?? ISO4217Enum::_USD); + $this->setThousands($locale['thousand'] ?? ','); + $this->setDecimal($locale['decimal'] ?? '.'); + $this->setAngle($locale['angle'] ?? AngleType::DEGREE); + $this->setTemperature($locale['temperature'] ?? emperatureType::CELSIUS); + $this->setWeight($locale['weight'] ?? []); + $this->setSpeed($locale['speed'] ?? []); + $this->setLength($locale['length'] ?? []); + $this->setArea($locale['area'] ?? []); + $this->setVolume($locale['volume'] ?? []); + $this->setDatetime($locale['datetime'] ?? []); } /** @@ -264,7 +321,7 @@ final class Localization */ public function setCurrency(string $currency) : void { - if (!ISO4217Enum::isValidValue($currency)) { + if (!ISO4217CharEnum::isValidValue($currency)) { throw new InvalidEnumValue($currency); } @@ -274,11 +331,11 @@ final class Localization /** * get datetime format * - * @return string + * @return array * * @since 1.0.0 */ - public function getDatetime() : string + public function getDatetime() : array { return $this->datetime; } @@ -286,13 +343,13 @@ final class Localization /** * Set datetime format * - * @param string $datetime Datetime format + * @param array $datetime Datetime format * * @return void * * @since 1.0.0 */ - public function setDatetime(string $datetime) : void + public function setDatetime(array $datetime) : void { $this->datetime = $datetime; } diff --git a/Localization/Money.php b/Localization/Money.php index ff658ce5c..3301677cd 100644 --- a/Localization/Money.php +++ b/Localization/Money.php @@ -107,6 +107,11 @@ final class Money implements \Serializable public static function toInt(string $value, string $thousands = ',', string $decimal = '.') : int { $split = \explode($decimal, $value); + + if ($split === false) { + throw new \Exception(); + } + $left = $split[0]; $left = \str_replace($thousands, '', $left); $right = ''; @@ -116,6 +121,9 @@ final class Money implements \Serializable } $right = \substr($right, 0, self::MAX_DECIMALS); + if ($right === false) { + throw new \Exception(); + } return ((int) $left) * 10 ** self::MAX_DECIMALS + (int) \str_pad($right, self::MAX_DECIMALS, '0'); } @@ -187,8 +195,11 @@ final class Money implements \Serializable $left = \substr($value, 0, -self::MAX_DECIMALS); $right = \substr($value, -self::MAX_DECIMALS); + if ($left === false || $right === false) { + throw new \Exception(); + } - return ($decimals > 0) ? number_format((float) $left, 0, $this->decimal, $this->thousands) . $this->decimal . \substr($right, 0, $decimals) : (string) $left; + return ($decimals > 0) ? number_format((float) $left, 0, $this->decimal, $this->thousands) . $this->decimal . \substr($right, 0, $decimals) : $left; } /** @@ -309,7 +320,7 @@ final class Money implements \Serializable public function pow($value) : Money { if (is_float($value) || is_int($value)) { - $this->value = $this->value ** $value; + $this->value = (int) ($this->value ** $value); } return $this; diff --git a/Log/FileLogger.php b/Log/FileLogger.php index c7b37c44d..33e4fcac4 100644 --- a/Log/FileLogger.php +++ b/Log/FileLogger.php @@ -98,11 +98,11 @@ final class FileLogger implements LoggerInterface */ public function __construct(string $lpath, bool $verbose = false) { - $path = realpath($lpath); + $path = \realpath($lpath); $this->verbose = $verbose; - if (is_dir($lpath) || strpos($lpath, '.') === false) { - $path = $lpath . '/' . date('Y-m-d') . '.log'; + if (\is_dir($lpath) || \strpos($lpath, '.') === false) { + $path = $lpath . '/' . \date('Y-m-d') . '.log'; } else { $path = $lpath; } @@ -154,8 +154,8 @@ final class FileLogger implements LoggerInterface */ public function __destruct() { - if (is_resource($this->fp)) { - fclose($this->fp); + if (\is_resource($this->fp)) { + \fclose($this->fp); } } @@ -186,8 +186,8 @@ final class FileLogger implements LoggerInterface return false; } - $mtime = \explode(' ', microtime()); - $mtime = $mtime[1] + $mtime[0]; + $temp = \explode(' ', \microtime()); + $mtime = ((float) $temp[1]) + ((float) $temp[0]); self::$timings[$id] = ['start' => $mtime]; @@ -205,8 +205,8 @@ final class FileLogger implements LoggerInterface */ public static function endTimeLog($id = '') : float { - $mtime = \explode(' ', microtime()); - $mtime = $mtime[1] + $mtime[0]; + $temp = \explode(' ', \microtime()); + $mtime = ((float) $temp[1]) + ((float) $temp[0]); self::$timings[$id]['end'] = $mtime; self::$timings[$id]['time'] = $mtime - self::$timings[$id]['start']; @@ -232,7 +232,7 @@ final class FileLogger implements LoggerInterface $replace['{' . $key . '}'] = $val; } - $backtrace = debug_backtrace(); + $backtrace = \debug_backtrace(); // Removing sensitive config data from logging foreach ($backtrace as $key => $value) { @@ -244,15 +244,15 @@ final class FileLogger implements LoggerInterface $backtrace = \json_encode($backtrace); $replace['{backtrace}'] = $backtrace; - $replace['{datetime}'] = sprintf('%--19s', (new \DateTime('NOW'))->format('Y-m-d H:i:s')); - $replace['{level}'] = sprintf('%--12s', $level); + $replace['{datetime}'] = \sprintf('%--19s', (new \DateTime('NOW'))->format('Y-m-d H:i:s')); + $replace['{level}'] = \sprintf('%--12s', $level); $replace['{path}'] = $_SERVER['REQUEST_URI'] ?? 'REQUEST_URI'; - $replace['{ip}'] = sprintf('%--15s', $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'); - $replace['{version}'] = sprintf('%--15s', PHP_VERSION); - $replace['{os}'] = sprintf('%--15s', PHP_OS); - $replace['{line}'] = sprintf('%--15s', $context['line'] ?? '?'); + $replace['{ip}'] = \sprintf('%--15s', $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'); + $replace['{version}'] = \sprintf('%--15s', PHP_VERSION); + $replace['{os}'] = \sprintf('%--15s', PHP_OS); + $replace['{line}'] = \sprintf('%--15s', $context['line'] ?? '?'); - return strtr($message, $replace); + return \strtr($message, $replace); } /** @@ -266,24 +266,24 @@ final class FileLogger implements LoggerInterface */ private function write(string $message) : void { + if ($this->verbose) { + echo $message, "\n"; + } + $this->createFile(); - if (!is_writable($this->path)) { + if (!\is_writable($this->path)) { return; } - $this->fp = fopen($this->path, 'a'); + $this->fp = \fopen($this->path, 'a'); - if (flock($this->fp, LOCK_EX) && $this->fp !== false) { - fwrite($this->fp, $message . "\n"); - fflush($this->fp); - flock($this->fp, LOCK_UN); - fclose($this->fp); + if ($this->fp !== false && \flock($this->fp, LOCK_EX)) { + \fwrite($this->fp, $message . "\n"); + \fflush($this->fp); + \flock($this->fp, LOCK_UN); + \fclose($this->fp); $this->fp = false; } - - if ($this->verbose) { - echo $message, "\n"; - } } /** @@ -386,21 +386,32 @@ final class FileLogger implements LoggerInterface return $levels; } - $this->fp = fopen($this->path, 'r'); - fseek($this->fp, 0); + $this->fp = \fopen($this->path, 'r'); - while (($line = fgetcsv($this->fp, 0, ';')) !== false) { - $line[1] = trim($line[1]); + if ($this->fp === false) { + return $levels; + } + + \fseek($this->fp, 0); + $line = \fgetcsv($this->fp, 0, ';'); + + while ($line !== false && $line !== null) { + if (count($line) < 2) { + continue; + } + + $line[1] = \trim($line[1]); if (!isset($levels[$line[1]])) { $levels[$line[1]] = 0; } $levels[$line[1]]++; + $line = \fgetcsv($this->fp, 0, ';'); } - fseek($this->fp, 0, SEEK_END); - fclose($this->fp); + \fseek($this->fp, 0, SEEK_END); + \fclose($this->fp); return $levels; } @@ -422,24 +433,35 @@ final class FileLogger implements LoggerInterface return $connection; } - $this->fp = fopen($this->path, 'r'); - fseek($this->fp, 0); + $this->fp = \fopen($this->path, 'r'); - while (($line = fgetcsv($this->fp, 0, ';')) !== false) { - $line[2] = trim($line[2]); + if ($this->fp === false) { + return $connection; + } + + \fseek($this->fp, 0); + $line = \fgetcsv($this->fp, 0, ';'); + + while ($line !== false && $line !== null) { + if (count($line) < 3) { + continue; + } + + $line[2] = \trim($line[2]); if (!isset($connection[$line[2]])) { $connection[$line[2]] = 0; } $connection[$line[2]]++; + $line = \fgetcsv($this->fp, 0, ';'); } - fseek($this->fp, 0, SEEK_END); - fclose($this->fp); - asort($connection); + \fseek($this->fp, 0, SEEK_END); + \fclose($this->fp); + \asort($connection); - return array_slice($connection, 0, $limit); + return \array_slice($connection, 0, $limit); } /** @@ -461,10 +483,16 @@ final class FileLogger implements LoggerInterface return $logs; } - $this->fp = fopen($this->path, 'r'); - fseek($this->fp, 0); + $this->fp = \fopen($this->path, 'r'); - while (($line = fgetcsv($this->fp, 0, ';')) !== false) { + if ($this->fp === false) { + return $logs; + } + + \fseek($this->fp, 0); + + $line = \fgetcsv($this->fp, 0, ';'); + while ($line !== false && $line !== null) { $id++; if ($offset > 0) { @@ -478,16 +506,17 @@ final class FileLogger implements LoggerInterface } foreach ($line as &$value) { - $value = trim($value); + $value = \trim($value); } $logs[$id] = $line; $limit--; ksort($logs); + $line = \fgetcsv($this->fp, 0, ';'); } - fseek($this->fp, 0, SEEK_END); - fclose($this->fp); + \fseek($this->fp, 0, SEEK_END); + \fclose($this->fp); return $logs; } @@ -510,31 +539,36 @@ final class FileLogger implements LoggerInterface return $log; } - $this->fp = fopen($this->path, 'r'); - fseek($this->fp, 0); + $this->fp = \fopen($this->path, 'r'); - while (($line = fgetcsv($this->fp, 0, ';')) !== false && $current <= $id) { + if ($this->fp === false) { + return $log; + } + + \fseek($this->fp, 0); + + while (($line = \fgetcsv($this->fp, 0, ';')) !== false && $current <= $id) { $current++; if ($current < $id) { continue; } - $log['datetime'] = trim($line[0] ?? ''); - $log['level'] = trim($line[1] ?? ''); - $log['ip'] = trim($line[2] ?? ''); - $log['line'] = trim($line[3] ?? ''); - $log['version'] = trim($line[4] ?? ''); - $log['os'] = trim($line[5] ?? ''); - $log['path'] = trim($line[6] ?? ''); - $log['message'] = trim($line[7] ?? ''); - $log['file'] = trim($line[8] ?? ''); - $log['backtrace'] = trim($line[9] ?? ''); + $log['datetime'] = \trim($line[0] ?? ''); + $log['level'] = \trim($line[1] ?? ''); + $log['ip'] = \trim($line[2] ?? ''); + $log['line'] = \trim($line[3] ?? ''); + $log['version'] = \trim($line[4] ?? ''); + $log['os'] = \trim($line[5] ?? ''); + $log['path'] = \trim($line[6] ?? ''); + $log['message'] = \trim($line[7] ?? ''); + $log['file'] = \trim($line[8] ?? ''); + $log['backtrace'] = \trim($line[9] ?? ''); break; } - fseek($this->fp, 0, SEEK_END); - fclose($this->fp); + \fseek($this->fp, 0, SEEK_END); + \fclose($this->fp); return $log; } @@ -553,11 +587,11 @@ final class FileLogger implements LoggerInterface public function console(string $message, bool $verbose = true, array $context = []) : void { if (empty($context)) { - $message = date('[Y-m-d H:i:s] ') . $message . "\r\n"; + $message = \date('[Y-m-d H:i:s] ') . $message . "\r\n"; } if ($verbose) { - echo $message; + echo $this->interpolate($message, $context); } else { $this->info($message, $context); } diff --git a/Log/LoggerInterface.php b/Log/LoggerInterface.php index dde03237b..c16454d3a 100644 --- a/Log/LoggerInterface.php +++ b/Log/LoggerInterface.php @@ -28,8 +28,8 @@ interface LoggerInterface /** * System is unusable. * - * @param string $message Logging message schema - * @param array $context Context to log + * @param string $message Logging message schema + * @param array $context Context to log * * @return void */ @@ -41,8 +41,8 @@ interface LoggerInterface * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * - * @param string $message Logging message schema - * @param array $context Context to log + * @param string $message Logging message schema + * @param array $context Context to log * * @return void */ @@ -53,8 +53,8 @@ interface LoggerInterface * * Example: Application component unavailable, unexpected exception. * - * @param string $message Logging message schema - * @param array $context Context to log + * @param string $message Logging message schema + * @param array $context Context to log * * @return void */ @@ -64,8 +64,8 @@ interface LoggerInterface * Runtime errors that do not require immediate action but should typically * be logged and monitored. * - * @param string $message Logging message schema - * @param array $context Context to log + * @param string $message Logging message schema + * @param array $context Context to log * * @return void */ @@ -77,8 +77,8 @@ interface LoggerInterface * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * - * @param string $message Logging message schema - * @param array $context Context to log + * @param string $message Logging message schema + * @param array $context Context to log * * @return void */ @@ -87,8 +87,8 @@ interface LoggerInterface /** * Normal but significant events. * - * @param string $message Logging message schema - * @param array $context Context to log + * @param string $message Logging message schema + * @param array $context Context to log * * @return void */ @@ -99,8 +99,8 @@ interface LoggerInterface * * Example: User logs in, SQL logs. * - * @param string $message Logging message schema - * @param array $context Context to log + * @param string $message Logging message schema + * @param array $context Context to log * * @return void */ @@ -109,8 +109,8 @@ interface LoggerInterface /** * Detailed debug information. * - * @param string $message Logging message schema - * @param array $context Context to log + * @param string $message Logging message schema + * @param array $context Context to log * * @return void */ @@ -119,9 +119,9 @@ interface LoggerInterface /** * Logs with an arbitrary level. * - * @param string $level Log level/severeness - * @param string $message Logging message schema - * @param array $context Context to log + * @param string $level Log level/severeness + * @param string $message Logging message schema + * @param array $context Context to log * * @return void */ diff --git a/Math/Exception/ZeroDevisionException.php b/Math/Exception/ZeroDevisionException.php index ed3ba5417..538d463ea 100644 --- a/Math/Exception/ZeroDevisionException.php +++ b/Math/Exception/ZeroDevisionException.php @@ -27,13 +27,12 @@ final class ZeroDevisionException extends \UnexpectedValueException /** * Constructor. * - * @param string $message Exception message * @param int $code Exception code * @param \Exception $previous Previous exception * * @since 1.0.0 */ - public function __construct(string $message = '', int $code = 0, \Exception $previous = null) + public function __construct(int $code = 0, \Exception $previous = null) { parent::__construct('Devision by zero is not defined.', $code, $previous); } diff --git a/Math/Functions/Functions.php b/Math/Functions/Functions.php index b82794bdc..36d1702b1 100644 --- a/Math/Functions/Functions.php +++ b/Math/Functions/Functions.php @@ -315,6 +315,6 @@ final class Functions */ public static function getRelativeDegree(int $value, int $length, int $start = 0) : int { - return (int) abs(self::mod($value - $start, $length)); + return abs(self::mod($value - $start, $length)); } } diff --git a/Math/Geometry/Shape/D2/Polygon.php b/Math/Geometry/Shape/D2/Polygon.php index ca67290c9..f718507c3 100644 --- a/Math/Geometry/Shape/D2/Polygon.php +++ b/Math/Geometry/Shape/D2/Polygon.php @@ -192,7 +192,7 @@ final class Polygon implements D2ShapeInterface */ public function getSurface() : float { - return (float) abs($this->getSignedSurface()); + return abs($this->getSignedSurface()); } /** diff --git a/Math/Geometry/Shape/D3/Sphere.php b/Math/Geometry/Shape/D3/Sphere.php index e440f3db6..64debdbb1 100644 --- a/Math/Geometry/Shape/D3/Sphere.php +++ b/Math/Geometry/Shape/D3/Sphere.php @@ -116,7 +116,7 @@ final class Sphere implements D3ShapeInterface */ public static function getRadiusByVolume(float $v) : float { - return (float) pow($v * 3 / (4 * pi()), 1 / 3); + return pow($v * 3 / (4 * pi()), 1 / 3); } /** diff --git a/Math/Matrix/Matrix.php b/Math/Matrix/Matrix.php index 2f41933ad..135de8fba 100644 --- a/Math/Matrix/Matrix.php +++ b/Math/Matrix/Matrix.php @@ -402,7 +402,7 @@ class Matrix implements \ArrayAccess, \Iterator { if ($value instanceof Matrix) { return $this->add($value->mult(-1)); - } elseif (is_numeric($value)) { + } elseif (!is_string($value) && is_numeric($value)) { return $this->add(-$value); } @@ -424,7 +424,7 @@ class Matrix implements \ArrayAccess, \Iterator { if ($value instanceof Matrix) { return $this->addMatrix($value); - } elseif (is_numeric($value)) { + } elseif (!is_string($value) && is_numeric($value)) { return $this->addScalar($value); } @@ -529,7 +529,7 @@ class Matrix implements \ArrayAccess, \Iterator { if ($value instanceof Matrix) { return $this->multMatrix($value); - } elseif (is_numeric($value)) { + } elseif (!is_string($value) && is_numeric($value)) { return $this->multScalar($value); } diff --git a/Math/Number/Complex.php b/Math/Number/Complex.php index 139646ba9..c4a8c74c5 100644 --- a/Math/Number/Complex.php +++ b/Math/Number/Complex.php @@ -159,7 +159,7 @@ final class Complex throw new \InvalidArgumentException(); } - public function powComplex() : Complex + public function powComplex(Complex $value) : Complex { } @@ -184,7 +184,7 @@ final class Complex return $this->multComplex($this->powInteger(--$value)); } - public function powScalar() : Complex + public function powScalar($value) : Complex { } diff --git a/Math/Number/Integer.php b/Math/Number/Integer.php index 0f503e187..91a7f6586 100644 --- a/Math/Number/Integer.php +++ b/Math/Number/Integer.php @@ -138,7 +138,7 @@ final class Integer } } - return (int) $m; + return $m; } /** @@ -160,13 +160,13 @@ final class Integer } $a = (int) ceil(sqrt($value)); - $b2 = (int) ($a * $a - $value); + $b2 = ($a * $a - $value); $i = 1; while (!Numbers::isSquare($b2) && $i < $limit) { $i++; $a += 1; - $b2 = (int) ($a * $a - $value); + $b2 = ($a * $a - $value); } return [(int) round($a - sqrt($b2)), (int) round($a + sqrt($b2))]; diff --git a/Math/Number/Numbers.php b/Math/Number/Numbers.php index 454634d1b..0eb76b044 100644 --- a/Math/Number/Numbers.php +++ b/Math/Number/Numbers.php @@ -69,10 +69,14 @@ final class Numbers public static function isSelfdescribing(int $n) : bool { $n = (string) $n; - $split = str_split($n); + $split = \str_split($n); + + if ($split === false) { + return false; + } foreach ($split as $place => $value) { - if (substr_count($n, (string) $place) != $value) { + if (\substr_count($n, (string) $place) != $value) { return false; } } diff --git a/Math/Parser/Evaluator.php b/Math/Parser/Evaluator.php index 2aab74418..f26f0819d 100644 --- a/Math/Parser/Evaluator.php +++ b/Math/Parser/Evaluator.php @@ -15,7 +15,7 @@ declare(strict_types=1); namespace phpOMS\Math\Parser; /** - * Shape interface. + * Basic math function evaluation. * * @package Framework * @license OMS License 1.0 @@ -27,10 +27,7 @@ class Evaluator /** * Evaluate function. * - * Example: ('2*x^3-4x', ['x' => 99]) - * - * @param string $formula Formula to differentiate - * @param array $vars Variables to evaluate + * @param string $equation Formula to evaluate * * @return float * @@ -38,18 +35,118 @@ class Evaluator * * @since 1.0.0 */ - public static function evaluate(string $formula, array $vars) : float + public static function evaluate(string $equation) : ?float { - // todo: do i need array_values here? - $formula = \str_replace(array_keys($vars), array_values($vars), $formula); - - // todo: this is horrible in case things like mod etc. need to be supported - if (\preg_match('#[^0-9\+\-\*\/\(\)]#', $formula)) { - throw new \Exception('Bad elements'); + if (\preg_match('#[^0-9\+\-\*\/\(\)\ \^\.]#', $equation)) { + return null; } - // todo: create parser + $stack = []; + $postfix = self::shuntingYard($equation); - return 0; + foreach ($postfix as $i => $value) { + if (\is_numeric($value)) { + $stack[] = $value; + } else { + $a = self::parseValue(\array_pop($stack)); + $b = self::parseValue(\array_pop($stack)); + + if ($value === '+') { + $stack[] = $a + $b; + } elseif ($value === '-') { + $stack[] = $b - $a; + } elseif ($value === '*') { + $stack[] = $a * $b; + } elseif ($value === '/') { + $stack[] = $b / $a; + } elseif ($value === '^') { + $stack[] = $b ** $a; + } + } + } + + $result = \array_pop($stack); + + return \is_numeric($result) ? (float) $result : null; + } + + /** + * Parse value. + * + * @param mixed $value Value to parse + * + * @return mixed + * + * @since 1.0.0 + */ + private static function parseValue($value) + { + return !\is_string($value) ? $value : (\stripos($value, '.') === false ? (int) $value : (float) $value); + } + + /** + * Shunting Yard algorithm. + * + * @param string $equation Equation to convert + * + * @return array + * + * @since 1.0.0 + */ + private static function shuntingYard(string $equation) : array + { + $stack = []; + $operators = [ + '^' => ['precedence' => 4, 'order' => 1], + '*' => ['precedence' => 3, 'order' => -1], + '/' => ['precedence' => 3, 'order' => -1], + '+' => ['precedence' => 2, 'order' => -1], + '-' => ['precedence' => 2, 'order' => -1], + ]; + $output = []; + + $equation = \str_replace(' ', '', $equation); + $equation = \preg_split('/([\+\-\*\/\^\(\)])/', $equation, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + + if ($equation === false) { + return []; + } + + $equation = \array_filter($equation, function($n) { + return $n !== ''; + }); + + foreach ($equation as $i => $token) { + if (\is_numeric($token)) { + $output[] = $token; + } elseif (\strpbrk($token, '^*/+-') !== false) { + $o1 = $token; + $o2 = end($stack); + + while ($o2 !== false && \strpbrk($o2, '^*/+-') !== false + && (($operators[$o1]['order'] === -1 && $operators[$o1]['precedence'] <= $operators[$o2]['precedence']) + || ($operators[$o1]['order'] === 1 && $operators[$o1]['precedence'] < $operators[$o2]['precedence'])) + ) { + $output[] = \array_pop($stack); + $o2 = end($stack); + } + + $stack[] = $o1; + } elseif ($token === '(') { + $stack[] = $token; + } elseif ($token === ')') { + while (end($stack) !== '(') { + $output[] = \array_pop($stack); + } + + \array_pop($stack); + } + } + + while (count($stack) > 0) { + $output[] = \array_pop($stack); + } + + return $output; } } diff --git a/Math/Statistic/Average.php b/Math/Statistic/Average.php index 48d7ed4fc..8f52a7233 100644 --- a/Math/Statistic/Average.php +++ b/Math/Statistic/Average.php @@ -168,7 +168,7 @@ final class Average * * @return float * - * @throws phpOMS\Math\Exception\ZeroDevisionException + * @throws ZeroDevisionException * * @since 1.0.0 */ @@ -252,7 +252,7 @@ final class Average throw new ZeroDevisionException(); } - return (float) pow(array_product($values), 1 / $count); + return pow(\array_product($values), 1 / $count); } /** diff --git a/Math/Stochastic/Distribution/BinomialDistribution.php b/Math/Stochastic/Distribution/BinomialDistribution.php index ef62eb044..82a49127c 100644 --- a/Math/Stochastic/Distribution/BinomialDistribution.php +++ b/Math/Stochastic/Distribution/BinomialDistribution.php @@ -57,7 +57,7 @@ class BinomialDistribution */ public static function getMgf(int $n, float $t, float $p) : float { - return (float) pow(1 - $p + $p * exp($t), $n); + return pow(1 - $p + $p * exp($t), $n); } /** diff --git a/Math/Stochastic/Distribution/ChiSquaredDistribution.php b/Math/Stochastic/Distribution/ChiSquaredDistribution.php index 80d472bcc..18547f119 100644 --- a/Math/Stochastic/Distribution/ChiSquaredDistribution.php +++ b/Math/Stochastic/Distribution/ChiSquaredDistribution.php @@ -31,7 +31,7 @@ class ChiSquaredDistribution /** * Chi square table. * - * @var array + * @var array> * @since 1.0.0 */ public const TABLE = [ @@ -106,7 +106,7 @@ class ChiSquaredDistribution $df = self::getDegreesOfFreedom($dataset); } - if (!defined(self::TABLE[$df])) { + if (!defined('self::TABLE') || !array_key_exists($df, self::TABLE)) { throw new \Exception('Degrees of freedom not supported'); } @@ -117,7 +117,8 @@ class ChiSquaredDistribution } } - $p = 1 - ($p ?? key(end(self::TABLE[$df]))); + $key = key(end(self::TABLE[$df])); + $p = 1 - ($p ?? ($key === false ? 1 : (float) $key)); return ['P' => $p, 'H0' => ($p > $significance), 'df' => $df]; } @@ -235,7 +236,7 @@ class ChiSquaredDistribution throw new \Exception('Out of bounds'); } - return (float) pow(1 - 2 * $t, -$df / 2); + return pow(1 - 2 * $t, -$df / 2); } /** diff --git a/Math/Stochastic/Distribution/ExponentialDistribution.php b/Math/Stochastic/Distribution/ExponentialDistribution.php index d53ed6303..43be19014 100644 --- a/Math/Stochastic/Distribution/ExponentialDistribution.php +++ b/Math/Stochastic/Distribution/ExponentialDistribution.php @@ -105,7 +105,7 @@ class ExponentialDistribution */ public static function getVariance(float $lambda) : float { - return (float) pow($lambda, -2); + return pow($lambda, -2); } /** diff --git a/Math/Stochastic/Distribution/GeometricDistribution.php b/Math/Stochastic/Distribution/GeometricDistribution.php index 84ec3bf70..fdbca94ad 100644 --- a/Math/Stochastic/Distribution/GeometricDistribution.php +++ b/Math/Stochastic/Distribution/GeometricDistribution.php @@ -36,7 +36,7 @@ class GeometricDistribution */ public static function getPmf(float $p, int $k) : float { - return (float) pow(1 - $p, $k - 1) * $p; + return pow(1 - $p, $k - 1) * $p; } /** diff --git a/Math/Stochastic/Distribution/PoissonDistribution.php b/Math/Stochastic/Distribution/PoissonDistribution.php index 438982080..94d6d9088 100644 --- a/Math/Stochastic/Distribution/PoissonDistribution.php +++ b/Math/Stochastic/Distribution/PoissonDistribution.php @@ -147,7 +147,7 @@ class PoissonDistribution */ public static function getSkewness(float $lambda) : float { - return (float) pow($lambda, -1 / 2); + return pow($lambda, -1 / 2); } /** @@ -161,7 +161,7 @@ class PoissonDistribution */ public static function getFisherInformation(float $lambda) : float { - return (float) pow($lambda, -1); + return pow($lambda, -1); } /** @@ -175,7 +175,7 @@ class PoissonDistribution */ public static function getExKurtosis(float $lambda) : float { - return (float) pow($lambda, -1); + return pow($lambda, -1); } public static function getRandom() diff --git a/Message/Console/Header.php b/Message/Console/Header.php index e69de29bb..7b7d56c01 100644 --- a/Message/Console/Header.php +++ b/Message/Console/Header.php @@ -0,0 +1,270 @@ +set('Content-Type', 'text/html; charset=utf-8'); + parent::__construct(); + } + + /** + * Set header. + * + * @param string $key Header key (case insensitive) + * @param string $header Header value + * @param bool $overwrite Overwrite if already existing + * + * @return bool + * + * @since 1.0.0 + */ + public function set(string $key, string $header, bool $overwrite = false) : bool + { + if (self::$isLocked) { + return false; + } + + if (self::isSecurityHeader($key) && isset($this->header[$key])) { + return false; + } + + $key = strtolower($key); + + if (!$overwrite && isset($this->header[$key])) { + return false; + } + + unset($this->header[$key]); + + if (!isset($this->header[$key])) { + $this->header[$key] = []; + } + + $this->header[$key][] = $header; + + return true; + } + + /** + * Is security header. + * + * @param string $key Header key + * + * @return bool + * + * @since 1.0.0 + */ + public static function isSecurityHeader(string $key) : bool + { + $key = strtolower($key); + + return $key === 'content-security-policy' + || $key === 'x-xss-protection' + || $key === 'x-content-type-options' + || $key === 'x-frame-options'; + } + + /** + * {@inheritdoc} + */ + public function getProtocolVersion() : string + { + return self::VERSION; + } + + /** + * Get status code. + * + * @return int + * + * @since 1.0.0 + */ + public function getStatusCode() : int + { + if ($this->status === 0) { + $this->status = (int) \http_response_code(); + } + + return parent::getStatusCode(); + } + + /** + * Get all headers for apache and nginx + * + * @return array + * + * @since 1.0.0 + */ + public static function getAllHeaders() : array + { + if (function_exists('getallheaders')) { + // @codeCoverageIgnoreStart + return getallheaders(); + // @codeCoverageIgnoreEnd + } + + $headers = []; + foreach ($_SERVER as $name => $value) { + $part = \substr($name, 5); + if ($part === 'HTTP_') { + $headers[\str_replace(' ', '-', \ucwords(\strtolower(\str_replace('_', ' ', $part))))] = $value; + } + } + + return $headers; + } + + /** + * Remove header by ID. + * + * @param mixed $key Header key + * + * @return bool + * + * @since 1.0.0 + */ + public function remove($key) : bool + { + if (self::$isLocked) { + return false; + } + + if (isset($this->header[$key])) { + unset($this->header[$key]); + + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getReasonPhrase() : string + { + $phrases = $this->get('Status'); + + return empty($phrases) ? '' : $phrases[0]; + } + + /** + * Get header by name. + * + * @param string $key Header key + * + * @return array + * + * @since 1.0.0 + */ + public function get(string $key) : array + { + return $this->header[strtolower($key)] ?? []; + } + + /** + * Check if header is defined. + * + * @param string $key Header key + * + * @return bool + * + * @since 1.0.0 + */ + public function has(string $key) : bool + { + return isset($this->header[$key]); + } + + /** + * Push all headers. + * + * @return void + * + * @since 1.0.0 + * @codeCoverageIgnore + */ + public function push() : void + { + if (self::$isLocked) { + throw new \Exception('Already locked'); + } + + foreach ($this->header as $name => $arr) { + foreach ($arr as $ele => $value) { + header($name . ': ' . $value); + } + } + } + + /** + * {@inheritdoc} + */ + public function generate(int $code) : void + { + switch ($code) { + default: + $this->generate500(); + } + } + + /** + * Generate predefined header. + * + * @return void + * + * @since 1.0.0 + */ + private function generate500() : void + { + } +} diff --git a/Message/Console/Request.php b/Message/Console/Request.php index a2d074f91..3ac251ae6 100644 --- a/Message/Console/Request.php +++ b/Message/Console/Request.php @@ -17,7 +17,9 @@ namespace phpOMS\Message\Console; use phpOMS\Localization\Localization; use phpOMS\Message\RequestAbstract; use phpOMS\Message\RequestSource; +use phpOMS\Message\Http\RequestMethod; use phpOMS\Router\RouteVerb; +use phpOMS\Uri\UriInterface; /** * Request class. @@ -50,12 +52,39 @@ final class Request extends RequestAbstract public function __construct(UriInterface $uri, Localization $l11n = null) { $this->header = new Header(); + $this->header->setL11n($l11n ?? new Localization()); - if ($l11n === null) { - $l11n = $l11n ?? new Localization(); - } + $this->uri = $uri; + $this->init(); + } - $this->header->setL11n($l11n); + /** + * Init request. + * + * This is used in order to either initialize the current http request or a batch of GET requests + * + * @return void + * + * @since 1.0.0 + */ + private function init() : void + { + $lang = \explode('_', $_SERVER['LANG'] ?? ''); + $this->header->getL11n()->setLanguage($lang[0] ?? 'en'); + + $this->cleanupGlobals(); + } + + /** + * Clean up globals that musn't be used any longer + * + * @return void + * + * @since 1.0.0 + */ + private function cleanupGlobals() : void + { + unset($_SERVER); } /** @@ -80,7 +109,7 @@ final class Request extends RequestAbstract $paths[] = $pathArray[$i]; } - $this->hash[] = sha1(implode('', $paths)); + $this->hash[] = sha1(\implode('', $paths)); } } @@ -115,7 +144,11 @@ final class Request extends RequestAbstract public function getMethod() : string { if ($this->method === null) { - $this->method = RequestMethod::GET; + $temp = $this->uri->__toString(); + $found = \stripos($temp, ':'); + $method = $found !== false && $found > 3 && $found < 8 ? \substr($temp, 0, $found) : RequestMethod::GET; + + $this->method = $method === false ? RequestMethod::GET : $method; } return $this->method; diff --git a/Message/Console/Response.php b/Message/Console/Response.php index e69de29bb..10effadb6 100644 --- a/Message/Console/Response.php +++ b/Message/Console/Response.php @@ -0,0 +1,174 @@ +header = new Header(); + $this->header->setL11n($l11n ?? new Localization()); + } + + /** + * Set response. + * + * @param array $response Response to set + * + * @return void + * + * @since 1.0.0 + */ + public function setResponse(array $response) : void + { + $this->response = $response; + } + + /** + * Remove response by ID. + * + * @param mixed $id Response ID + * + * @return bool + * + * @since 1.0.0 + */ + public function remove($id) : bool + { + if (isset($this->response[$id])) { + unset($this->response[$id]); + + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getBody() : string + { + return $this->render(); + } + + /** + * Generate response based on header. + * + * @return string + * + * @since 1.0.0 + */ + public function render() : string + { + $types = $this->header->get('Content-Type'); + + foreach ($types as $type) { + if (\stripos($type, MimeType::M_JSON) !== false) { + return (string) \json_encode($this->jsonSerialize()); + } + } + + return $this->getRaw(); + } + + /** + * Generate raw response. + * + * @return string + * + * @throws \Exception + * + * @since 1.0.0 + */ + private function getRaw() : string + { + $render = ''; + + foreach ($this->response as $key => $response) { + if ($response instanceOf \Serializable) { + $render .= $response->serialize(); + } elseif (\is_string($response) || \is_numeric($response)) { + $render .= $response; + } else { + throw new \Exception('Wrong response type'); + } + } + + return $render; + } + + /** + * {@inheritdoc} + */ + public function toArray() : array + { + $result = []; + + try { + foreach ($this->response as $key => $response) { + if ($response instanceof View) { + $result += $response->toArray(); + } elseif (\is_array($response)) { + $result += $response; + } elseif (\is_scalar($response)) { + $result[] = $response; + } elseif ($response instanceof \JsonSerializable) { + $result[] = $response->jsonSerialize(); + } elseif ($response === null) { + continue; + } else { + throw new \Exception('Wrong response type'); + } + } + } catch (\Exception $e) { + // todo: handle exception + // need to to try catch for logging. otherwise the json_encode in the logger will have a problem with this + $result = []; + } finally { + return $result; + } + } +} diff --git a/Message/Http/Header.php b/Message/Http/Header.php index 4922931b6..69ea4ae11 100644 --- a/Message/Http/Header.php +++ b/Message/Http/Header.php @@ -70,7 +70,7 @@ final class Header extends HeaderAbstract return false; } - $key = strtolower($key); + $key = \strtolower($key); if (!$overwrite && isset($this->header[$key])) { return false; @@ -98,7 +98,7 @@ final class Header extends HeaderAbstract */ public static function isSecurityHeader(string $key) : bool { - $key = strtolower($key); + $key = \strtolower($key); return $key === 'content-security-policy' || $key === 'x-xss-protection' @@ -139,16 +139,17 @@ final class Header extends HeaderAbstract */ public static function getAllHeaders() : array { - if (function_exists('getallheaders')) { + if (\function_exists('getallheaders')) { // @codeCoverageIgnoreStart - return getallheaders(); + return \getallheaders(); // @codeCoverageIgnoreEnd } $headers = []; foreach ($_SERVER as $name => $value) { - if (substr($name, 0, 5) == 'HTTP_') { - $headers[\str_replace(' ', '-', ucwords(strtolower(\str_replace('_', ' ', substr($name, 5)))))] = $value; + $part = \substr($name, 5); + if ($part === 'HTTP_') { + $headers[\str_replace(' ', '-', \ucwords(\strtolower(\str_replace('_', ' ', $part))))] = $value; } } @@ -200,7 +201,7 @@ final class Header extends HeaderAbstract */ public function get(string $key) : array { - return $this->header[strtolower($key)] ?? []; + return $this->header[\strtolower($key)] ?? []; } /** diff --git a/Message/Http/Request.php b/Message/Http/Request.php index 834db20b8..61114fd37 100644 --- a/Message/Http/Request.php +++ b/Message/Http/Request.php @@ -105,7 +105,7 @@ final class Request extends RequestAbstract $this->setupUriBuilder(); } - $this->data = array_change_key_case($this->data, CASE_LOWER); + $this->data = \array_change_key_case($this->data, CASE_LOWER); } /** @@ -140,13 +140,16 @@ final class Request extends RequestAbstract { if (isset($_SERVER['CONTENT_TYPE'])) { if (\stripos($_SERVER['CONTENT_TYPE'], 'application/json') !== false) { - if (($json = \json_decode(($input = \file_get_contents('php://input')), true)) === false || $json === null) { + $input = \file_get_contents('php://input'); + $json = \json_decode($input === false ? '' : $input, true); + if ($input === false || $json === false || $json === null) { throw new \Exception('Is not valid json ' . $input); } $this->data += $json; } elseif (\stripos($_SERVER['CONTENT_TYPE'], 'application/x-www-form-urlencoded') !== false) { - parse_str(file_get_contents('php://input'), $temp); + $content = \file_get_contents('php://input'); + \parse_str($content === false ? '' : $content, $temp); $this->data += $temp; } } @@ -246,7 +249,7 @@ final class Request extends RequestAbstract $paths[] = $pathArray[$i]; } - $this->hash[] = sha1(implode('', $paths)); + $this->hash[] = sha1(\implode('', $paths)); } } @@ -292,10 +295,10 @@ final class Request extends RequestAbstract { if ($this->browser === null) { $arr = BrowserType::getConstants(); - $httpUserAgent = strtolower($_SERVER['HTTP_USER_AGENT']); + $httpUserAgent = \strtolower($_SERVER['HTTP_USER_AGENT']); foreach ($arr as $key => $val) { - if (stripos($httpUserAgent, $val)) { + if (\stripos($httpUserAgent, $val)) { $this->browser = $val; return $this->browser; @@ -333,10 +336,10 @@ final class Request extends RequestAbstract { if ($this->os === null) { $arr = OSType::getConstants(); - $httpUserAgent = strtolower($_SERVER['HTTP_USER_AGENT']); + $httpUserAgent = \strtolower($_SERVER['HTTP_USER_AGENT']); foreach ($arr as $key => $val) { - if (stripos($httpUserAgent, $val)) { + if (\stripos($httpUserAgent, $val)) { $this->os = $val; return $this->os; @@ -397,7 +400,8 @@ final class Request extends RequestAbstract */ public function getBody() : string { - return \file_get_contents('php://input'); + $body = \file_get_contents('php://input'); + return $body === false ? '' : $body; } /** @@ -462,7 +466,7 @@ final class Request extends RequestAbstract { if ($this->getMethod() === RequestMethod::GET && !empty($this->data)) { return $this->uri->__toString() - . (parse_url($this->uri->__toString(), PHP_URL_QUERY) ? '&' : '?') + . (\parse_url($this->uri->__toString(), PHP_URL_QUERY) ? '&' : '?') . http_build_query($this->data); } diff --git a/Message/Http/Response.php b/Message/Http/Response.php index 1a8b30f3a..c536881d7 100644 --- a/Message/Http/Response.php +++ b/Message/Http/Response.php @@ -106,7 +106,7 @@ final class Response extends ResponseAbstract implements RenderableInterface foreach ($types as $type) { if (\stripos($type, MimeType::M_JSON) !== false) { - return \json_encode($this->jsonSerialize()); + return (string) \json_encode($this->jsonSerialize()); } } @@ -131,6 +131,8 @@ final class Response extends ResponseAbstract implements RenderableInterface $render .= $response->serialize(); } elseif (\is_string($response) || \is_numeric($response)) { $render .= $response; + } elseif (\is_array($response)) { + $render .= \json_encode($response); } else { throw new \Exception('Wrong response type'); } @@ -141,7 +143,7 @@ final class Response extends ResponseAbstract implements RenderableInterface /** * Remove whitespace and line break from render - * + * * @param string $render Rendered string * * @return string @@ -152,7 +154,7 @@ final class Response extends ResponseAbstract implements RenderableInterface { $types = $this->header->get('Content-Type'); if (\stripos($types[0], MimeType::M_HTML) !== false) { - return \trim(\preg_replace('/(\s{2,}|\n|\t)(?![^<>]*<\/pre>)/', ' ', $render)); + return \trim(\preg_replace('/(?s).*?<\/pre>(*SKIP)(*F)|(\s{2,}|\n|\t)/', ' ', $render)); } return $render; diff --git a/Message/Http/Rest.php b/Message/Http/Rest.php index 3bc67adea..6cde97c18 100644 --- a/Message/Http/Rest.php +++ b/Message/Http/Rest.php @@ -36,35 +36,47 @@ final class Rest */ public static function request(Request $request) : string { - $curl = curl_init(); + $curl = \curl_init(); + + if ($curl === false) { + throw new \Exception(); + } + + curl_setopt($curl, CURLOPT_NOBODY, true); + curl_setopt($curl, CURLOPT_HEADER, false); switch ($request->getMethod()) { + case RequestMethod::GET: + curl_setopt($curl, CURLOPT_HTTPGET, true); + break; case RequestMethod::PUT: - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT'); + \curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT'); break; case RequestMethod::DELETE: - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE'); + \curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE'); break; } if ($request->getMethod() !== RequestMethod::GET) { - curl_setopt($curl, CURLOPT_POST, 1); + \curl_setopt($curl, CURLOPT_POST, 1); if ($request->getData() !== null) { - curl_setopt($curl, CURLOPT_POSTFIELDS, $request->getData()); + \curl_setopt($curl, CURLOPT_POSTFIELDS, $request->getData()); } } - curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - curl_setopt($curl, CURLOPT_USERPWD, 'username:password'); + if ($request->getUri()->getUser() !== '') { + \curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + \curl_setopt($curl, CURLOPT_USERPWD, $request->getUri()->getUserInfo()); + } - curl_setopt($curl, CURLOPT_URL, $request->__toString()); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + \curl_setopt($curl, CURLOPT_URL, $request->__toString()); + \curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - $result = curl_exec($curl); + $result = \curl_exec($curl); - curl_close($curl); + \curl_close($curl); - return $result; + return \is_bool($result) ? '' : $result; } } diff --git a/Message/Mail/Mail.php b/Message/Mail/Mail.php index c9618ba4f..513ea0bab 100644 --- a/Message/Mail/Mail.php +++ b/Message/Mail/Mail.php @@ -123,7 +123,7 @@ class Mail /** * Word wrap. * - * @var string + * @var int * @since 1.0.0 */ protected $wordWrap = 78; diff --git a/Module/InfoManager.php b/Module/InfoManager.php index 1518f2cb2..197f0c479 100644 --- a/Module/InfoManager.php +++ b/Module/InfoManager.php @@ -85,7 +85,9 @@ final class InfoManager throw new PathException($this->path); } - $this->info = \json_decode(file_get_contents($this->path), true); + $contents = \file_get_contents($this->path); + $info = \json_decode($contents === false ? '[]' : $contents, true); + $this->info = $info === false ? [] : $info; } /** diff --git a/Module/InstallerAbstract.php b/Module/InstallerAbstract.php index 4a347780e..b7a8a2418 100644 --- a/Module/InstallerAbstract.php +++ b/Module/InstallerAbstract.php @@ -18,6 +18,7 @@ use phpOMS\DataStorage\Database\DatabaseType; use phpOMS\DataStorage\Database\Exception\InvalidDatabaseTypeException; use phpOMS\DataStorage\Database\DatabasePool; use phpOMS\System\File\Local\Directory; +use phpOMS\System\File\Local\File; use phpOMS\System\File\PathException; use phpOMS\System\File\PermissionException; use phpOMS\Utils\Parser\Php\ArrayParser; @@ -73,7 +74,6 @@ class InstallerAbstract $sth->bindValue(':from', $val['from'], \PDO::PARAM_STR); $sth->bindValue(':for', $val['for'], \PDO::PARAM_STR); $sth->bindValue(':file', $val['file'], \PDO::PARAM_STR); - $sth->execute(); } } @@ -115,7 +115,7 @@ class InstallerAbstract */ private static function activate(DatabasePool $dbPool, InfoManager $info) : void { - /** @var ActivateAbstract $class */ + /** @var StatusAbstract $class */ $class = '\Modules\\' . $info->getDirectory() . '\Admin\Status'; $class::activate($dbPool, $info); } @@ -150,11 +150,13 @@ class InstallerAbstract { $directories = new Directory(\dirname($info->getPath()) . '/Admin/Routes'); - foreach ($directories as $key => $subdir) { - if ($subdir instanceof Directory) { - foreach ($subdir as $key2 => $file) { - self::installRoutes(__DIR__ . '/../../' . $subdir->getName() . '/' . basename($file->getName(), '.php') . '/Routes.php', $file->getPath()); + foreach ($directories as $key => $child) { + if ($child instanceof Directory) { + foreach ($child as $key2 => $file) { + self::installRoutes(__DIR__ . '/../../' . $child->getName() . '/' . \basename($file->getName(), '.php') . '/Routes.php', $file->getPath()); } + } elseif ($child instanceof File) { + self::installRoutes(__DIR__ . '/../../' . $child->getName() . '/Routes.php', $child->getPath()); } } } @@ -185,7 +187,7 @@ class InstallerAbstract throw new PathException($destRoutePath); } - if (!is_writable($destRoutePath)) { + if (!\is_writable($destRoutePath)) { throw new PermissionException($destRoutePath); } @@ -194,7 +196,7 @@ class InstallerAbstract /** @noinspection PhpIncludeInspection */ $moduleRoutes = include $srcRoutePath; - $appRoutes = array_merge_recursive($appRoutes, $moduleRoutes); + $appRoutes = \array_merge_recursive($appRoutes, $moduleRoutes); \file_put_contents($destRoutePath, 'getPath()) . '/Admin/Hooks'); - foreach ($directories as $key => $subdir) { - if ($subdir instanceof Directory) { - foreach ($subdir as $key2 => $file) { - self::installHooks(__DIR__ . '/../../' . $subdir->getName() . '/' . basename($file->getName(), '.php') . '/Hooks.php', $file->getPath()); + foreach ($directories as $key => $child) { + if ($child instanceof Directory) { + foreach ($child as $key2 => $file) { + self::installHooks(__DIR__ . '/../../' . $child->getName() . '/' . \basename($file->getName(), '.php') . '/Hooks.php', $file->getPath()); } + } elseif ($child instanceof File) { + self::installRoutes(__DIR__ . '/../../' . $child->getName() . '/Hooks.php', $child->getPath()); } } } @@ -249,7 +253,7 @@ class InstallerAbstract throw new PathException($destHookPath); } - if (!is_writable($destHookPath)) { + if (!\is_writable($destHookPath)) { throw new PermissionException($destHookPath); } @@ -258,7 +262,7 @@ class InstallerAbstract /** @noinspection PhpIncludeInspection */ $moduleHooks = include $srcHookPath; - $appHooks = array_merge_recursive($appHooks, $moduleHooks); + $appHooks = \array_merge_recursive($appHooks, $moduleHooks); \file_put_contents($destHookPath, 'active === null || !$useCache) { + if (empty($this->active) || !$useCache) { switch ($this->app->dbPool->get('select')->getType()) { case DatabaseType::MYSQL: $sth = $this->app->dbPool->get('select')->con->prepare('SELECT `module_path` FROM `' . $this->app->dbPool->get('select')->prefix . 'module` WHERE `module_active` = 1'); $sth->execute(); - $this->active = $sth->fetchAll(\PDO::FETCH_COLUMN); + $active = $sth->fetchAll(\PDO::FETCH_COLUMN); + + foreach ($active as $module) { + $path = $this->modulePath . '/' . $module . '/info.json'; + + if (!\file_exists($path)) { + continue; + // throw new PathException($path); + } + + $content = \file_get_contents($path); + $json = \json_decode($content === false ? '[]' : $content, true); + $this->active[$json['name']['internal']] = $json === false ? [] : $json; + } break; default: throw new InvalidDatabaseTypeException($this->app->dbPool->get('select')->getType()); } } + + return $this->active; } @@ -221,7 +236,7 @@ final class ModuleManager */ public function isActive(string $module) : bool { - return \in_array($module, $this->getActiveModules(false)); + return isset($this->getActiveModules(false)[$module]); } /** @@ -233,7 +248,7 @@ final class ModuleManager */ public function getAllModules() : array { - if ($this->all === null) { + if (empty($this->all)) { chdir($this->modulePath); $files = glob('*', GLOB_ONLYDIR); $c = count($files); @@ -246,8 +261,9 @@ final class ModuleManager // throw new PathException($path); } - $json = \json_decode(file_get_contents($path), true); - $this->all[$json['name']['internal']] = $json; + $content = \file_get_contents($path); + $json = \json_decode($content === false ? '[]' : $content, true); + $this->all[$json['name']['internal']] = $json === false ? [] : $json; } } @@ -277,12 +293,26 @@ final class ModuleManager */ public function getInstalledModules(bool $useCache = true) : array { - if ($this->installed === null || !$useCache) { + if (empty($this->installed) || !$useCache) { switch ($this->app->dbPool->get('select')->getType()) { case DatabaseType::MYSQL: - $sth = $this->app->dbPool->get('select')->con->prepare('SELECT `module_id`,`module_theme`,`module_version` FROM `' . $this->app->dbPool->get('select')->prefix . 'module`'); + $sth = $this->app->dbPool->get('select')->con->prepare('SELECT `module_path` FROM `' . $this->app->dbPool->get('select')->prefix . 'module`'); $sth->execute(); - $this->installed = $sth->fetchAll(\PDO::FETCH_GROUP); + $installed = $sth->fetchAll(\PDO::FETCH_COLUMN); + + foreach ($installed as $module) { + $path = $this->modulePath . '/' . $module . '/info.json'; + + if (!\file_exists($path)) { + continue; + // throw new PathException($path); + } + + $content = \file_get_contents($path); + $json = \json_decode($content === false ? '[]' : $content, true); + $this->installed[$json['name']['internal']] = $json === false ? [] : $json; + } + break; default: throw new InvalidDatabaseTypeException($this->app->dbPool->get('select')->getType()); @@ -303,7 +333,7 @@ final class ModuleManager */ private function loadInfo(string $module) : InfoManager { - $path = realpath($oldPath = $this->modulePath . '/' . $module . '/info.json'); + $path = \realpath($oldPath = $this->modulePath . '/' . $module . '/info.json'); if ($path === false) { throw new PathException($oldPath); diff --git a/Module/PackageManager.php b/Module/PackageManager.php index b336ec7b6..d88d32bdb 100644 --- a/Module/PackageManager.php +++ b/Module/PackageManager.php @@ -109,7 +109,9 @@ final class PackageManager throw new PathException($this->extractPath); } - $this->info = \json_decode(file_get_contents($this->extractPath . '/info.json'), true); + $contents = \file_get_contents($this->extractPath . '/info.json'); + $info = \json_decode($contents === false ? '[]' : $contents, true); + $this->info = $info === false ? [] : $info; } /** @@ -121,7 +123,8 @@ final class PackageManager */ public function isValid() : bool { - return $this->authenticate(file_get_contents($this->extractPath . '/package.cert'), $this->hashFiles()); + $contents = \file_get_contents($this->extractPath . '/package.cert'); + return $this->authenticate($contents === false ? '' : $contents, $this->hashFiles()); } /** @@ -141,10 +144,15 @@ final class PackageManager continue; } - \sodium_crypto_generichash_update($state, \file_get_contents($this->extractPath . '/package/' . $file)); + $contents = \file_get_contents($this->extractPath . '/package/' . $file); + if ($contents === false) { + throw new \Exception(); + } + + \sodium_crypto_generichash_update($state, $contents); } - return \sodium_crypto_generichash_final(); + return \sodium_crypto_generichash_final($state); } /** diff --git a/README.md b/README.md index 3c5784431..336b975ff 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Maintainability](https://api.codeclimate.com/v1/badges/7e83e9f82ea1b19e1574/maintainability)](https://codeclimate.com/github/Orange-Management/phpOMS/maintainability) +[![Build Status](https://travis-ci.org/Orange-Management/phpOMS.svg?branch=develop)](https://travis-ci.org/Orange-Management/phpOMS) # General diff --git a/Router/Router.php b/Router/Router.php index 5e58ec9bb..cac6691fd 100644 --- a/Router/Router.php +++ b/Router/Router.php @@ -35,15 +35,6 @@ final class Router */ private $routes = []; - /** - * Constructor. - * - * @since 1.0.0 - */ - public function __construct() - { - } - /** * Add routes from file. * @@ -55,7 +46,7 @@ final class Router */ public function importFromFile(string $path) : bool { - if (file_exists($path)) { + if (\file_exists($path)) { /** @noinspection PhpIncludeInspection */ $this->routes += include $path; @@ -69,7 +60,7 @@ final class Router * Add route. * * @param string $route Route regex - * @param mixed $destination Destination e.g. Module:function & verb + * @param mixed $destination Destination e.g. Module:function string or callback * @param int $verb Request verb * * @return void @@ -100,22 +91,22 @@ final class Router * * @since 1.0.0 */ - public function route($request, int $verb = RouteVerb::GET) : array + public function route(string $request, int $verb = RouteVerb::GET, string $app = '', int $orgId = 1, $account = null) : array { - if ($request instanceof RequestAbstract) { - $uri = $request->getUri()->getRoute(); - $verb = $request->getRouteVerb(); - } elseif (is_string($request)) { - $uri = $request; - } else { - throw new \InvalidArgumentException(); - } - $bound = []; foreach ($this->routes as $route => $destination) { foreach ($destination as $d) { - if ($this->match($route, $d['verb'], $uri, $verb)) { - $bound[] = ['dest' => $d['dest']]; + if ($this->match($route, $d['verb'], $request, $verb)) { + if (!isset($d['permission']) + || !isset($account) + || (isset($d['permission']) + && isset($account) + && $account->hasPermission($d['permission']['type'], $orgId, $app, $d['permission']['module'], $d['permission']['state'])) + ) { + $bound[] = ['dest' => $d['dest']]; + } else { + \array_merge($bound, $this->route('/' . $app . '/e403', $verb)); + } } } } diff --git a/Security/PhpCode.php b/Security/PhpCode.php index 716246bd9..a6ca86393 100644 --- a/Security/PhpCode.php +++ b/Security/PhpCode.php @@ -104,7 +104,12 @@ final class PhpCode */ public static function isDisabled(array $functions) : bool { - $disabled = ini_get('disable_functions'); + $disabled = \ini_get('disable_functions'); + + if ($disabled === false) { + return true; + } + $disabled = \str_replace(' ', '', $disabled); $disabled = \explode(',', $disabled); @@ -149,6 +154,21 @@ final class PhpCode */ public static function validateFileIntegrity(string $source, string $hash) : bool { - return md5_file($source) === $hash; + return \md5_file($source) === $hash; + } + + /** + * Validate code integrety + * + * @param string $source Source code + * @param string $remote Remote code + * + * @return bool + * + * @since 1.0.0 + */ + public static function validateStringIntegrity(string $source, string $remote) : bool + { + return $source === $remote; } } diff --git a/Stdlib/Base/EnumArray.php b/Stdlib/Base/EnumArray.php index 30f7c04af..d33a314fe 100644 --- a/Stdlib/Base/EnumArray.php +++ b/Stdlib/Base/EnumArray.php @@ -26,6 +26,13 @@ namespace phpOMS\Stdlib\Base; */ abstract class EnumArray { + /** + * Constants. + * + * @var array + * @since 1.0.0 + */ + protected static $constants = []; /** * Checking enum name. diff --git a/Stdlib/Base/Iban.php b/Stdlib/Base/Iban.php index 44d68beb5..6fea5acdf 100644 --- a/Stdlib/Base/Iban.php +++ b/Stdlib/Base/Iban.php @@ -115,15 +115,16 @@ class Iban implements \Serializable { $country = $this->getCountry(); $layout = \str_replace(' ', '', IbanEnum::getByName('C_' . $country)); + $start = \stripos($layout, $sequence); + $end = \strrpos($layout, $sequence); - $start = \stripos($layout, $sequence); - $end = strrpos($layout, $sequence); - - if ($start === false) { + if ($start === false || $end === false) { return ''; } - return substr($this->iban, $start, $end - $start + 1); + $sequence = \substr($this->iban, $start, $end - $start + 1); + + return $sequence === false ? '' : $sequence; } /** @@ -135,7 +136,9 @@ class Iban implements \Serializable */ public function getCountry() : string { - return substr($this->iban, 0, 2); + $country = \substr($this->iban, 0, 2); + + return $country === false ? '?' : $country; } /** diff --git a/Stdlib/Base/Location.php b/Stdlib/Base/Location.php index f30934767..7f772c1df 100644 --- a/Stdlib/Base/Location.php +++ b/Stdlib/Base/Location.php @@ -297,7 +297,7 @@ class Location implements \JsonSerializable, \Serializable */ public function serialize() : string { - return \json_encode($this->jsonSerialize()); + return (string) \json_encode($this->jsonSerialize()); } /** diff --git a/Stdlib/Base/SmartDateTime.php b/Stdlib/Base/SmartDateTime.php index cf3e617b0..d0a95f317 100644 --- a/Stdlib/Base/SmartDateTime.php +++ b/Stdlib/Base/SmartDateTime.php @@ -97,8 +97,8 @@ class SmartDateTime extends \DateTime $yearNew = (int) $this->format('Y') + $y + $yearChange; $monthNew = ((int) $this->format('m') + $m) % 12; $monthNew = $monthNew === 0 ? 12 : $monthNew < 0 ? 12 + $monthNew : $monthNew; - $dayMonthOld = cal_days_in_month($calendar, (int) $this->format('m'), (int) $this->format('Y')); - $dayMonthNew = cal_days_in_month($calendar, $monthNew, $yearNew); + $dayMonthOld = \cal_days_in_month($calendar, (int) $this->format('m'), (int) $this->format('Y')); + $dayMonthNew = \cal_days_in_month($calendar, $monthNew, $yearNew); $dayOld = (int) $this->format('d'); if ($dayOld > $dayMonthNew) { @@ -219,7 +219,13 @@ class SmartDateTime extends \DateTime */ public static function getDayOfWeek(int $y, int $m, int $d) : int { - return (int) date('w', strtotime($d . '-' . $m . '-' . $y)); + $time = \strtotime($d . '-' . $m . '-' . $y); + + if ($time === false) { + return -1; + } + + return (int) date('w', $time); } /** diff --git a/Stdlib/Queue/PriorityQueue.php b/Stdlib/Queue/PriorityQueue.php index 96029929f..71357896e 100644 --- a/Stdlib/Queue/PriorityQueue.php +++ b/Stdlib/Queue/PriorityQueue.php @@ -53,14 +53,15 @@ class PriorityQueue implements \Countable, \Serializable /** * Insert element into queue. * - * @param mixed $data Queue element - * @param float $priority Priority of this element + * @param mixed $data Queue element + * @param string $job Job cmd + * @param float $priority Priority of this element * * @return int * * @since 1.0.0 */ - public function insert($data, float $priority = 1.0) : int + public function insert($data, string $job, float $priority = 1.0) : int { do { $key = rand(); @@ -78,6 +79,7 @@ class PriorityQueue implements \Countable, \Serializable $pos++; } + $original = []; array_splice($original, $pos, 0, [$key => ['key' => $key, 'job' => $job, 'priority' => $priority]]); } @@ -184,7 +186,7 @@ class PriorityQueue implements \Countable, \Serializable */ public function serialize() : string { - return \json_encode($this->queue); + return (string) \json_encode($this->queue); } /** diff --git a/System/File/ContainerInterface.php b/System/File/ContainerInterface.php index 8084f7a70..fc999e9b3 100644 --- a/System/File/ContainerInterface.php +++ b/System/File/ContainerInterface.php @@ -205,7 +205,7 @@ interface ContainerInterface * @param bool $recursive Consider subdirectories * @param array $ignore Files/paths to ignore (no regex) * - * @return string + * @return int * * @since 1.0.0 */ diff --git a/System/File/FileUtils.php b/System/File/FileUtils.php index c61690791..d7b35825d 100644 --- a/System/File/FileUtils.php +++ b/System/File/FileUtils.php @@ -92,7 +92,7 @@ final class FileUtils */ public static function absolute(string $origPath) : string { - if (!\file_exists($origPath)) { + if (!\file_exists($origPath) || \realpath($origPath) === false) { $startsWithSlash = strpos($origPath, '/') === 0 ? '/' : ''; $path = []; @@ -106,15 +106,34 @@ final class FileUtils if ($part !== '..') { $path[] = $part; } elseif (!empty($path)) { - array_pop($path); + \array_pop($path); } else { throw new PathException($origPath); } } - return $startsWithSlash . implode('/', $path); + return $startsWithSlash . \implode('/', $path); } - return realpath($origPath); + return \realpath($origPath); + } + + /** + * Change encoding of file + * + * @param string $file Path to file which should be re-encoded + * @param string $encoding New file encoding + * + * @return void + * + * @since 1.0.0 + */ + public static function changeFileEncoding(string $file, string $encoding) : void + { + $content = \file_get_contents($file); + + if ($content !== false && preg_match('!!u', $content)) { + \file_put_contents($file, \mb_convert_encoding($content, 'UTF-8', mb_list_encodings())); + } } } diff --git a/System/File/Ftp/Directory.php b/System/File/Ftp/Directory.php index 5a31e69a6..e12b9c441 100644 --- a/System/File/Ftp/Directory.php +++ b/System/File/Ftp/Directory.php @@ -178,6 +178,22 @@ class Directory extends FileAbstract implements DirectoryInterface } + /** + * {@inheritdoc} + */ + public static function dirname(string $path) : string + { + + } + + /** + * {@inheritdoc} + */ + public static function dirpath(string $path) : string + { + + } + /** * {@inheritdoc} */ diff --git a/System/File/Ftp/File.php b/System/File/Ftp/File.php index 6ff95bc18..07bc15335 100644 --- a/System/File/Ftp/File.php +++ b/System/File/Ftp/File.php @@ -138,6 +138,14 @@ class File extends FileAbstract implements FileInterface return $content; } + /** + * {@inheritdoc} + */ + public static function count(string $path, bool $recursive = true, array $ignore = []) : int + { + + } + /** * {@inheritdoc} */ diff --git a/System/File/Local/Directory.php b/System/File/Local/Directory.php index d5e51fe64..f07bcd5e3 100644 --- a/System/File/Local/Directory.php +++ b/System/File/Local/Directory.php @@ -71,7 +71,7 @@ final class Directory extends FileAbstract implements DirectoryInterface * @param string $path Path * @param string $filter Filter * - * @return string[] + * @return array * * @since 1.0.0 */ @@ -105,7 +105,7 @@ final class Directory extends FileAbstract implements DirectoryInterface * @param string $extension Extension * @param string $exclude Pattern to exclude * - * @return string[] + * @return array * * @since 1.0.0 */ @@ -168,6 +168,10 @@ final class Directory extends FileAbstract implements DirectoryInterface $countSize = 0; $directories = \scandir($dir); + if ($directories === false) { + return $countSize; + } + foreach ($directories as $key => $filename) { if ($filename === ".." || $filename === ".") { continue; @@ -181,7 +185,7 @@ final class Directory extends FileAbstract implements DirectoryInterface } } - return (int) $countSize; + return $countSize; } /** @@ -198,6 +202,10 @@ final class Directory extends FileAbstract implements DirectoryInterface $ignore[] = '.'; $ignore[] = '..'; + if ($files === false) { + return $size; + } + foreach ($files as $t) { if (\in_array($t, $ignore)) { continue; @@ -221,6 +229,10 @@ final class Directory extends FileAbstract implements DirectoryInterface { $files = \scandir($path); + if ($files === false) { + return false; + } + /* Removing . and .. */ unset($files[1]); unset($files[0]); @@ -260,7 +272,9 @@ final class Directory extends FileAbstract implements DirectoryInterface } $created = new \DateTime('now'); - $created->setTimestamp(\filemtime($path)); + $time = \filemtime($path); + + $created->setTimestamp($time === false ? 0 : $time); return $created; } @@ -275,7 +289,9 @@ final class Directory extends FileAbstract implements DirectoryInterface } $changed = new \DateTime(); - $changed->setTimestamp(\filectime($path)); + $time = \filectime($path); + + $changed->setTimestamp($time === false ? 0 : $time); return $changed; } @@ -289,7 +305,7 @@ final class Directory extends FileAbstract implements DirectoryInterface throw new PathException($path); } - return \fileowner($path); + return (int) \fileowner($path); } /** @@ -301,7 +317,7 @@ final class Directory extends FileAbstract implements DirectoryInterface throw new PathException($path); } - return \fileperms($path); + return (int) \fileperms($path); } /** diff --git a/System/File/Local/File.php b/System/File/Local/File.php index 2e0c84033..2f52e98f7 100644 --- a/System/File/Local/File.php +++ b/System/File/Local/File.php @@ -56,7 +56,7 @@ final class File extends FileAbstract implements FileInterface { parent::index(); - $this->size = \filesize($this->path); + $this->size = (int) \filesize($this->path); } /** @@ -98,7 +98,9 @@ final class File extends FileAbstract implements FileInterface throw new PathException($path); } - return \file_get_contents($path); + $contents = \file_get_contents($path); + + return $contents === false ? '' : $contents; } /** @@ -166,7 +168,9 @@ final class File extends FileAbstract implements FileInterface throw new PathException($path); } - return self::createFileTime(\filemtime($path)); + $time = \filemtime($path); + + return self::createFileTime($time === false ? 0 : $time); } /** @@ -178,7 +182,9 @@ final class File extends FileAbstract implements FileInterface throw new PathException($path); } - return self::createFileTime(\filemtime($path)); + $time = \filemtime($path); + + return self::createFileTime($time === false ? 0 : $time); } /** @@ -207,7 +213,7 @@ final class File extends FileAbstract implements FileInterface throw new PathException($path); } - return filesize($path); + return (int) \filesize($path); } /** @@ -219,7 +225,7 @@ final class File extends FileAbstract implements FileInterface throw new PathException($path); } - return \fileowner($path); + return (int) \fileowner($path); } /** @@ -231,7 +237,7 @@ final class File extends FileAbstract implements FileInterface throw new PathException($path); } - return \fileperms($path); + return (int) \fileperms($path); } /** @@ -245,7 +251,7 @@ final class File extends FileAbstract implements FileInterface */ public static function dirname(string $path) : string { - return basename(\dirname($path)); + return \basename(\dirname($path)); } /** @@ -259,7 +265,7 @@ final class File extends FileAbstract implements FileInterface */ public static function dirpath(string $path) : string { - return dirname($path); + return \dirname($path); } /** @@ -267,7 +273,7 @@ final class File extends FileAbstract implements FileInterface */ public static function copy(string $from, string $to, bool $overwrite = false) : bool { - if (!is_file($from)) { + if (!\is_file($from)) { throw new PathException($from); } @@ -325,7 +331,7 @@ final class File extends FileAbstract implements FileInterface */ public function getDirName() : string { - return basename(\dirname($this->path)); + return \basename(\dirname($this->path)); } /** @@ -375,7 +381,9 @@ final class File extends FileAbstract implements FileInterface */ public function getContent() : string { - return \file_get_contents($this->path); + $contents = \file_get_contents($this->path); + + return $contents === false ? '' : $contents; } /** diff --git a/System/File/Local/FileAbstract.php b/System/File/Local/FileAbstract.php index 2176205ea..1e4bbf37c 100644 --- a/System/File/Local/FileAbstract.php +++ b/System/File/Local/FileAbstract.php @@ -185,9 +185,15 @@ abstract class FileAbstract implements ContainerInterface */ public function index() : void { - $this->createdAt->setTimestamp(filemtime($this->path)); - $this->changedAt->setTimestamp(filectime($this->path)); - $this->owner = fileowner($this->path); - $this->permission = (int) substr(sprintf('%o', fileperms($this->path)), -4); + $mtime = \filemtime($this->path); + $ctime = \filectime($this->path); + + $this->createdAt->setTimestamp($mtime === false ? 0 : $mtime); + $this->changedAt->setTimestamp($ctime === false ? 0 : $ctime); + + $owner = \fileowner($this->path); + + $this->owner = $owner === false ? 0 : $owner; + $this->permission = (int) \substr(\sprintf('%o', \fileperms($this->path)), -4); } } diff --git a/System/File/Local/LocalStorage.php b/System/File/Local/LocalStorage.php index fc3860de9..f4e7420bf 100644 --- a/System/File/Local/LocalStorage.php +++ b/System/File/Local/LocalStorage.php @@ -54,13 +54,7 @@ class LocalStorage extends StorageAbstract } /** - * Get the internal class type (directory or file) based on path. - * - * @param string $path Path to the directory or file - * - * @return string Class namespace - * - * @since 1.0.0 + * {@inheritdoc} */ protected static function getClassType(string $path) : string { diff --git a/System/File/StorageAbstract.php b/System/File/StorageAbstract.php index d36e09831..48b1d8d37 100644 --- a/System/File/StorageAbstract.php +++ b/System/File/StorageAbstract.php @@ -43,6 +43,17 @@ abstract class StorageAbstract */ abstract public static function getInstance() : StorageAbstract; + /** + * Get the internal class type (directory or file) based on path. + * + * @param string $path Path to the directory or file + * + * @return string Class namespace + * + * @since 1.0.0 + */ + abstract protected static function getClassType(string $path) : string; + /** * Get storage type. * @@ -265,7 +276,7 @@ abstract class StorageAbstract * @param bool $recursive Consider subdirectories * @param array $ignore Files/paths to ignore (no regex) * - * @return string + * @return int * * @since 1.0.0 */ diff --git a/System/SystemUtils.php b/System/SystemUtils.php index ce40b5a34..729e9a1d3 100644 --- a/System/SystemUtils.php +++ b/System/SystemUtils.php @@ -46,25 +46,27 @@ final class SystemUtils { $mem = 0; - if (stristr(PHP_OS, 'WIN')) { - $mem = null; - exec('wmic memorychip get capacity', $mem); + if (\stristr(PHP_OS, 'WIN')) { + $memArr = []; + exec('wmic memorychip get capacity', $memArr); - /** @var array $mem */ - $mem = array_sum($mem) / 1024; - } elseif (stristr(PHP_OS, 'LINUX')) { - $fh = fopen('/proc/meminfo', 'r'); - $mem = 0; + $mem = \array_sum($memArr) / 1024; + } elseif (\stristr(PHP_OS, 'LINUX')) { + $fh = \fopen('/proc/meminfo', 'r'); - while ($line = fgets($fh)) { + if ($fh === false) { + return $mem; + } + + while ($line = \fgets($fh)) { $pieces = []; if (\preg_match('/^MemTotal:\s+(\d+)\skB$/', $line, $pieces)) { - $mem = $pieces[1] * 1024; + $mem = (int) ($pieces[1] ?? 0) * 1024; break; } } - fclose($fh); + \fclose($fh); } return (int) $mem; @@ -81,12 +83,17 @@ final class SystemUtils { $memUsage = 0; - if (stristr(PHP_OS, 'LINUX')) { - $free = shell_exec('free'); - $free = (string) trim($free); + if (\stristr(PHP_OS, 'LINUX')) { + $free = \shell_exec('free'); + + if ($free === null) { + return $memUsage; + } + + $free = trim($free); $freeArr = \explode("\n", $free); - $mem = \explode(" ", $freeArr[1]); - $mem = array_values(array_filter($mem)); + $mem = \explode(' ', $freeArr[1]); + $mem = \array_values(\array_filter($mem)); $memUsage = $mem[2] / $mem[1] * 100; } @@ -104,11 +111,11 @@ final class SystemUtils { $cpuUsage = 0; - if (stristr(PHP_OS, 'WIN') !== false) { + if (\stristr(PHP_OS, 'WIN') !== false) { $cpuUsage = null; exec('wmic cpu get LoadPercentage', $cpuUsage); $cpuUsage = $cpuUsage[1]; - } elseif (stristr(PHP_OS, 'LINUX') !== false) { + } elseif (\stristr(PHP_OS, 'LINUX') !== false) { $cpuUsage = \sys_getloadavg()[0] * 100; } diff --git a/UnhandledHandler.php b/UnhandledHandler.php index defda347f..2a920073b 100644 --- a/UnhandledHandler.php +++ b/UnhandledHandler.php @@ -61,14 +61,14 @@ final class UnhandledHandler { $logger = FileLogger::getInstance(__DIR__ . '/../Logs'); - if (!(error_reporting() & $errno)) { + if (!(\error_reporting() & $errno)) { $logger->error(FileLogger::MSG_FULL, [ 'message' => 'Undefined error', 'line' => $errline, 'file' => $errfile, ]); - error_clear_last(); + \error_clear_last(); return false; } @@ -80,7 +80,7 @@ final class UnhandledHandler 'file' => $errfile, ]); - error_clear_last(); + \error_clear_last(); return true; } @@ -95,9 +95,9 @@ final class UnhandledHandler */ public static function shutdownHandler() : void { - $e = error_get_last(); + $e = \error_get_last(); - if (isset($e)) { + if ($e !== null) { $logger = FileLogger::getInstance(__DIR__ . '/../Logs'); $logger->warning(FileLogger::MSG_FULL, [ 'message' => $e['message'], diff --git a/Uri/Argument.php b/Uri/Argument.php new file mode 100644 index 000000000..1b5196197 --- /dev/null +++ b/Uri/Argument.php @@ -0,0 +1,326 @@ +set($uri); + } + + /** + * {@inheritdoc} + */ + public function set(string $uri) : void + { + $this->uri = $uri; + + $temp = $this->__toString(); + $found = \stripos($temp, ':'); + $path = $found !== false && $found > 3 && $found < 8 ? \substr($temp, $found) : $temp; + $this->path = $path === false ? '' : $path; + } + + /** + * {@inheritdoc} + */ + public static function isValid(string $uri) : bool + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getRootPath() : string + { + return $this->rootPath; + } + + /** + * {@inheritdoc} + */ + public function setRootPath(string $root) : void + { + $this->rootPath = $root; + $this->set($this->uri); + } + + /** + * {@inheritdoc} + */ + public function getScheme() : string + { + return $this->scheme; + } + + /** + * {@inheritdoc} + */ + public function getHost() : string + { + return $this->host; + } + + /** + * {@inheritdoc} + */ + public function getPort() : int + { + return $this->port; + } + + /** + * {@inheritdoc} + */ + public function getPass() : string + { + return $this->pass; + } + + /** + * {@inheritdoc} + */ + public function getPath() : string + { + return $this->path; + } + + /** + * Get path offset. + * + * @return int + * + * @since 1.0.0 + */ + public function getPathOffset() : int + { + return \substr_count($this->rootPath, '/') - 1; + } + + /** + * {@inheritdoc} + */ + public function getRoute() : string + { + $query = $this->getQuery(); + return $this->path . (!empty($query) ? '?' . $this->getQuery() : ''); + } + + /** + * {@inheritdoc} + */ + public function getQuery(string $key = null) : string + { + if ($key !== null) { + $key = \strtolower($key); + + return $this->query[$key] ?? ''; + } + + return $this->queryString; + } + + /** + * {@inheritdoc} + */ + public function getPathElement(int $pos = null) : string + { + return explode('/', $this->path)[$pos] ?? ''; + } + + /** + * {@inheritdoc} + */ + public function getPathElements() : array + { + return explode('/', $this->path); + } + + /** + * {@inheritdoc} + */ + public function getQueryArray() : array + { + return $this->query; + } + + /** + * {@inheritdoc} + */ + public function getFragment() : string + { + return $this->fragment; + } + + /** + * {@inheritdoc} + */ + public function getBase() : string + { + return $this->base; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->uri; + } + + /** + * {@inheritdoc} + */ + public function getAuthority() : string + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function getUser() : string + { + return $this->user; + } + + /** + * {@inheritdoc} + */ + public function getUserInfo() : string + { + return ''; + } +} diff --git a/Uri/Http.php b/Uri/Http.php index 647cbeb15..b9803d16f 100644 --- a/Uri/Http.php +++ b/Uri/Http.php @@ -145,7 +145,7 @@ final class Http implements UriInterface public function set(string $uri) : void { $this->uri = $uri; - $url = parse_url($this->uri); + $url = \parse_url($this->uri); $this->scheme = $url['scheme'] ?? ''; $this->host = $url['host'] ?? ''; @@ -155,17 +155,23 @@ final class Http implements UriInterface $this->path = $url['path'] ?? ''; if (StringUtils::endsWith($this->path, '.php')) { - $this->path = substr($this->path, 0, -4); + $path = \substr($this->path, 0, -4); + + if ($path === false) { + throw new \Exception(); + } + + $this->path = $path; } - $this->path = strpos($this->path, $this->rootPath) === 0 ? substr($this->path, strlen($this->rootPath), strlen($this->path)) : $this->path; + $this->path = \strpos($this->path, $this->rootPath) === 0 ? \substr($this->path, \strlen($this->rootPath), \strlen($this->path)) : $this->path; $this->queryString = $url['query'] ?? ''; if (!empty($this->queryString)) { - parse_str($this->queryString, $this->query); + \parse_str($this->queryString, $this->query); } - $this->query = array_change_key_case($this->query, CASE_LOWER); + $this->query = \array_change_key_case($this->query, CASE_LOWER); $this->fragment = $url['fragment'] ?? ''; $this->base = $this->scheme . '://' . $this->host . $this->rootPath; @@ -189,7 +195,7 @@ final class Http implements UriInterface */ public static function isValid(string $uri) : bool { - return (bool) filter_var($uri, FILTER_VALIDATE_URL); + return (bool) \filter_var($uri, FILTER_VALIDATE_URL); } /** @@ -258,7 +264,7 @@ final class Http implements UriInterface */ public function getPathOffset() : int { - return substr_count($this->rootPath, '/') - 1; + return \substr_count($this->rootPath, '/') - 1; } /** @@ -276,7 +282,7 @@ final class Http implements UriInterface public function getQuery(string $key = null) : string { if ($key !== null) { - $key = strtolower($key); + $key = \strtolower($key); return $this->query[$key] ?? ''; } @@ -289,7 +295,7 @@ final class Http implements UriInterface */ public function getPathElement(int $pos = null) : string { - return explode('/', $this->path)[$pos] ?? ''; + return \explode('/', $this->path)[$pos] ?? ''; } /** @@ -297,7 +303,7 @@ final class Http implements UriInterface */ public function getPathElements() : array { - return explode('/', $this->path); + return \explode('/', $this->path); } /** diff --git a/Uri/UriFactory.php b/Uri/UriFactory.php index 6ce3a6b12..0afe90c10 100644 --- a/Uri/UriFactory.php +++ b/Uri/UriFactory.php @@ -187,11 +187,10 @@ final class UriFactory */ private static function unique(string $url) : string { - $parts = \explode('?', $url); + $parts = \explode('&', \str_replace('?', '&', $url)); if (count($parts) >= 2) { - $full = $parts[1]; - $pars = \explode('&', $full); + $pars = \array_slice($parts, 1); $comps = []; $length = count($pars); diff --git a/Utils/ArrayUtils.php b/Utils/ArrayUtils.php index 02733d7e6..839bb9eae 100644 --- a/Utils/ArrayUtils.php +++ b/Utils/ArrayUtils.php @@ -51,8 +51,11 @@ final class ArrayUtils $nodes = \explode($delim, trim($path, $delim)); $prevEl = null; $el = &$data; + $node = null; - $node = null; + if ($nodes === false) { + throw new \Exception(); + } foreach ($nodes as &$node) { $prevEl = &$el; @@ -89,6 +92,10 @@ final class ArrayUtils $pathParts = \explode($delim, trim($path, $delim)); $current = &$data; + if ($pathParts === false) { + throw new \Exception(); + } + foreach ($pathParts as $key) { $current = &$current[$key]; } @@ -124,6 +131,10 @@ final class ArrayUtils $pathParts = \explode($delim, trim($path, $delim)); $current = $data; + if ($pathParts === false) { + throw new \Exception(); + } + foreach ($pathParts as $key) { if (!isset($current[$key])) { return null; @@ -266,14 +277,19 @@ final class ArrayUtils */ public static function arrayToCsv(array $data, string $delimiter = ';', string $enclosure = '"', string $escape = '\\') : string { - $outstream = fopen('php://memory', 'r+'); - /** @noinspection PhpMethodParametersCountMismatchInspection */ - fputcsv($outstream, $data, $delimiter, $enclosure, $escape); - rewind($outstream); - $csv = fgets($outstream); - fclose($outstream); + $outstream = \fopen('php://memory', 'r+'); - return $csv; + if ($outstream === false) { + throw new \Exception(); + } + + /** @noinspection PhpMethodParametersCountMismatchInspection */ + \fputcsv($outstream, $data, $delimiter, $enclosure, $escape); + rewind($outstream); + $csv = \fgets($outstream); + \fclose($outstream); + + return $csv === false ? '' : $csv; } /** @@ -290,11 +306,11 @@ final class ArrayUtils */ public static function getArg(string $id, array $args) : ?string { - if (($key = array_search($id, $args)) === false || $key === count($args) - 1) { + if (($key = \array_search($id, $args)) === false || $key === count($args) - 1) { return null; } - return trim($args[$key + 1], '" '); + return trim($args[(int) $key + 1], '" '); } /** @@ -309,11 +325,11 @@ final class ArrayUtils */ public static function hasArg(string $id, array $args) : ?int { - if (($key = array_search($id, $args)) === false) { + if (($key = \array_search($id, $args)) === false) { return null; } - return $key; + return (int) $key; } /** diff --git a/Utils/Barcode/C128Abstract.php b/Utils/Barcode/C128Abstract.php index 9c730fd8d..e66572cea 100644 --- a/Utils/Barcode/C128Abstract.php +++ b/Utils/Barcode/C128Abstract.php @@ -147,11 +147,11 @@ abstract class C128Abstract public function setDimension(int $width, int $height) : void { if ($width < 0) { - throw new \OutOfBoundsException($width); + throw new \OutOfBoundsException((string) $width); } if ($height < 0) { - throw new \OutOfBoundsException($height); + throw new \OutOfBoundsException((string) $height); } $this->dimension['width'] = $width; @@ -243,8 +243,8 @@ abstract class C128Abstract { $res = $this->get(); - imagepng($res, $file); - imagedestroy($res); + \imagepng($res, $file); + \imagedestroy($res); } /** @@ -260,8 +260,8 @@ abstract class C128Abstract { $res = $this->get(); - imagejpeg($res, $file); - imagedestroy($res); + \imagejpeg($res, $file); + \imagedestroy($res); } /** @@ -273,14 +273,14 @@ abstract class C128Abstract */ protected function generateCodeString() : string { - $keys = array_keys(static::$CODEARRAY); - $values = array_flip($keys); + $keys = \array_keys(static::$CODEARRAY); + $values = \array_flip($keys); $codeString = ''; - $length = strlen($this->content); + $length = \strlen($this->content); $checksum = static::$CHECKSUM; for ($pos = 1; $pos <= $length; $pos++) { - $activeKey = substr($this->content, ($pos - 1), 1); + $activeKey = \substr($this->content, ($pos - 1), 1); $codeString .= static::$CODEARRAY[$activeKey]; $checksum += $values[$activeKey] * $pos; } @@ -302,18 +302,23 @@ abstract class C128Abstract protected function createImage(string $codeString) { $dimensions = $this->calculateDimensions($codeString); - $image = imagecreate($dimensions['width'], $dimensions['height']); - $black = imagecolorallocate($image, 0, 0, 0); - $white = imagecolorallocate($image, 255, 255, 255); + $image = \imagecreate($dimensions['width'], $dimensions['height']); + + if ($image === false) { + throw new \Exception(); + } + + $black = \imagecolorallocate($image, 0, 0, 0); + $white = \imagecolorallocate($image, 255, 255, 255); $location = 0; - $length = strlen($codeString); - imagefill($image, 0, 0, $white); + $length = \strlen($codeString); + \imagefill($image, 0, 0, $white); for ($position = 1; $position <= $length; $position++) { - $cur_size = $location + (int) (substr($codeString, ($position - 1), 1)); + $cur_size = $location + (int) (\substr($codeString, ($position - 1), 1)); if ($this->orientation === OrientationType::HORIZONTAL) { - imagefilledrectangle( + \imagefilledrectangle( $image, $location + $this->margin, 0 + $this->margin, @@ -322,7 +327,7 @@ abstract class C128Abstract ($position % 2 == 0 ? $white : $black) ); } else { - imagefilledrectangle( + \imagefilledrectangle( $image, 0 + $this->margin, $location + $this->margin, @@ -350,10 +355,10 @@ abstract class C128Abstract private function calculateCodeLength(string $codeString) : int { $codeLength = 0; - $length = strlen($codeString); + $length = \strlen($codeString); for ($i = 1; $i <= $length; ++$i) { - $codeLength = $codeLength + (int) (substr($codeString, ($i - 1), 1)); + $codeLength = $codeLength + (int) (\substr($codeString, ($i - 1), 1)); } return $codeLength; diff --git a/Utils/Compression/LZW.php b/Utils/Compression/LZW.php index 3e8cee371..5757f9fe8 100644 --- a/Utils/Compression/LZW.php +++ b/Utils/Compression/LZW.php @@ -39,17 +39,17 @@ class LZW implements CompressionInterface $dictionary[chr($i)] = $i; } - $length = strlen($source); + $length = \strlen($source); for ($i = 0; $i < $length; ++$i) { $c = $source[$i]; $wc = $w . $c; - if (array_key_exists($w . $c, $dictionary)) { + if (\array_key_exists($w . $c, $dictionary)) { $w = $w . $c; } else { $result[] = $dictionary[$w]; $dictionary[$wc] = $dictSize++; - $w = (string) $c; + $w = $c; } } @@ -57,7 +57,7 @@ class LZW implements CompressionInterface $result[] = $dictionary[$w]; } - return implode(',', $result); + return \implode(',', $result); } /** @@ -70,16 +70,20 @@ class LZW implements CompressionInterface $entry = ''; $dictSize = 256; + if (empty($compressed)) { + return ''; + } + for ($i = 0; $i < 256; ++$i) { $dictionary[$i] = chr($i); } - $w = chr($compressed[0]); - $result = $dictionary[$compressed[0]]; + $w = chr((int) $compressed[0]); + $result = $dictionary[(int) ($compressed[0])] ?? 0; $count = count($compressed); for ($i = 1; $i < $count; ++$i) { - $k = $compressed[$i]; + $k = (int) $compressed[$i]; if ($dictionary[$k]) { $entry = $dictionary[$k]; diff --git a/Utils/Converter/Ip.php b/Utils/Converter/Ip.php index a2e686b8b..21a86dad0 100644 --- a/Utils/Converter/Ip.php +++ b/Utils/Converter/Ip.php @@ -49,6 +49,9 @@ class Ip { $split = \explode('.', $ip); - return $split[0] * (256 ** 3) + $split[1] * (256 ** 2) + $split[2] * (256 ** 1) + $split[3]; + return ((int) $split[0] ?? 0) * (256 ** 3) + + ((int) $split[1] ?? 0) * (256 ** 2) + + ((int) $split[2] ?? 0) * (256 ** 1) + + ((int) $split[3] ?? 0); } } diff --git a/Utils/Converter/Numeric.php b/Utils/Converter/Numeric.php index eb359d177..f0d929b5a 100644 --- a/Utils/Converter/Numeric.php +++ b/Utils/Converter/Numeric.php @@ -64,22 +64,26 @@ class Numeric return $numberInput; } - $fromBase = str_split($fromBaseInput, 1); - $toBase = str_split($toBaseInput, 1); - $number = str_split($numberInput, 1); - $fromLen = strlen($fromBaseInput); - $toLen = strlen($toBaseInput); - $numberLen = strlen($numberInput); + $fromBase = \str_split($fromBaseInput, 1); + $toBase = \str_split($toBaseInput, 1); + $number = \str_split($numberInput, 1); + $fromLen = \strlen($fromBaseInput); + $toLen = \strlen($toBaseInput); + $numberLen = \strlen($numberInput); $newOutput = ''; + if ($fromBase === false || $toBase === false || $number === false) { + throw new \Exception(); + } + if ($toBaseInput === '0123456789') { - $newOutput = 0; + $newOutput = '0'; for ($i = 1; $i <= $numberLen; ++$i) { $newOutput = bcadd( - (string) $newOutput, + $newOutput, bcmul( - (string) array_search($number[$i - 1], $fromBase), + (string) \array_search($number[$i - 1], $fromBase), bcpow((string) $fromLen, (string) ($numberLen - $i)) ) ); @@ -88,15 +92,15 @@ class Numeric return $newOutput; } - $base10 = $fromBaseInput != '0123456789' ? self::convertBase($numberInput, $fromBaseInput, '0123456789') : $numberInput; + $base10 = (int) ($fromBaseInput != '0123456789' ? self::convertBase($numberInput, $fromBaseInput, '0123456789') : $numberInput); - if ($base10 < strlen($toBaseInput)) { + if ($base10 < \strlen($toBaseInput)) { return $toBase[$base10]; } while ($base10 !== '0') { - $newOutput = $toBase[bcmod($base10, (string) $toLen)] . $newOutput; - $base10 = bcdiv($base10, (string) $toLen, 0); + $newOutput = $toBase[(int) bcmod((string) $base10, (string) $toLen)] . $newOutput; + $base10 = bcdiv((string) $base10, (string) $toLen, 0); } return $newOutput; @@ -144,9 +148,13 @@ class Numeric $result = 0; foreach (self::ROMANS as $key => $value) { - while (strpos($roman, $key) === 0) { + while (\strpos($roman, $key) === 0) { $result += $value; - $roman = substr($roman, strlen($key)); + $temp = \substr($roman, \strlen($key)); + + if ($temp !== false) { + $roman = $temp; + } } } @@ -169,7 +177,7 @@ class Numeric $alpha = ''; for ($i = 1; $number >= 0 && $i < 10; ++$i) { - $alpha = chr(0x41 + ($number % pow(26, $i) / pow(26, $i - 1))) . $alpha; + $alpha = chr(0x41 + (int) ($number % pow(26, $i) / pow(26, $i - 1))) . $alpha; $number -= pow(26, $i); } @@ -188,12 +196,12 @@ class Numeric public static function alphaToNumeric(string $alpha) : int { $numeric = 0; - $length = strlen($alpha); + $length = \strlen($alpha); for ($i = 0; $i < $length; ++$i) { $numeric += pow(26, $i) * (ord($alpha[$length - $i - 1]) - 0x40); } - return $numeric - 1; + return (int) $numeric - 1; } } diff --git a/Utils/Encoding/Huffman/Huffman.php b/Utils/Encoding/Huffman/Huffman.php index b79f5d7d3..165a1c73e 100644 --- a/Utils/Encoding/Huffman/Huffman.php +++ b/Utils/Encoding/Huffman/Huffman.php @@ -82,11 +82,15 @@ final class Huffman $binary .= $this->dictionary->get($source[$i]); } - $splittedBinaryString = str_split('1' . $binary . '1', 8); + $splittedBinaryString = \str_split('1' . $binary . '1', 8); $binary = ''; + if ($splittedBinaryString === false) { + return $binary; + } + foreach ($splittedBinaryString as $i => $c) { - while (strlen($c) < 8) { + while (\strlen($c) < 8) { $c .= '0'; } @@ -112,22 +116,40 @@ final class Huffman } $binary = ''; - $rawLenght = strlen($raw); + $rawLenght = \strlen($raw); $source = ''; for ($i = 0; $i < $rawLenght; ++$i) { $decbin = decbin(ord($raw[$i])); - while (strlen($decbin) < 8) { + while (\strlen($decbin) < 8) { $decbin = '0' . $decbin; } if ($i === 0) { - $decbin = substr($decbin, strpos($decbin, '1') + 1); + $pos = \strpos($decbin, '1'); + + if ($pos === false) { + throw new \Exception(); + } + + $decbin = \substr($decbin, $pos + 1); + if ($decbin === false) { + throw new \Exception(); + } } if ($i + 1 === $rawLenght) { - $decbin = substr($decbin, 0, strrpos($decbin, '1')); + $pos = \strrpos($decbin, '1'); + + if ($pos === false) { + throw new \Exception(); + } + + $decbin = \substr($decbin, 0, $pos); + if ($decbin === false) { + throw new \Exception(); + } } $binary .= $decbin; diff --git a/Utils/Git/Commit.php b/Utils/Git/Commit.php index 758468bec..e31bcaf48 100644 --- a/Utils/Git/Commit.php +++ b/Utils/Git/Commit.php @@ -101,7 +101,7 @@ class Commit $this->author = new Author(); $this->branch = new Branch(); $this->tag = new Tag(); - $this->repository = new Repository(realpath(__DIR__ . '/../../../../../')); + $this->repository = new Repository(); } /** diff --git a/Utils/Git/Git.php b/Utils/Git/Git.php index 2c6c5ef6c..ffd3c6e5e 100644 --- a/Utils/Git/Git.php +++ b/Utils/Git/Git.php @@ -45,16 +45,16 @@ class Git public static function test() : bool { $pipes = []; - $resource = proc_open(escapeshellarg(Git::getBin()), [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes); + $resource = \proc_open(\escapeshellarg(Git::getBin()), [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes); - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); + $stdout = \stream_get_contents($pipes[1]); + $stderr = \stream_get_contents($pipes[2]); foreach ($pipes as $pipe) { - fclose($pipe); + \fclose($pipe); } - return trim(proc_close($resource)) !== 127; + return $resource !== false && \proc_close($resource) !== 127; } /** @@ -82,10 +82,10 @@ class Git */ public static function setBin(string $path) : void { - if (realpath($path) === false) { + if (\realpath($path) === false) { throw new PathException($path); } - self::$bin = realpath($path); + self::$bin = \realpath($path); } } diff --git a/Utils/Git/Repository.php b/Utils/Git/Repository.php index 3e9c69d11..fd84f731c 100644 --- a/Utils/Git/Repository.php +++ b/Utils/Git/Repository.php @@ -67,9 +67,11 @@ class Repository * * @since 1.0.0 */ - public function __construct(string $path) + public function __construct(string $path = '') { - $this->setPath($path); + if (\is_dir($path)) { + $this->setPath($path); + } } /** @@ -85,20 +87,16 @@ class Repository */ private function setPath(string $path) : void { - if (!is_dir($path)) { + if (!\is_dir($path) || \realpath($path) === false) { throw new PathException($path); } - $this->path = realpath($path); + $this->path = \realpath($path); - if ($this->path === false) { - throw new PathException($path); - } - - if (file_exists($this->path . '/.git') && \is_dir($this->path . '/.git')) { + if (\file_exists($this->path . '/.git') && \is_dir($this->path . '/.git')) { $this->bare = false; - } elseif (is_file($this->path . '/config')) { // Is this a bare repo? - $parseIni = parse_ini_file($this->path . '/config'); + } elseif (\is_file($this->path . '/config')) { // Is this a bare repo? + $parseIni = \parse_ini_file($this->path . '/config'); if ($parseIni['bare']) { $this->bare = true; @@ -128,7 +126,7 @@ class Repository public function getActiveBranch() : Branch { $branches = $this->getBranches(); - $active = preg_grep('/^\*/', $branches); + $active = \preg_grep('/^\*/', $branches); reset($active); return new Branch(current($active)); @@ -170,14 +168,14 @@ class Repository */ private function run(string $cmd) : array { - if (strtolower(substr(PHP_OS, 0, 3)) == 'win') { - $cmd = 'cd ' . escapeshellarg(\dirname(Git::getBin())) - . ' && ' . basename(Git::getBin()) - . ' -C ' . escapeshellarg($this->path) . ' ' + if (\strtolower((string) \substr(PHP_OS, 0, 3)) == 'win') { + $cmd = 'cd ' . \escapeshellarg(\dirname(Git::getBin())) + . ' && ' . \basename(Git::getBin()) + . ' -C ' . \escapeshellarg($this->path) . ' ' . $cmd; } else { - $cmd = escapeshellarg(Git::getBin()) - . ' -C ' . escapeshellarg($this->path) . ' ' + $cmd = \escapeshellarg(Git::getBin()) + . ' -C ' . \escapeshellarg($this->path) . ' ' . $cmd; } @@ -187,15 +185,20 @@ class Repository 2 => ['pipe', 'w'], ]; - $resource = proc_open($cmd, $desc, $pipes, $this->path, null); - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); + $resource = \proc_open($cmd, $desc, $pipes, $this->path, null); - foreach ($pipes as $pipe) { - fclose($pipe); + if ($resource === false) { + throw new \Exception(); } - $status = trim(proc_close($resource)); + $stdout = \stream_get_contents($pipes[1]); + $stderr = \stream_get_contents($pipes[2]); + + foreach ($pipes as $pipe) { + \fclose($pipe); + } + + $status = \proc_close($resource); if ($status == -1) { throw new \Exception($stderr); @@ -215,11 +218,15 @@ class Repository */ private function parseLines(string $lines) : array { - $lineArray = preg_split('/\r\n|\n|\r/', $lines); + $lineArray = \preg_split('/\r\n|\n|\r/', $lines); $lines = []; + if ($lineArray === false) { + return $lines; + } + foreach ($lineArray as $key => $line) { - $temp = preg_replace('/\s+/', ' ', trim($line, ' ')); + $temp = \preg_replace('/\s+/', ' ', trim($line, ' ')); if (!empty($temp)) { $lines[] = $temp; @@ -242,7 +249,7 @@ class Repository */ public function create(string $source = null) : void { - if (!is_dir($this->path) || \file_exists($this->path . '/.git')) { + if (!\is_dir($this->path) || \file_exists($this->path . '/.git')) { throw new \Exception('Already repository'); } @@ -264,7 +271,7 @@ class Repository */ public function status() : string { - return implode("\n", $this->run('status')); + return \implode("\n", $this->run('status')); } /** @@ -282,7 +289,7 @@ class Repository { $files = $this->parseFileList($files); - return implode("\n", $this->run('add ' . $files . ' -v')); + return \implode("\n", $this->run('add ' . $files . ' -v')); } /** @@ -299,7 +306,7 @@ class Repository { $files = $this->parseFileList($files); - return implode("\n", $this->run('rm ' . ($cached ? '--cached ' : '') . $files)); + return \implode("\n", $this->run('rm ' . ($cached ? '--cached ' : '') . $files)); } /** @@ -315,9 +322,9 @@ class Repository */ private function parseFileList($files) : string { - if (is_array($files)) { - return '"' . implode('" "', $files) . '"'; - } elseif (!is_string($files)) { + if (\is_array($files)) { + return '"' . \implode('" "', $files) . '"'; + } elseif (!\is_string($files)) { throw new \InvalidArgumentException('Wrong type for $files.'); } @@ -336,7 +343,7 @@ class Repository */ public function commit(Commit $commit, $all = true) : string { - return implode("\n", $this->run('commit ' . ($all ? '-av' : '-v') . ' -m ' . escapeshellarg($commit->getMessage()))); + return \implode("\n", $this->run('commit ' . ($all ? '-av' : '-v') . ' -m ' . \escapeshellarg($commit->getMessage()))); } /** @@ -352,11 +359,11 @@ class Repository */ public function cloneTo(string $target) : string { - if (!is_dir($target)) { + if (!\is_dir($target)) { throw new PathException($target); } - return implode("\n", $this->run('clone --local ' . $this->path . ' ' . $target)); + return \implode("\n", $this->run('clone --local ' . $this->path . ' ' . $target)); } /** @@ -372,11 +379,11 @@ class Repository */ public function cloneFrom(string $source) : string { - if (!is_dir($source)) { + if (!\is_dir($source)) { throw new PathException($source); } - return implode("\n", $this->run('clone --local ' . $source . ' ' . $this->path)); + return \implode("\n", $this->run('clone --local ' . $source . ' ' . $this->path)); } /** @@ -390,7 +397,7 @@ class Repository */ public function cloneRemote(string $source) : string { - return implode("\n", $this->run('clone ' . $source . ' ' . $this->path)); + return \implode("\n", $this->run('clone ' . $source . ' ' . $this->path)); } /** @@ -405,7 +412,7 @@ class Repository */ public function clean(bool $dirs = false, bool $force = false) : string { - return implode("\n", $this->run('clean' . ($force ? ' -f' : '') . ($dirs ? ' -d' : ''))); + return \implode("\n", $this->run('clean' . ($force ? ' -f' : '') . ($dirs ? ' -d' : ''))); } /** @@ -420,7 +427,7 @@ class Repository */ public function createBranch(Branch $branch, bool $force = false) : string { - return implode("\n", $this->run('branch ' . ($force ? '-D' : '-d') . ' ' . $branch->getName())); + return \implode("\n", $this->run('branch ' . ($force ? '-D' : '-d') . ' ' . $branch->getName())); } /** @@ -488,7 +495,7 @@ class Repository */ public function checkout(Branch $branch) : string { - $result = implode("\n", $this->run('checkout ' . $branch->getName())); + $result = \implode("\n", $this->run('checkout ' . $branch->getName())); $this->branch = $branch; return $result; @@ -505,7 +512,7 @@ class Repository */ public function merge(Branch $branch) : string { - return implode("\n", $this->run('merge ' . $branch->getName() . ' --no-ff')); + return \implode("\n", $this->run('merge ' . $branch->getName() . ' --no-ff')); } /** @@ -517,7 +524,7 @@ class Repository */ public function fetch() : string { - return implode("\n", $this->run('fetch')); + return \implode("\n", $this->run('fetch')); } /** @@ -531,7 +538,7 @@ class Repository */ public function createTag(Tag $tag) : string { - return implode("\n", $this->run('tag -a ' . $tag->getName() . ' -m ' . escapeshellarg($tag->getMessage()))); + return \implode("\n", $this->run('tag -a ' . $tag->getName() . ' -m ' . \escapeshellarg($tag->getMessage()))); } /** @@ -568,9 +575,9 @@ class Repository */ public function push(string $remote, Branch $branch) : string { - $remote = escapeshellarg($remote); + $remote = \escapeshellarg($remote); - return implode("\n", $this->run('push --tags ' . $remote . ' ' . $branch->getName())); + return \implode("\n", $this->run('push --tags ' . $remote . ' ' . $branch->getName())); } /** @@ -587,7 +594,7 @@ class Repository { $remote = escapeshellarg($remote); - return implode("\n", $this->run('pull ' . $remote . ' ' . $branch->getName())); + return \implode("\n", $this->run('pull ' . $remote . ' ' . $branch->getName())); } /** @@ -613,7 +620,7 @@ class Repository */ public function getDescription() : string { - return \file_get_contents($this->getDirectoryPath() . '/description'); + return (string) \file_get_contents($this->getDirectoryPath() . '/description'); } /** @@ -657,18 +664,18 @@ class Repository return 0; } - $fh = fopen($path, 'r'); + $fh = \fopen($path, 'r'); if (!$fh) { return 0; } while (!feof($fh)) { - fgets($fh); + \fgets($fh); $loc++; } - fclose($fh); + \fclose($fh); } return $loc; @@ -700,7 +707,8 @@ class Repository foreach ($lines as $line) { \preg_match('/^[0-9]*/', $line, $matches); - $contributor = new Author(substr($line, strlen($matches[0]) + 1)); + $author = \substr($line, \strlen($matches[0]) + 1); + $contributor = new Author($author === false ? '' : $author); $contributor->setCommitCount($this->getCommitsCount($start, $end)[$contributor->getName()]); $addremove = $this->getAdditionsRemovalsByContributor($contributor, $start, $end); @@ -739,7 +747,7 @@ class Repository foreach ($lines as $line) { \preg_match('/^[0-9]*/', $line, $matches); - $commits[substr($line, strlen($matches[0]) + 1)] = (int) $matches[0]; + $commits[\substr($line, \strlen($matches[0]) + 1)] = (int) $matches[0]; } return $commits; @@ -768,7 +776,7 @@ class Repository $addremove = ['added' => 0, 'removed' => 0]; $lines = $this->run( - 'log --author=' . escapeshellarg($author->getName()) + 'log --author=' . \escapeshellarg($author->getName()) . ' --since="' . $start->format('Y-m-d') . '" --before="' . $end->format('Y-m-d') . '" --pretty=tformat: --numstat' @@ -793,7 +801,7 @@ class Repository */ public function getRemote() : string { - return implode("\n", $this->run('config --get remote.origin.url')); + return \implode("\n", $this->run('config --get remote.origin.url')); } /** @@ -820,7 +828,7 @@ class Repository if ($author === null) { $author = ''; } else { - $author = ' --author=' . escapeshellarg($author->getName()) . ''; + $author = ' --author=' . \escapeshellarg($author->getName()) . ''; } $lines = $this->run( @@ -865,7 +873,7 @@ class Repository \preg_match('/[0-9ABCDEFabcdef]{40}/', $lines[0], $matches); - if (!isset($matches[0]) || strlen($matches[0]) !== 40) { + if (!isset($matches[0]) || \strlen($matches[0]) !== 40) { throw new \Exception('Invalid commit id'); } @@ -874,8 +882,16 @@ class Repository } $author = \explode(':', $lines[1] ?? ''); - $author = \explode('<', trim($author[1] ?? '')); - $date = substr($lines[2] ?? '', 6); + if (count($author) < 2) { + $author = ['none', 'none']; + } else { + $author = \explode('<', trim($author[1] ?? '')); + } + + $date = \substr($lines[2] ?? '', 6); + if ($date === false) { + $date = 'now'; + } $commit = new Commit($matches[0]); $commit->setAuthor(new Author(trim($author[0] ?? ''), rtrim($author[1] ?? '', '>'))); @@ -913,7 +929,7 @@ class Repository \preg_match('/[0-9ABCDEFabcdef]{40}/', $lines[0], $matches); - if (!isset($matches[0]) || strlen($matches[0]) !== 40) { + if (!isset($matches[0]) || \strlen($matches[0]) !== 40) { throw new \Exception('Invalid commit id'); } diff --git a/Utils/IO/Csv/CsvDatabaseMapper.php b/Utils/IO/Csv/CsvDatabaseMapper.php deleted file mode 100644 index 26489fe94..000000000 --- a/Utils/IO/Csv/CsvDatabaseMapper.php +++ /dev/null @@ -1,90 +0,0 @@ -db = $db; - } - - public function addSource(string $source) - { - $this->sources[] = $source; - - $this->sources = array_unique($this->sources); - } - - public function setSources(array $sources) : void - { - $this->sources = $sources; - } - - public function autoIdentifyCsvSettings(bool $identify) - { - $this->autoIdentifyCsvSettings = $identify; - } - - public function setLineBuffer(int $buffer) : void - { - $this->lineBuffer = $buffer; - } - - public function insert() - { - foreach ($this->sources as $source) { - $file = fopen($source, 'r'); - $header = []; - $delimiter = $this->autoIdentifyCsvSettings ? $this->getFileDelimiter($file, 100) : $this->delimiter; - - if (feof($file) && ($line = fgetcsv($file, 0, $delimiter)) !== false) { - $header = $line; - } - - $query = new Builder($this->db); - $query->insert(...$header)->into(\str_replace(' ', '', explode($source, '.'))); - - while (feof($file)) { - $c = 0; - - while (($line = fgetcsv($file)) !== false && $c < $this->lineBuffer && feof($file)) { - $query->values($line); - $c++; - } - - $this->db->con->prepare($query->toSql())->execute(); - } - - fclose($file); - } - } -} diff --git a/Utils/IO/Csv/CsvSettings.php b/Utils/IO/Csv/CsvSettings.php index 0523273e4..f001235a9 100644 --- a/Utils/IO/Csv/CsvSettings.php +++ b/Utils/IO/Csv/CsvSettings.php @@ -25,9 +25,9 @@ class CsvSettings /** * Get csv file delimiter. * - * @param mixed $file File resource - * @param int $checkLines Lines to check for evaluation - * @param array $delimiters Potential delimiters + * @param mixed $file File resource + * @param int $checkLines Lines to check for evaluation + * @param string[] $delimiters Potential delimiters * * @return string * @@ -36,14 +36,23 @@ class CsvSettings public static function getFileDelimiter($file, int $checkLines = 2, array $delimiters = [',', '\t', ';', '|', ':']) : string { $results = []; + $i = 0; + $line = \fgets($file); - $i = 0; - while (($line = fgets($file)) !== false && $i < $checkLines) { + if ($line === false) { + return ';'; + } + + while ($line !== false && $i < $checkLines) { $i++; foreach ($delimiters as $delimiter) { $regExp = '/[' . $delimiter . ']/'; - $fields = preg_split($regExp, $line); + $fields = \preg_split($regExp, $line); + + if ($fields === false) { + return ';'; + } if (count($fields) > 1) { if (!empty($results[$delimiter])) { @@ -55,7 +64,7 @@ class CsvSettings } } - $results = array_keys($results, max($results)); + $results = \array_keys($results, max($results)); return $results[0]; } diff --git a/Utils/IO/Zip/Gz.php b/Utils/IO/Zip/Gz.php index 47e3ea97f..922a2ea6e 100644 --- a/Utils/IO/Zip/Gz.php +++ b/Utils/IO/Zip/Gz.php @@ -31,23 +31,25 @@ class Gz implements ArchiveInterface */ public static function pack($source, string $destination, bool $overwrite = true) : bool { - $destination = \str_replace('\\', '/', realpath($destination)); + $destination = \str_replace('\\', '/', $destination); if (!$overwrite && \file_exists($destination)) { return false; } - if (($gz = gzopen($destination, 'w')) === false) { + $gz = \gzopen($destination, 'w'); + $src = \fopen($source, 'r'); + if ($gz === false || $src === false) { return false; } - $src = fopen($source, 'r'); - while (!feof($src)) { - gzwrite($gz, fread($src, 4096)); + while (!\feof($src)) { + $read = \fread($src, 4096); + \gzwrite($gz, $read === false ? '' : $read); } - fclose($src); + \fclose($src); - return gzclose($gz); + return \gzclose($gz); } /** @@ -55,22 +57,23 @@ class Gz implements ArchiveInterface */ public static function unpack(string $source, string $destination) : bool { - $destination = \str_replace('\\', '/', realpath($destination)); - if (file_exists($destination)) { + $destination = \str_replace('\\', '/', $destination); + if (\file_exists($destination)) { return false; } - if (($gz = gzopen($source, 'w')) === false) { + $gz = \gzopen($source, 'w'); + $dest = \fopen($destination, 'w'); + if ($gz === false || $dest === false) { return false; } - $dest = fopen($destination, 'w'); - while (!gzeof($gz)) { - fwrite($dest, gzread($gz, 4096)); + while (!\gzeof($gz)) { + \fwrite($dest, \gzread($gz, 4096)); } - fclose($dest); + \fclose($dest); - return gzclose($gz); + return \gzclose($gz); } } diff --git a/Utils/IO/Zip/Tar.php b/Utils/IO/Zip/Tar.php index 498494215..71695911e 100644 --- a/Utils/IO/Zip/Tar.php +++ b/Utils/IO/Zip/Tar.php @@ -14,6 +14,8 @@ declare(strict_types=1); namespace phpOMS\Utils\IO\Zip; +use phpOMS\System\File\FileUtils; + /** * Zip class for handling zip files. * @@ -31,48 +33,57 @@ class Tar implements ArchiveInterface */ public static function pack($sources, string $destination, bool $overwrite = true) : bool { - $destination = \str_replace('\\', '/', realpath($destination)); + $destination = FileUtils::absolute(\str_replace('\\', '/', $destination)); if (!$overwrite && \file_exists($destination)) { return false; } + $tar = new \PharData($destination); + /** @var array $sources */ - foreach ($sources as $source) { - $source = \str_replace('\\', '/', realpath($source)); + foreach ($sources as $source => $relative) { + $source = \realpath($source); + + if ($source === false) { + continue; + } + + $source = \str_replace('\\', '/', $source); if (!\file_exists($source)) { continue; } - if (is_dir($source)) { - $files = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($source), - \RecursiveIteratorIterator::SELF_FIRST - ); + if (\is_dir($source)) { + $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source), \RecursiveIteratorIterator::SELF_FIRST); foreach ($files as $file) { $file = \str_replace('\\', '/', $file); /* Ignore . and .. */ - if (\in_array(mb_substr($file, mb_strrpos($file, '/') + 1), ['.', '..'])) { + if (($pos = \mb_strrpos($file, '/')) === false + || \in_array(\mb_substr($file, $pos + 1), ['.', '..']) + ) { continue; } - $file = realpath($file); + $absolute = \realpath($file); + $absolute = \str_replace('\\', '/', (string) $absolute); + $dir = \str_replace($source . '/', '', $relative . '/' . $absolute); - if (is_dir($file)) { - // todo: do work here - } elseif (is_file($file)) { - // todo: do work here + if (\is_dir($absolute)) { + $tar->addEmptyDir($dir . '/'); + } elseif (\is_file($absolute)) { + $tar->addFile($absolute, $dir); } } - } elseif (is_file($source)) { - // todo: do work here + } elseif (\is_file($source)) { + $tar->addFile($source, $relative); } } - fwrite($tar, pack('a1024', '')); + return true; } /** @@ -80,6 +91,16 @@ class Tar implements ArchiveInterface */ public static function unpack(string $source, string $destination) : bool { + if (!\file_exists($source)) { + return false; + } + $destination = \str_replace('\\', '/', $destination); + $destination = \rtrim($destination, '/'); + $tar = new \PharData($destination); + + $tar->extractTo($destination . '/'); + + return true; } } diff --git a/Utils/IO/Zip/Zip.php b/Utils/IO/Zip/Zip.php index 0060cc868..a330e48cb 100644 --- a/Utils/IO/Zip/Zip.php +++ b/Utils/IO/Zip/Zip.php @@ -47,34 +47,42 @@ class Zip implements ArchiveInterface /** @var array $sources */ foreach ($sources as $source => $relative) { - $source = \str_replace('\\', '/', realpath($source)); + $source = \realpath($source); + + if ($source === false) { + continue; + } + + $source = \str_replace('\\', '/', $source); if (!\file_exists($source)) { continue; } - if (is_dir($source)) { + if (\is_dir($source)) { $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source), \RecursiveIteratorIterator::SELF_FIRST); foreach ($files as $file) { $file = \str_replace('\\', '/', $file); /* Ignore . and .. */ - if (\in_array(mb_substr($file, mb_strrpos($file, '/') + 1), ['.', '..'])) { + if (($pos = \mb_strrpos($file, '/')) === false + || \in_array(\mb_substr($file, $pos + 1), ['.', '..']) + ) { continue; } - $absolute = realpath($file); - $absolute = \str_replace('\\', '/', $absolute); + $absolute = \realpath($file); + $absolute = \str_replace('\\', '/', (string) $absolute); $dir = \str_replace($source . '/', '', $relative . '/' . $absolute); - if (is_dir($absolute)) { + if (\is_dir($absolute)) { $zip->addEmptyDir($dir . '/'); - } elseif (is_file($absolute)) { + } elseif (\is_file($absolute)) { $zip->addFile($absolute, $dir); } } - } elseif (is_file($source)) { + } elseif (\is_file($source)) { $zip->addFile($source, $relative); } } @@ -92,7 +100,7 @@ class Zip implements ArchiveInterface } $destination = \str_replace('\\', '/', $destination); - $destination = rtrim($destination, '/'); + $destination = \rtrim($destination, '/'); $zip = new \ZipArchive(); if (!$zip->open($source)) { diff --git a/Utils/ImageUtils.php b/Utils/ImageUtils.php index 9cd030d7b..914a61690 100644 --- a/Utils/ImageUtils.php +++ b/Utils/ImageUtils.php @@ -51,6 +51,6 @@ final class ImageUtils $img = \str_replace('data:image/png;base64,', '', $img); $img = \str_replace(' ', '+', $img); - return base64_decode($img); + return (string) base64_decode($img); } } diff --git a/Utils/JsonBuilder.php b/Utils/JsonBuilder.php index f96d709bf..08a92fa21 100644 --- a/Utils/JsonBuilder.php +++ b/Utils/JsonBuilder.php @@ -80,7 +80,7 @@ final class JsonBuilder implements \Serializable, \JsonSerializable */ public function serialize() : string { - return \json_encode($this->json); + return (string) \json_encode($this->json); } /** diff --git a/Utils/Parser/Markdown/Markdown.php b/Utils/Parser/Markdown/Markdown.php index e50abfbd6..f81edd4de 100644 --- a/Utils/Parser/Markdown/Markdown.php +++ b/Utils/Parser/Markdown/Markdown.php @@ -15,6 +15,8 @@ declare(strict_types=1); namespace phpOMS\Utils\Parser\Markdown; +use phpOMS\Uri\UriFactory; + /** * Markdown parser class. * @@ -769,7 +771,7 @@ class Markdown } $data = [ - 'url' => $matches[2], + 'url' => UriFactory::build($matches[2]), 'title' => $matches[3] ?? null, ]; @@ -978,17 +980,17 @@ class Markdown $inline['position'] = $markerPosition; } - $unmarkedText = \substr($text, 0, $inline['position']); + $unmarkedText = (string) \substr($text, 0, $inline['position']); $markup .= self::unmarkedText($unmarkedText); $markup .= isset($inline['markup']) ? $inline['markup'] : self::element($inline['element']); - $text = \substr($text, $inline['position'] + $inline['extent']); + $text = (string) \substr($text, $inline['position'] + $inline['extent']); continue 2; } - $unmarkedText = \substr($text, 0, $markerPosition + 1); + $unmarkedText = (string) \substr($text, 0, $markerPosition + 1); $markup .= self::unmarkedText($unmarkedText); - $text = \substr($text, $markerPosition + 1); + $text = (string) \substr($text, $markerPosition + 1); } $markup .= self::unmarkedText($text); @@ -1049,7 +1051,7 @@ class Markdown 'name' => 'a', 'text' => $matches[1], 'attributes' => [ - 'href' => $url, + 'href' => UriFactory::build($url), ], ], ]; @@ -1140,7 +1142,7 @@ class Markdown 'element' => [ 'name' => 'img', 'attributes' => [ - 'src' => $link['element']['attributes']['href'], + 'src' => UriFactory::build($link['element']['attributes']['href']), 'alt' => $link['element']['text'], ], ], @@ -1183,13 +1185,13 @@ class Markdown $element['text'] = $matches[1]; $extent += \strlen($matches[0]); - $remainder = \substr($remainder, $extent); + $remainder = (string) \substr($remainder, $extent); if (\preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) { - $element['attributes']['href'] = $matches[1]; + $element['attributes']['href'] = UriFactory::build($matches[1]); if (isset($matches[2])) { - $element['attributes']['title'] = \substr($matches[2], 1, - 1); + $element['attributes']['title'] = (string) \substr($matches[2], 1, - 1); } $extent += \strlen($matches[0]); @@ -1209,7 +1211,7 @@ class Markdown $def = self::$definitionData['Reference'][$definition]; - $element['attributes']['href'] = $def['url']; + $element['attributes']['href'] = UriFactory::build($def['url']); $element['attributes']['title'] = $def['title']; } @@ -1302,7 +1304,7 @@ class Markdown 'name' => 'a', 'text' => $matches[0][0], 'attributes' => [ - 'href' => $matches[0][0], + 'href' => UriFactory::build($matches[0][0]), ], ], ]; @@ -1329,7 +1331,7 @@ class Markdown 'name' => 'a', 'text' => $matches[1], 'attributes' => [ - 'href' => $matches[1], + 'href' => UriFactory::build($matches[1]), ], ], ]; @@ -1363,7 +1365,7 @@ class Markdown */ protected static function element(array $element) : string { - $element = self::sanitizeElement($element); + $element = self::sanitizeAndBuildElement($element); $markup = '<' . $element['name']; if (isset($element['attributes'])) { @@ -1425,7 +1427,7 @@ class Markdown if (!\in_array('', $lines) && \substr($trimmedMarkup, 0, 3) === '

') { $markup = $trimmedMarkup; - $markup = \substr($markup, 3); + $markup = (string) \substr($markup, 3); $position = \strpos($markup, '

'); $markup = \substr_replace($markup, '', $position, 4); } @@ -1442,7 +1444,7 @@ class Markdown * * @since 1.0.0 */ - protected static function sanitizeElement(array $element) : array + protected static function sanitizeAndBuildElement(array $element) : array { $safeUrlNameToAtt = [ 'a' => 'href', @@ -1522,6 +1524,6 @@ class Markdown return false; } - return \strtolower(\substr($string, 0, $length)) === \strtolower($needle); + return \strtolower((string) \substr($string, 0, $length)) === \strtolower($needle); } } diff --git a/Utils/RnG/ArrayRandomize.php b/Utils/RnG/ArrayRandomize.php index 2037e7e9b..d22e7dba0 100644 --- a/Utils/RnG/ArrayRandomize.php +++ b/Utils/RnG/ArrayRandomize.php @@ -27,7 +27,7 @@ class ArrayRandomize /** * Yates array shuffler. * - * @param array $arr Array to randomize + * @param array $arr Array to randomize. Array must NOT be associative * * @return array * @@ -38,8 +38,8 @@ class ArrayRandomize $shuffled = []; while (!empty($arr)) { - $rnd = array_rand($arr); - $shuffled[] = $arr[$rnd]; + $rnd = (int) array_rand($arr); + $shuffled[] = $arr[$rnd] ?? null; array_splice($arr, $rnd, 1); } diff --git a/Utils/RnG/Text.php b/Utils/RnG/Text.php index 706ea8667..cf7cec1eb 100644 --- a/Utils/RnG/Text.php +++ b/Utils/RnG/Text.php @@ -117,14 +117,14 @@ class Text /** * Get a random string. * - * @param int $length Text length - * @param int $words Vocabulary + * @param int $length Text length + * @param array $words Vocabulary * * @return string * * @since 1.0.0 */ - public function generateText(int $length, $words = null) : string + public function generateText(int $length, array $words = null) : string { if ($length === 0) { return ''; @@ -135,8 +135,8 @@ class Text } $punctuation = $this->generatePunctuation($length); - $punctuationCount = array_count_values( - array_map( + $punctuationCount = \array_count_values( + \array_map( function ($item) { return $item[1]; }, @@ -163,20 +163,20 @@ class Text for ($i = 0; $i < $length + 1; ++$i) { $newSentence = false; - $lastChar = substr($text, -1); + $lastChar = \substr($text, -1); if ($lastChar === '.' || $lastChar === '!' || $lastChar === '?' || !$lastChar) { $newSentence = true; } - $word = $words[rand(0, $wordCount - 1)]; + $word = $words[rand(0, $wordCount - 1)] ?? ''; if ($newSentence) { - $word = ucfirst($word); + $word = \ucfirst($word); $sentenceCount++; /** @noinspection PhpUndefinedVariableInspection */ - if ($this->hasParagraphs && $sentenceCount === $paragraph[$paid]) { + if ($this->hasParagraphs) { $paid++; $text .= '

'; @@ -184,7 +184,7 @@ class Text } /** @noinspection PhpUndefinedVariableInspection */ - if ($this->hasFormatting && array_key_exists($i, $formatting)) { + if ($this->hasFormatting && isset($formatting[$i])) { $word = '<' . $formatting[$i] . '>' . $word . ''; } @@ -276,11 +276,11 @@ class Text * * @param int $length Amount of sentences * - * @return string + * @return array * * @since 1.0.0 */ - private function generateParagraph(int $length) : string + private function generateParagraph(int $length) : array { $minSentence = 3; $maxSentence = 10; diff --git a/Utils/StringCompare.php b/Utils/StringCompare.php index abbc4345f..6009214f4 100644 --- a/Utils/StringCompare.php +++ b/Utils/StringCompare.php @@ -98,15 +98,19 @@ final class StringCompare */ public static function valueWords(string $s1, string $s2) : int { - $words1 = preg_split('/[ _-]/', $s1); - $words2 = preg_split('/[ _-]/', $s2); + $words1 = \preg_split('/[ _-]/', $s1); + $words2 = \preg_split('/[ _-]/', $s2); $total = 0; + if ($words1 === false || $words2 === false) { + return PHP_INT_MAX; + } + foreach ($words1 as $word1) { - $best = strlen($s2); + $best = \strlen($s2); foreach ($words2 as $word2) { - $wordDist = levenshtein($word1, $word2); + $wordDist = \levenshtein($word1, $word2); if ($wordDist < $best) { $best = $wordDist; @@ -136,7 +140,7 @@ final class StringCompare */ public static function valuePhrase(string $s1, string $s2) : int { - return levenshtein($s1, $s2); + return \levenshtein($s1, $s2); } /** @@ -151,7 +155,7 @@ final class StringCompare */ public static function valueLength(string $s1, string $s2) : int { - return (int) abs(strlen($s1) - strlen($s2)); + return abs(\strlen($s1) - \strlen($s2)); } /** diff --git a/Utils/StringUtils.php b/Utils/StringUtils.php index 50c46f306..3ef63e202 100644 --- a/Utils/StringUtils.php +++ b/Utils/StringUtils.php @@ -50,8 +50,6 @@ final class StringUtils * @param array $needles Needles to check if any of them are part of the haystack * * @example StringUtils::contains('This string', ['This', 'test']); // true - * @example StringUtils::contains('This string', 'is st'); // true - * @example StringUtils::contains('This string', 'something'); // false * * @return bool The function returns true if any of the needles is part of the haystack, false otherwise. * @@ -77,8 +75,6 @@ final class StringUtils * @param array $needles Needles to check if any of them are part of the haystack * * @example StringUtils::mb_contains('This string', ['This', 'test']); // true - * @example StringUtils::mb_contains('This string', 'is st'); // true - * @example StringUtils::mb_contains('This string', 'something'); // false * * @return bool The function returns true if any of the needles is part of the haystack, false otherwise. * @@ -105,8 +101,6 @@ final class StringUtils * @param string|array $needles Needles to check if they are at the end of the haystack. * * @example StringUtils::endsWith('Test string', ['test1', 'string']); // true - * @example StringUtils::endsWith('Test string', 'string'); // true - * @example StringUtils::endsWith('Test string', 'String'); // false * * @return bool The function returns true if any of the needles is at the end of the haystack, false otherwise. * diff --git a/Utils/TaskSchedule/Cron.php b/Utils/TaskSchedule/Cron.php index 37171fee6..1bc6035c5 100644 --- a/Utils/TaskSchedule/Cron.php +++ b/Utils/TaskSchedule/Cron.php @@ -25,57 +25,83 @@ namespace phpOMS\Utils\TaskSchedule; */ class Cron extends SchedulerAbstract { - /** - * Run command - * - * @param string $cmd Command to run - * - * @return string - * - * @throws \Exception - * - * @since 1.0.0 + * {@inheritdoc} */ - private function run(string $cmd) : string + public function create(TaskAbstract $task) : void { - $cmd = 'cd ' . escapeshellarg(\dirname(self::$bin)) . ' && ' . basename(self::$bin) . ' ' . $cmd; - - $pipes = []; - $desc = [ - 1 => ['pipe', 'w'], - 2 => ['pipe', 'w'], - ]; - - $resource = proc_open($cmd, $desc, $pipes, __DIR__, null); - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); - - foreach ($pipes as $pipe) { - fclose($pipe); - } - - $status = trim((string) proc_close($resource)); - - if ($status == -1) { - throw new \Exception($stderr); - } - - return trim($stdout); + $this->run('-l > ' . __DIR__ . '/tmpcron.tmp'); + \file_put_contents(__DIR__ . '/tmpcron.tmp', $task->__toString() . "\n", FILE_APPEND); + $this->run(__DIR__ . '/tmpcron.tmp'); + unlink(__DIR__ . '/tmpcron.tmp'); } /** - * Normalize run result for easier parsing - * - * @param string $raw Raw command output - * - * @return string Normalized string for parsing - * - * @since 1.0.0 + * {@inheritdoc} */ - private function normalize(string $raw) : string + public function update(TaskAbstract $task) : void { - return \str_replace("\r\n", "\n", $raw); + $this->run('-l > ' . __DIR__ . '/tmpcron.tmp'); + + $new = ''; + $fp = \fopen(__DIR__ . '/tmpcron.tmp', 'r+'); + + if ($fp) { + $line = \fgets($fp); + while ($line !== false) { + if ($line[0] !== '#' && \stripos($line, 'name="' . $task->getId()) !== false) { + $new .= $task->__toString() . "\n"; + } else { + $new .= $line . "\n"; + } + + $line = \fgets($fp); + } + + \fclose($fp); + \file_put_contents(__DIR__ . '/tmpcron.tmp', $new); + } + + $this->run(__DIR__ . '/tmpcron.tmp'); + unlink(__DIR__ . '/tmpcron.tmp'); + } + + /** + * {@inheritdoc} + */ + public function delete(TaskAbstract $task) : void + { + $this->deleteByName($task->getId()); + } + + /** + * {@inheritdoc} + */ + public function deleteByName(string $name) : void + { + $this->run('-l > ' . __DIR__ . '/tmpcron.tmp'); + + $new = ''; + $fp = \fopen(__DIR__ . '/tmpcron.tmp', 'r+'); + + if ($fp) { + $line = \fgets($fp); + while ($line !== false) { + if ($line[0] !== '#' && \stripos($line, 'name="' . $name) !== false) { + $line = \fgets($fp); + continue; + } + + $new .= $line . "\n"; + $line = \fgets($fp); + } + + \fclose($fp); + \file_put_contents(__DIR__ . '/tmpcron.tmp', $new); + } + + $this->run(__DIR__ . '/tmpcron.tmp'); + unlink(__DIR__ . '/tmpcron.tmp'); } /** @@ -83,16 +109,36 @@ class Cron extends SchedulerAbstract */ public function getAll() : array { - $lines = \explode("\n", $this->normalize($this->run('-l'))); - unset($lines[0]); + $this->run('-l > ' . __DIR__ . '/tmpcron.tmp'); $jobs = []; - foreach ($lines as $line) { - if ($line !== '' && strrpos($line, '#', -strlen($line)) === false) { - $jobs[] = CronJob::createWith(str_getcsv($line, ' ')); + $fp = \fopen(__DIR__ . '/tmpcron.tmp', 'r+'); + + if ($fp) { + $line = \fgets($fp); + while ($line !== false) { + if ($line[0] !== '#') { + $elements = []; + $namePos = \stripos($line, 'name="'); + $nameEndPos = \stripos($line, '"', $namePos + 7); + + if ($namePos !== false && $nameEndPos !== false) { + $elements[] = \substr($line, $namePos + 6, $nameEndPos - 1); + } + + $elements = \array_merge($elements, \explode(' ', $line)); + $jobs[] = CronJob::createWith($elements); + } + + $line = \fgets($fp); } + + \fclose($fp); } + $this->run(__DIR__ . '/tmpcron.tmp'); + unlink(__DIR__ . '/tmpcron.tmp'); + return $jobs; } @@ -101,29 +147,30 @@ class Cron extends SchedulerAbstract */ public function getAllByName(string $name, bool $exact = true) : array { - $lines = \explode("\n", $this->normalize($this->run('-l'))); - unset($lines[0]); + $this->run('-l > ' . __DIR__ . '/tmpcron.tmp'); - if ($exact) { - $jobs = []; - foreach ($lines as $line) { - $csv = str_getcsv($line, ' '); + $jobs = []; + $fp = \fopen(__DIR__ . '/tmpcron.tmp', 'r+'); - if ($line !== '' && strrpos($line, '#', -strlen($line)) === false && $csv[5] === $name) { - $jobs[] = CronJob::createWith($csv); + if ($fp) { + $line = \fgets($fp); + while ($line !== false) { + if ($line[0] !== '#' && \stripos($line, '# name="' . $name) !== false) { + $elements = []; + $elements[] = $name; + $elements += \explode(' ', $line); + $jobs[] = CronJob::createWith($elements); } - } - } else { - $jobs = []; - foreach ($lines as $line) { - $csv = str_getcsv($line, ' '); - if ($line !== '' && strrpos($line, '#', -strlen($line)) === false && \stripos($csv[5], $name) !== false) { - $jobs[] = CronJob::createWith($csv); - } + $line = \fgets($fp); } + + \fclose($fp); } + $this->run(__DIR__ . '/tmpcron.tmp'); + unlink(__DIR__ . '/tmpcron.tmp'); + return $jobs; } } diff --git a/Utils/TaskSchedule/CronJob.php b/Utils/TaskSchedule/CronJob.php index 9df129406..be874ef24 100644 --- a/Utils/TaskSchedule/CronJob.php +++ b/Utils/TaskSchedule/CronJob.php @@ -14,6 +14,8 @@ declare(strict_types=1); namespace phpOMS\Utils\TaskSchedule; +use phpOMS\Validation\Base\DateTime; + /** * CronJob class. * @@ -24,15 +26,22 @@ namespace phpOMS\Utils\TaskSchedule; */ class CronJob extends TaskAbstract { + /** + * {@inheritdoc} + */ + public function __toString() : string + { + return $this->interval . ' ' . $this->command . ' # name="' . $this->id . '" ' . $this->comment; + } + /** * {@inheritdoc} */ public static function createWith(array $jobData) : TaskAbstract { - $job = new self($jobData[5], ''); + $interval = \array_splice($jobData, 1, 4); + $job = new self($jobData[0], $jobData[1], \implode(' ', $interval)); - $job->setRun($jobData[5]); - - return $job; + return $job; } } diff --git a/Utils/TaskSchedule/NullCronJob.php b/Utils/TaskSchedule/NullCronJob.php new file mode 100644 index 000000000..03928ac29 --- /dev/null +++ b/Utils/TaskSchedule/NullCronJob.php @@ -0,0 +1,27 @@ +id . ' ' . $this->interval . ' ' . $this->command; + } + /** * {@inheritdoc} */ public static function createWith(array $jobData) : TaskAbstract { - $job = new self($jobData[1], ''); + $job = new self($jobData[1], $jobData[8]); - $job->setRun($jobData[8]); - $job->setStatus($jobData[3]); + $job->setStatus($jobData[3]); - if (DateTime::isValid($jobData[2])) { - $job->setNextRunTime(new \DateTime($jobData[2])); - } + if (DateTime::isValid($jobData[2])) { + $job->setNextRunTime(new \DateTime($jobData[2])); + } - if (DateTime::isValid($jobData[5])) { - $job->setLastRuntime(new \DateTime($jobData[5])); - } + if (DateTime::isValid($jobData[5])) { + $job->setLastRuntime(new \DateTime($jobData[5])); + } - $job->setComment($jobData[10]); - $job->addResult($jobData[6]); + $job->setComment($jobData[10]); - return $job; + return $job; } } diff --git a/Utils/TaskSchedule/SchedulerAbstract.php b/Utils/TaskSchedule/SchedulerAbstract.php index cd7ed7436..86bd2b094 100644 --- a/Utils/TaskSchedule/SchedulerAbstract.php +++ b/Utils/TaskSchedule/SchedulerAbstract.php @@ -34,7 +34,7 @@ abstract class SchedulerAbstract * @var string * @since 1.0.0 */ - protected static $bin = ''; + private static $bin = ''; /** * Get git binary. @@ -61,11 +61,44 @@ abstract class SchedulerAbstract */ public static function setBin(string $path) : void { - if (realpath($path) === false) { + if (\realpath($path) === false) { throw new PathException($path); } - self::$bin = realpath($path); + self::$bin = \realpath($path); + } + + /** + * Gues git binary. + * + * @return bool + * + * @since 1.0.0 + */ + public static function guessBin() : bool + { + $paths = [ + 'c:/WINDOWS/system32/schtasks.exe', + 'd:/WINDOWS/system32/schtasks.exe', + 'e:/WINDOWS/system32/schtasks.exe', + 'f:/WINDOWS/system32/schtasks.exe', + '/usr/bin/crontab', + '/usr/local/bin/crontab', + '/usr/local/sbin/crontab', + '/usr/sbin/crontab', + '/bin/crontab', + '/sbin/crontab', + ]; + + foreach ($paths as $path) { + if (\file_exists($path)) { + self::setBin($path); + + return true; + } + } + + return false; } /** @@ -79,16 +112,62 @@ abstract class SchedulerAbstract public static function test() : bool { $pipes = []; - $resource = proc_open(escapeshellarg(self::$bin), [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes); + $resource = \proc_open(\escapeshellarg(self::$bin), [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes); - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); - - foreach ($pipes as $pipe) { - fclose($pipe); + if ($resource === false) { + return false; } - return trim(proc_close($resource)) !== 127; + $stdout = \stream_get_contents($pipes[1]); + $stderr = \stream_get_contents($pipes[2]); + + foreach ($pipes as $pipe) { + \fclose($pipe); + } + + return \proc_close($resource) !== 127; + } + + /** + * Run command + * + * @param string $cmd Command to run + * + * @return string + * + * @throws \Exception + * + * @since 1.0.0 + */ + protected function run(string $cmd) : string + { + $cmd = 'cd ' . \escapeshellarg(\dirname(self::$bin)) . ' && ' . \basename(self::$bin) . ' ' . $cmd; + + $pipes = []; + $desc = [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + + $resource = \proc_open($cmd, $desc, $pipes, __DIR__, null); + if ($resource === false) { + return ''; + } + + $stdout = \stream_get_contents($pipes[1]); + $stderr = \stream_get_contents($pipes[2]); + + foreach ($pipes as $pipe) { + \fclose($pipe); + } + + $status = \proc_close($resource); + + if ($status === -1) { + throw new \Exception($stderr); + } + + return trim($stdout); } /** @@ -100,8 +179,64 @@ abstract class SchedulerAbstract * * @since 1.0.0 */ - public function create(TaskAbstract $task) : void + abstract public function create(TaskAbstract $task) : void; + + /** + * Update task + * + * @param TaskAbstract $task Task to update + * + * @return void + * + * @since 1.0.0 + */ + abstract public function update(TaskAbstract $task) : void; + + /** + * Delete task by name + * + * @param string $name Task name + * + * @return void + * + * @since 1.0.0 + */ + abstract public function deleteByName(string $name) : void; + + /** + * Delete task + * + * @param TaskAbstract $task Task to delete + * + * @return void + * + * @since 1.0.0 + */ + abstract public function delete(TaskAbstract $task) : void; + + /** + * Normalize run result for easier parsing + * + * @param string $raw Raw command output + * + * @return string Normalized string for parsing + * + * @since 1.0.0 + */ + protected function normalize(string $raw) : string { - $this->run($task->getCommand()); + return \str_replace("\r\n", "\n", $raw); } + + /** + * Get all jobs/tasks by name + * + * @param string $name Name of the job + * @param bool $exact Name has to be exact + * + * @return array + * + * @since 1.0.0 + */ + abstract public function getAllByName(string $name, bool $exact = true) : array; } diff --git a/Utils/TaskSchedule/TaskAbstract.php b/Utils/TaskSchedule/TaskAbstract.php index 76df199b4..ca8607a93 100644 --- a/Utils/TaskSchedule/TaskAbstract.php +++ b/Utils/TaskSchedule/TaskAbstract.php @@ -41,12 +41,12 @@ abstract class TaskAbstract protected $command = ''; /** - * Command/script to run. + * Run interval * * @var string * @since 1.0.0 */ - protected $run = ''; + protected $interval = ''; /** * Status of the task @@ -91,10 +91,11 @@ abstract class TaskAbstract * * @since 1.0.0 */ - public function __construct(string $name, string $cmd = '') + public function __construct(string $name, string $cmd = '', string $interval = '') { $this->id = $name; $this->command = $cmd; + $this->interval = $interval; $this->lastRunTime = new \DateTime('1900-01-01'); $this->nextRunTime = new \DateTime('1900-01-01'); } @@ -111,6 +112,15 @@ abstract class TaskAbstract return $this->id; } + /** + * Stringify task for direct handling + * + * @return string + * + * @since 1.0.0 + */ + abstract public function __toString() : string; + /** * Get command to create the task * @@ -138,29 +148,29 @@ abstract class TaskAbstract } /** - * Get command/script to run + * Get interval to create the task * * @return string * * @since 1.0.0 */ - public function getRun() : string + public function getInterval() : string { - return $this->run; + return $this->interval; } /** - * Set script to run + * Set interval to create the task * - * @param string $run Command/script to run + * @param string $interval Interval * * @return void * * @since 1.0.0 */ - public function setRun(string $run) : void + public function setInterval(string $interval) : void { - $this->run = $run; + $this->interval = $interval; } /** diff --git a/Utils/TaskSchedule/TaskScheduler.php b/Utils/TaskSchedule/TaskScheduler.php index 6d76c6ba3..3f88a392e 100644 --- a/Utils/TaskSchedule/TaskScheduler.php +++ b/Utils/TaskSchedule/TaskScheduler.php @@ -26,55 +26,35 @@ namespace phpOMS\Utils\TaskSchedule; class TaskScheduler extends SchedulerAbstract { /** - * Run command - * - * @param string $cmd Command to run - * - * @return string - * - * @throws \Exception - * - * @since 1.0.0 + * {@inheritdoc} */ - private function run(string $cmd) : string + public function create(TaskAbstract $task) : void { - $cmd = 'cd ' . escapeshellarg(\dirname(self::$bin)) . ' && ' . basename(self::$bin) . ' ' . $cmd; - - $pipes = []; - $desc = [ - 1 => ['pipe', 'w'], - 2 => ['pipe', 'w'], - ]; - - $resource = proc_open($cmd, $desc, $pipes, __DIR__, null); - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); - - foreach ($pipes as $pipe) { - fclose($pipe); - } - - $status = trim((string) proc_close($resource)); - - if ($status == -1) { - throw new \Exception($stderr); - } - - return trim($stdout); + $this->run('/Create ' . $task->__toString()); } /** - * Normalize run result for easier parsing - * - * @param string $raw Raw command output - * - * @return string Normalized string for parsing - * - * @since 1.0.0 + * {@inheritdoc} */ - private function normalize(string $raw) : string + public function update(TaskAbstract $task) : void { - return \str_replace("\r\n", "\n", $raw); + $this->run('/Change ' . $task->__toString()); + } + + /** + * {@inheritdoc} + */ + public function deleteByName(string $name) : void + { + $this->run('/Delete /TN ' . $name); + } + + /** + * {@inheritdoc} + */ + public function delete(TaskAbstract $task) : void + { + $this->deleteByName($task->getId()); } /** @@ -87,7 +67,7 @@ class TaskScheduler extends SchedulerAbstract $jobs = []; foreach ($lines as $line) { - $jobs[] = Schedule::createWith(str_getcsv($line)); + $jobs[] = Schedule::createWith(\str_getcsv($line)); } return $jobs; @@ -99,12 +79,12 @@ class TaskScheduler extends SchedulerAbstract public function getAllByName(string $name, bool $exact = true) : array { if ($exact) { - $lines = \explode("\n", $this->normalize($this->run('/query /v /fo CSV /tn ' . escapeshellarg($name)))); + $lines = \explode("\n", $this->normalize($this->run('/query /v /fo CSV /tn ' . \escapeshellarg($name)))); unset($lines[0]); $jobs = []; foreach ($lines as $line) { - $jobs[] = Schedule::createWith(str_getcsv($line)); + $jobs[] = Schedule::createWith(\str_getcsv($line)); } } else { $lines = \explode("\n", $this->normalize($this->run('/query /v /fo CSV'))); @@ -112,9 +92,9 @@ class TaskScheduler extends SchedulerAbstract $jobs = []; foreach ($lines as $key => $line) { - $line = str_getcsv($line); + $line = \str_getcsv($line); - if (stripos($line[1], $name) !== false) { + if (\stripos($line[1], $name) !== false) { $jobs[] = Schedule::createWith($line); } } diff --git a/Utils/TestUtils.php b/Utils/TestUtils.php index 6f23b4a72..52ad9ff16 100644 --- a/Utils/TestUtils.php +++ b/Utils/TestUtils.php @@ -48,9 +48,9 @@ final class TestUtils * * @since 1.0.0 */ - public static function setMember(/* object */ $obj, string $name, $value) : bool + public static function setMember($obj, string $name, $value) : bool { - $reflectionClass = new \ReflectionClass(is_string($obj) ? $obj : get_class($obj)); + $reflectionClass = new \ReflectionClass(\is_string($obj) ? $obj : \get_class($obj)); if (!$reflectionClass->hasProperty($name)) { return false; @@ -62,7 +62,11 @@ final class TestUtils $reflectionProperty->setAccessible(true); } - $reflectionProperty->setValue($obj, $value); + if (\is_string($obj)) { + $reflectionProperty->setValue($value); + } elseif (\is_object($obj)) { + $reflectionProperty->setValue($obj, $value); + } if (!$accessible) { $reflectionProperty->setAccessible(false); @@ -83,7 +87,7 @@ final class TestUtils */ public static function getMember($obj, string $name) { - $reflectionClass = new \ReflectionClass(is_string($obj) ? $obj : get_class($obj)); + $reflectionClass = new \ReflectionClass(\is_string($obj) ? $obj : \get_class($obj)); if (!$reflectionClass->hasProperty($name)) { return null; @@ -95,7 +99,12 @@ final class TestUtils $reflectionProperty->setAccessible(true); } - $value = $reflectionProperty->getValue($obj); + $value = null; + if (\is_string($obj)) { + $value = $reflectionProperty->getValue(); + } elseif (\is_object($obj)) { + $value = $reflectionProperty->getValue($obj); + } if (!$accessible) { $reflectionProperty->setAccessible(false); diff --git a/Validation/Finance/Iban.php b/Validation/Finance/Iban.php index 00884dda2..f18fc508d 100644 --- a/Validation/Finance/Iban.php +++ b/Validation/Finance/Iban.php @@ -31,8 +31,14 @@ final class Iban extends ValidatorAbstract */ public static function isValid($value, array $constraints = null) : bool { - $value = \str_replace(' ', '', strtolower($value)); - $enumName = 'C_' . strtoupper(substr($value, 0, 2)); + $value = \str_replace(' ', '', \strtolower($value)); + + $temp = \substr($value, 0, 2); + if ($temp === false) { + return false; + } + + $enumName = 'C_' . \strtoupper($temp); if (!IbanEnum::isValidName($enumName)) { self::$error = IbanErrorType::INVALID_COUNTRY; @@ -42,7 +48,7 @@ final class Iban extends ValidatorAbstract $layout = \str_replace(' ', '', IbanEnum::getByName($enumName)); - if (strlen($value) !== strlen($layout)) { + if (\strlen($value) !== \strlen($layout)) { self::$error = IbanErrorType::INVALID_LENGTH; return false; @@ -81,12 +87,12 @@ final class Iban extends ValidatorAbstract */ private static function validateZeros(string $iban, string $layout) : bool { - if (strpos($layout, '0') === false) { + if (\strpos($layout, '0') === false) { return true; } $lastPos = 0; - while (($lastPos = strpos($layout, '0', $lastPos)) !== false) { + while (($lastPos = \strpos($layout, '0', $lastPos)) !== false) { if ($iban[$lastPos] !== '0') { return false; } @@ -109,13 +115,13 @@ final class Iban extends ValidatorAbstract */ private static function validateNumeric(string $iban, string $layout) : bool { - if (strpos($layout, 'n') === false) { + if (\strpos($layout, 'n') === false) { return true; } $lastPos = 0; - while (($lastPos = strpos($layout, 'n', $lastPos)) !== false) { - if (!is_numeric($iban[$lastPos])) { + while (($lastPos = \strpos($layout, 'n', $lastPos)) !== false) { + if (!\is_numeric($iban[$lastPos])) { return false; } @@ -139,18 +145,18 @@ final class Iban extends ValidatorAbstract $chars = ['a' => 10, 'b' => 11, 'c' => 12, 'd' => 13, 'e' => 14, 'f' => 15, 'g' => 16, 'h' => 17, 'i' => 18, 'j' => 19, 'k' => 20, 'l' => 21, 'm' => 22, 'n' => 23, 'o' => 24, 'p' => 25, 'q' => 26, 'r' => 27, 's' => 28, 't' => 29, 'u' => 30, 'v' => 31, 'w' => 32, 'x' => 33, 'y' => 34, 'z' => 35,]; - $moved = substr($iban, 4) . substr($iban, 0, 4); - $movedArray = str_split($moved); + $moved = \substr($iban, 4) . \substr($iban, 0, 4); + $movedArray = (array) \str_split($moved); $new = ''; foreach ($movedArray as $key => $value) { - if (!is_numeric($movedArray[$key])) { + if (!\is_numeric($movedArray[$key])) { $movedArray[$key] = $chars[$movedArray[$key]]; } $new .= $movedArray[$key]; } - return bcmod($new, '97') == 1; + return \bcmod($new, '97') == 1; } } diff --git a/Validation/Network/Email.php b/Validation/Network/Email.php index b6cfa2cb1..9cfdf0b8b 100644 --- a/Validation/Network/Email.php +++ b/Validation/Network/Email.php @@ -42,7 +42,7 @@ abstract class Email extends ValidatorAbstract */ public static function isValid($value, array $constraints = null) : bool { - if (filter_var($value, FILTER_VALIDATE_EMAIL) === false) { + if (\filter_var($value, FILTER_VALIDATE_EMAIL) === false) { self::$msg = 'Invalid Email by filter_var standards'; self::$error = 1; diff --git a/Validation/Validator.php b/Validation/Validator.php index 090156223..9c2c917f8 100644 --- a/Validation/Validator.php +++ b/Validation/Validator.php @@ -34,6 +34,8 @@ final class Validator extends ValidatorAbstract * @param array $constraints Constraints for validation * * @return bool + * + * @throws \Exception * * @since 1.0.0 */ @@ -44,14 +46,13 @@ final class Validator extends ValidatorAbstract } foreach ($constraints as $test => $settings) { - $callback = StringUtils::endsWith($test, 'Not') ? substr($test, 0, -3) : $test; + $callback = StringUtils::endsWith($test, 'Not') ? \substr($test, 0, -3) : (string) $test; - if (!empty($settings)) { - $valid = $callback($var, ...$settings); - } else { - $valid = $callback($var); + if (!\is_callable($callback)) { + throw new \Exception(); } + $valid = !empty($settings) ? $callback($var, ...$settings) : $callback($var); $valid = (StringUtils::endsWith($test, 'Not') ? !$valid : $valid); if (!$valid) { diff --git a/Views/View.php b/Views/View.php index 57b663d57..466fcfa06 100644 --- a/Views/View.php +++ b/Views/View.php @@ -177,25 +177,33 @@ class View extends ViewAbstract if ($module === null) { $match = '/Modules/'; - if (($start = strripos($this->template, $match)) === false) { + if (($start = \strripos($this->template, $match)) === false) { throw new InvalidModuleException($module ?? ''); } - $start = $start + strlen($match); - $end = strpos($this->template, '/', $start); - $module = substr($this->template, $start, $end - $start); + $start = $start + \strlen($match); + $end = \strpos($this->template, '/', $start); + $module = \substr($this->template, $start, $end - $start); + } + + if ($module === false) { + $module = '0'; } if ($theme === null) { $match = '/Theme/'; - if (($start = strripos($this->template, $match)) === false) { + if (($start = \strripos($this->template, $match)) === false) { throw new InvalidThemeException($theme ?? ''); } - $start = $start + strlen($match); - $end = strpos($this->template, '/', $start); - $theme = substr($this->template, $start, $end - $start); + $start = $start + \strlen($match); + $end = \strpos($this->template, '/', $start); + $theme = \substr($this->template, $start, $end - $start); + } + + if ($theme === false) { + $theme = '0'; } return $this->app->l11nManager->getText($this->l11n->getLanguage(), $module, $theme, $translation); diff --git a/Views/ViewAbstract.php b/Views/ViewAbstract.php index 8861a24ab..53a817794 100644 --- a/Views/ViewAbstract.php +++ b/Views/ViewAbstract.php @@ -203,7 +203,7 @@ abstract class ViewAbstract implements \Serializable public function serialize() : string { if (empty($this->template)) { - return \json_encode($this->toArray()); + return (string) \json_encode($this->toArray()); } return $this->render(); @@ -253,10 +253,10 @@ abstract class ViewAbstract implements \Serializable ob_start(); /** @noinspection PhpIncludeInspection */ $includeData = include $path; - $ob = ob_get_clean(); + $ob = (string) ob_get_clean(); if (is_array($includeData)) { - return \json_encode($includeData); + return (string) \json_encode($includeData); } } catch (\Throwable $e) { $ob = ''; diff --git a/composer.json b/composer.json index 3190c2f28..9a403e873 100644 --- a/composer.json +++ b/composer.json @@ -7,18 +7,11 @@ "email": "spl1nes.com@googlemail.com" } ], - "require": { - "php": "^7.0" - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/Orange-Management/phpOMS.git" - } - ], - "autoload": { - "psr-0" : { - "phpOMS" : "" - } + "require-dev": { + "phpunit/phpunit": "~6.4", + "squizlabs/php_codesniffer": "~3.2", + "phpmd/phpmd": "~2.6", + "phpstan/phpstan": "~0.10.1", + "codeclimate/php-test-reporter": "^0.4.4" } -} \ No newline at end of file +} diff --git a/tests/Asset/AssetManagerTest.php b/tests/Asset/AssetManagerTest.php index 6e379a259..30b28e96f 100644 --- a/tests/Asset/AssetManagerTest.php +++ b/tests/Asset/AssetManagerTest.php @@ -64,14 +64,12 @@ class AssetManagerTest extends \PHPUnit\Framework\TestCase self::assertEquals(2, $manager->count()); /* Test remove */ - $rem = $manager->remove('myAsset'); - self::assertTrue($rem); + self::assertTrue($manager->remove('myAsset')); self::assertEquals(1, $manager->count()); self::assertNull($manager->get('myAsset')); - $rem = $manager->remove('myAsset'); - self::assertFalse($rem); + self::assertFalse($manager->remove('myAsset')); self::assertEquals(1, $manager->count()); } diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php index 3b0892a21..186173f1e 100644 --- a/tests/Bootstrap.php +++ b/tests/Bootstrap.php @@ -2,14 +2,108 @@ ini_set('memory_limit', '2048M'); -require_once __DIR__ . '/../../vendor/autoload.php'; -require_once __DIR__ . '/Autoloader.php'; -$CONFIG = require_once __DIR__ . '/../../config.php'; +if (file_exists('vendor/autoload.php')) { + include_once 'vendor/autoload.php'; +} elseif (file_exists('../../vendor/autoload.php')) { + include_once '../../vendor/autoload.php'; +} + +require_once __DIR__ . '/../Autoloader.php'; use phpOMS\DataStorage\Session\HttpSession; use phpOMS\DataStorage\Database\DatabasePool; use phpOMS\DataStorage\Database\DataMapperAbstract; +$CONFIG = [ + 'db' => [ + 'core' => [ + 'masters' => [ + 'admin' => [ + 'db' => 'mysql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '3306', /* db host port */ + 'login' => 'root', /* db login name */ + 'password' => '', /* db login password */ + 'database' => 'oms', /* db name */ + 'prefix' => 'oms_', /* db table prefix */ + 'weight' => 1000, /* db table prefix */ + ], + 'insert' => [ + 'db' => 'mysql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '3306', /* db host port */ + 'login' => 'root', /* db login name */ + 'password' => '', /* db login password */ + 'database' => 'oms', /* db name */ + 'prefix' => 'oms_', /* db table prefix */ + 'weight' => 1000, /* db table prefix */ + ], + 'select' => [ + 'db' => 'mysql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '3306', /* db host port */ + 'login' => 'root', /* db login name */ + 'password' => '', /* db login password */ + 'database' => 'oms', /* db name */ + 'prefix' => 'oms_', /* db table prefix */ + 'weight' => 1000, /* db table prefix */ + ], + 'update' => [ + 'db' => 'mysql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '3306', /* db host port */ + 'login' => 'root', /* db login name */ + 'password' => '', /* db login password */ + 'database' => 'oms', /* db name */ + 'prefix' => 'oms_', /* db table prefix */ + 'weight' => 1000, /* db table prefix */ + ], + 'delete' => [ + 'db' => 'mysql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '3306', /* db host port */ + 'login' => 'root', /* db login name */ + 'password' => '', /* db login password */ + 'database' => 'oms', /* db name */ + 'prefix' => 'oms_', /* db table prefix */ + 'weight' => 1000, /* db table prefix */ + ], + 'schema' => [ + 'db' => 'mysql', /* db type */ + 'host' => '127.0.0.1', /* db host address */ + 'port' => '3306', /* db host port */ + 'login' => 'root', /* db login name */ + 'password' => '', /* db login password */ + 'database' => 'oms', /* db name */ + 'prefix' => 'oms_', /* db table prefix */ + 'weight' => 1000, /* db table prefix */ + ], + ], + ], + ], + 'log' => [ + 'file' => [ + 'path' => __DIR__ . '/Logs', + ], + ], + 'page' => [ + 'root' => '/', + 'https' => false, + ], + 'socket' => [ + 'master' => [ + 'host' => '127.0.0.1', + 'limit' => 300, + 'port' => 4310, + ], + ], + 'language' => [ + 'en', + ], + 'apis' => [ + ] +]; + // Reset database $db = new \PDO($CONFIG['db']['core']['masters']['admin']['db'] . ':host=' . $CONFIG['db']['core']['masters']['admin']['host'], diff --git a/tests/Console/CommandManagerTest.php b/tests/Console/CommandManagerTest.php deleted file mode 100644 index 60e60ba76..000000000 --- a/tests/Console/CommandManagerTest.php +++ /dev/null @@ -1,26 +0,0 @@ -get('key')); - self::assertGreaterThan(0, strlen($session->getSID())); self::assertFalse(HttpSession::isLocked()); } diff --git a/tests/Event/EventManagerTest.php b/tests/Event/EventManagerTest.php index 8fc996704..70c5dfd3d 100644 --- a/tests/Event/EventManagerTest.php +++ b/tests/Event/EventManagerTest.php @@ -32,6 +32,7 @@ class EventManagerTest extends \PHPUnit\Framework\TestCase $event = new EventManager(); self::assertEquals(0, $event->count()); + self::assertFalse($event->trigger('invalid')); } public function testBase() @@ -41,15 +42,12 @@ class EventManagerTest extends \PHPUnit\Framework\TestCase self::assertTrue($event->attach('group', function() { return true; }, false, false)); self::assertFalse($event->attach('group', function() { return true; }, false, false)); self::assertEquals(1, $event->count()); - - self::assertTrue($event->detach('group')); - self::assertFalse($event->trigger('group')); - self::assertEquals(0, $event->count()); } public function testReset() { $event = new EventManager(); + self::assertTrue($event->attach('group', function() { return true; }, false, true)); $event->addGroup('group', 'id1'); $event->addGroup('group', 'id2'); @@ -58,17 +56,17 @@ class EventManagerTest extends \PHPUnit\Framework\TestCase self::assertTrue($event->trigger('group', 'id2')); self::assertFalse($event->trigger('group', 'id2')); self::assertEquals(1, $event->count()); - - self::assertTrue($event->detach('group')); } public function testDetach() { $event = new EventManager(); + self::assertTrue($event->attach('group', function() { return true; }, false, true)); $event->addGroup('group', 'id1'); $event->addGroup('group', 'id2'); + self::assertEquals(1, $event->count()); self::assertTrue($event->detach('group')); self::assertEquals(0, $event->count()); self::assertFalse($event->trigger('group')); @@ -77,6 +75,7 @@ class EventManagerTest extends \PHPUnit\Framework\TestCase public function testRemove() { $event = new EventManager(); + self::assertTrue($event->attach('group1', function() { return true; }, true, false)); self::assertTrue($event->attach('group2', function() { return true; }, true, false)); self::assertEquals(2, $event->count()); diff --git a/tests/ExtensionTest.php b/tests/ExtensionTest.php index 8b97fa463..3fd423ae5 100644 --- a/tests/ExtensionTest.php +++ b/tests/ExtensionTest.php @@ -15,11 +15,28 @@ namespace phpOMS\tests; class ExtensionTest extends \PHPUnit\Framework\TestCase { - public function testExtension() + public function testExtensionMbstring() { self::assertTrue(extension_loaded('mbstring')); + } + + public function testExtensionCurl() + { self::assertTrue(extension_loaded('curl')); + } + + public function testExtensionImap() + { self::assertTrue(extension_loaded('imap')); + } + + public function testExtensionPdo() + { + self::assertTrue(extension_loaded('pdo')); + } + + public function testExtensionGD() + { self::assertTrue(extension_loaded('gd') || extension_loaded('gd2')); } } diff --git a/tests/Localization/LocalizationTest.php b/tests/Localization/LocalizationTest.php index 305c488ce..60739e5a2 100644 --- a/tests/Localization/LocalizationTest.php +++ b/tests/Localization/LocalizationTest.php @@ -16,6 +16,7 @@ namespace phpOMS\tests\Localization; use phpOMS\Localization\ISO3166TwoEnum; use phpOMS\Localization\ISO4217Enum; use phpOMS\Localization\ISO639x1Enum; +use phpOMS\Localization\ISO4217CharEnum; use phpOMS\Localization\L11nManager; use phpOMS\Localization\Localization; use phpOMS\Localization\TimeZoneEnumArray; @@ -55,7 +56,7 @@ class LocalizationTest extends \PHPUnit\Framework\TestCase self::assertTrue(ISO4217Enum::isValidValue($localization->getCurrency())); self::assertEquals('.', $localization->getDecimal()); self::assertEquals(',', $localization->getThousands()); - self::assertEquals('Y-m-d H:i:s', $localization->getDatetime()); + self::assertEquals([], $localization->getDatetime()); self::assertEquals([], $localization->getSpeed()); self::assertEquals([], $localization->getWeight()); @@ -113,11 +114,11 @@ class LocalizationTest extends \PHPUnit\Framework\TestCase $localization->setLanguage(ISO639x1Enum::_DE); self::assertEquals(ISO639x1Enum::_DE, $localization->getLanguage()); - $localization->setCurrency(ISO4217Enum::_EUR); - self::assertEquals(ISO4217Enum::_EUR, $localization->getCurrency()); + $localization->setCurrency(ISO4217CharEnum::_EUR); + self::assertEquals(ISO4217CharEnum::_EUR, $localization->getCurrency()); - $localization->setDatetime('Y-m-d H:i:s'); - self::assertEquals('Y-m-d H:i:s', $localization->getDatetime()); + $localization->setDatetime(['Y-m-d H:i:s']); + self::assertEquals(['Y-m-d H:i:s'], $localization->getDatetime()); $localization->setDecimal(','); self::assertEquals(',', $localization->getDecimal()); diff --git a/tests/Log/FileLoggerTest.php b/tests/Log/FileLoggerTest.php index 8ee028563..a9e07e858 100644 --- a/tests/Log/FileLoggerTest.php +++ b/tests/Log/FileLoggerTest.php @@ -55,7 +55,7 @@ class FileLoggerTest extends \PHPUnit\Framework\TestCase unlink(__DIR__ . '/test.log'); } - $log = new FileLogger(__DIR__ . '/test.log', true); + $log = new FileLogger(__DIR__ . '/test.log', false); $log->emergency(FileLogger::MSG_FULL, [ 'message' => 'msg', @@ -121,17 +121,17 @@ class FileLoggerTest extends \PHPUnit\Framework\TestCase self::assertEquals(2, $log->countLogs()['debug'] ?? 0); self::assertEquals(['0.0.0.0' => 9], $log->getHighestPerpetrator()); - self::assertEquals([5, 6, 7, 8, 9], array_keys($log->get(5, 1))); + self::assertEquals([6, 7, 8, 9, 10], array_keys($log->get(5, 1))); self::assertEquals('alert', $log->getByLine(2)['level']); ob_start(); - $log->console(FileLogger::MSG_FULL, false, [ + $log->console(FileLogger::MSG_FULL, true, [ 'message' => 'msg', 'line' => 11, 'file' => FileLoggerTest::class, ]); $ob = ob_get_clean(); - self::assertEquals(2, $log->countLogs()['info'] ?? 0); + self::assertEquals(1, $log->countLogs()['info'] ?? 0); self::assertTrue(stripos($ob, 'msg;') !== false); ob_start(); @@ -165,7 +165,7 @@ class FileLoggerTest extends \PHPUnit\Framework\TestCase { self::assertTrue(FileLogger::startTimeLog('test')); self::assertFalse(FileLogger::startTimeLog('test')); - self::assertGreaterThan(0, FileLogger::endTimeLog('test')); + self::assertGreaterThan(0.0, FileLogger::endTimeLog('test')); } public static function tearDownAfterClass() diff --git a/tests/Math/Matrix/CholeskyDecompositionTest.php b/tests/Math/Matrix/CholeskyDecompositionTest.php index 8d433f07c..ee9773f8c 100644 --- a/tests/Math/Matrix/CholeskyDecompositionTest.php +++ b/tests/Math/Matrix/CholeskyDecompositionTest.php @@ -36,11 +36,23 @@ class CholeskyDecompositionTest extends \PHPUnit\Framework\TestCase [-1, 1, 3], ], $cholesky->getL()->toArray(), '', 0.2); + self::assertTrue($cholesky->isSpd()); + } + + public function testSolve() + { + $A = new Matrix(); + $A->setMatrix([ + [25, 15, -5], + [15, 17, 0], + [-5, 0, 11], + ]); + + $cholesky = new CholeskyDecomposition($A); + $vec = new Vector(); $vec->setMatrix([[40], [49], [28]]); self::assertEquals([[1], [2], [3]], $cholesky->solve($vec)->toArray(), '', 0.2); - - self::assertTrue($cholesky->isSpd()); } /** diff --git a/tests/Math/Matrix/QRDecompositionTest.php b/tests/Math/Matrix/QRDecompositionTest.php new file mode 100644 index 000000000..7c473c25f --- /dev/null +++ b/tests/Math/Matrix/QRDecompositionTest.php @@ -0,0 +1,69 @@ +setMatrix([ + [12, -51, 4], + [6, 167, -68], + [-4, 24, -41], + ]); + + $QR = new QRDecomposition($A); + + self::assertTrue($QR->isFullRank()); + + self::assertEquals([ + [12, -69, -58 / 5], + [6, 158, 6 / 5], + [-4, 30, -33], + ], $QR->getH()->toArray(), '', 0.2); + + self::assertEquals([ + [6 / 7, -69 / 175, -58 / 175], + [3 / 7, 158 / 175, 6 / 175], + [-2 / 7, 6 / 35, -33 / 35], + ], $QR->getQ()->toArray(), '', 0.2); + + self::assertEquals([ + [14, 21, -14], + [0, 175, -70], + [0, 0, 35], + ], $QR->getR()->toArray(), '', 0.2); + }*/ + + public function testSolve() + { + $A = new Matrix(); + $A->setMatrix([ + [25, 15, -5], + [15, 17, 0], + [-5, 0, 11], + ]); + + $QR = new QRDecomposition($A); + + $vec = new Vector(); + $vec->setMatrix([[40], [49], [28]]); + self::assertEquals([[1], [2], [3]], $QR->solve($vec)->toArray(), '', 0.2); + } +} diff --git a/tests/Math/Number/ComplexTest.php b/tests/Math/Number/ComplexTest.php index 15d2dc002..fa3b584fe 100644 --- a/tests/Math/Number/ComplexTest.php +++ b/tests/Math/Number/ComplexTest.php @@ -32,37 +32,75 @@ class ComplexTest extends \PHPUnit\Framework\TestCase self::assertEquals(2, $cpl->im()); } - public function testBasics() + public function testAdd() { $cpl1 = new Complex(2, 3); $cpl2 = new Complex(3, 4); self::assertEquals('5.00 + 7.00i', $cpl1->add($cpl2)->render()); self::assertEquals('6.00 + 3.00i', $cpl1->add(4)->render()); + } + + public function testSub() + { + $cpl1 = new Complex(2, 3); + $cpl2 = new Complex(3, 4); self::assertEquals('-1.00 - 1.00i', $cpl1->sub($cpl2)->render()); self::assertEquals('-2.00 + 3.00i', $cpl1->sub(4)->render()); + } + + public function testMult() + { + $cpl1 = new Complex(2, 3); + $cpl2 = new Complex(3, 4); self::assertEquals('-6.00 + 17.00i', $cpl1->mult($cpl2)->render()); self::assertEquals('8.00 + 12.00i', $cpl1->mult(4)->render()); + } + + public function testDiv() + { + $cpl1 = new Complex(2, 3); + $cpl2 = new Complex(3, 4); self::assertEquals('0.72 + 0.04i', $cpl1->div($cpl2)->render(2)); self::assertEquals('0.50 + 0.75i', $cpl1->div(4)->render(2)); } - public function testSpecial() + public function testConjugate() { $cpl = new Complex(4, 3); self::assertEquals('4 - 3i', $cpl->conjugate()->render(0)); + } + + public function testReciprocal() + { + $cpl = new Complex(4, 3); + self::assertEquals('0.16 - 0.12i', $cpl->reciprocal()->render(2)); + } + + public function testPower() + { + $cpl = new Complex(4, 3); self::assertEquals('7.00 + 24.00i', $cpl->square()->render()); self::assertEquals('7.00 + 24.00i', $cpl->pow(2)->render()); self::assertEquals('-44.00 + 117.00i', $cpl->pow(3)->render()); + } + + public function testAbs() + { + $cpl = new Complex(4, 3); self::assertEquals(5, $cpl->abs(), '', 0.01); + } + public function testSqrt() + { + $cpl = new Complex(4, 3); self::assertEquals('2.12 + 0.71i', $cpl->sqrt()->render()); $cpl2 = new Complex(-1, 3); diff --git a/tests/Math/Number/IntegerTest.php b/tests/Math/Number/IntegerTest.php index 5d7d02a62..707647462 100644 --- a/tests/Math/Number/IntegerTest.php +++ b/tests/Math/Number/IntegerTest.php @@ -17,16 +17,22 @@ use phpOMS\Math\Number\Integer; class IntegerTest extends \PHPUnit\Framework\TestCase { - public function testInteger() + public function testIsInteger() { self::assertTrue(Integer::isInteger(4)); self::assertFalse(Integer::isInteger(1.0)); self::assertFalse(Integer::isInteger('3')); + } + public function testFactorization() + { self::assertArraySubset([2, 2, 5, 5], Integer::trialFactorization(100)); self::assertArraySubset([2], Integer::trialFactorization(2)); self::assertEquals([], Integer::trialFactorization(1)); + } + public function testOther() + { self::assertEquals(101, Integer::pollardsRho(10403, 2, 1, 2, 2)); self::assertEquals([59, 101], Integer::fermatFactor(5959)); diff --git a/tests/Math/Number/NaturalTest.php b/tests/Math/Number/NaturalTest.php index 3be1de107..1b1606922 100644 --- a/tests/Math/Number/NaturalTest.php +++ b/tests/Math/Number/NaturalTest.php @@ -17,7 +17,7 @@ use phpOMS\Math\Number\Natural; class NaturalTest extends \PHPUnit\Framework\TestCase { - public function testNatural() + public function testIsNatural() { self::assertTrue(Natural::isNatural(1235)); self::assertTrue(Natural::isNatural(0)); diff --git a/tests/Math/Number/NumbersTest.php b/tests/Math/Number/NumbersTest.php index a2ec0a445..e8a4c0945 100644 --- a/tests/Math/Number/NumbersTest.php +++ b/tests/Math/Number/NumbersTest.php @@ -37,7 +37,10 @@ class NumbersTest extends \PHPUnit\Framework\TestCase self::assertTrue(Numbers::isSquare(81)); self::assertTrue(Numbers::isSquare(6561)); self::assertFalse(Numbers::isSquare(5545348)); + } + public function testZeroCounting() + { self::assertEquals(3, Numbers::countTrailingZeros(1000)); self::assertEquals(5, Numbers::countTrailingZeros(12300000)); } diff --git a/tests/Math/Parser/EvaluatorTest.php b/tests/Math/Parser/EvaluatorTest.php index a3b4729f5..6adfadd34 100644 --- a/tests/Math/Parser/EvaluatorTest.php +++ b/tests/Math/Parser/EvaluatorTest.php @@ -17,8 +17,11 @@ use phpOMS\Math\Parser\Evaluator; class EvaluatorTest extends \PHPUnit\Framework\TestCase { - public function testPlaceholder() + public function testBasicEvaluation() { - self::markTestIncomplete(); + self::assertEquals(4.5, Evaluator::evaluate('3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3 + 1.5'), '', 2); + self::assertEquals(4.5, Evaluator::evaluate('3+4*2/(1-5)^2^3+1.5'), '', 2); + self::assertEquals(null, Evaluator::evaluate('invalid')); + self::assertEquals(null, Evaluator::evaluate('3+4*2/(1-5^2^3+1.5')); } } diff --git a/tests/Module/ModuleFactoryTest.php b/tests/Module/ModuleFactoryTest.php index 960c45d8f..9436b5264 100644 --- a/tests/Module/ModuleFactoryTest.php +++ b/tests/Module/ModuleFactoryTest.php @@ -17,11 +17,17 @@ require_once __DIR__ . '/../Autoloader.php'; use phpOMS\ApplicationAbstract; use phpOMS\Module\ModuleFactory; +use phpOMS\Module\NullModule; class ModuleFactoryTest extends \PHPUnit\Framework\TestCase { public function testFactory() { - self::assertInstanceOf(\Modules\Admin\Controller::class, ModuleFactory::getInstance('Admin', new class extends ApplicationAbstract {})); + $instance = NullModule::class; + if (\file_exists(__DIR__ . '/../../../Modules')) { + $instance = \Modules\Admin\Controller::class; + } + + self::assertInstanceOf($instance, ModuleFactory::getInstance('Admin', new class extends ApplicationAbstract {})); } } diff --git a/tests/Router/RouterTest.php b/tests/Router/RouterTest.php index 5541dfcdd..ab2f41333 100644 --- a/tests/Router/RouterTest.php +++ b/tests/Router/RouterTest.php @@ -75,13 +75,4 @@ class RouterTest extends \PHPUnit\Framework\TestCase $router->route('http://test.com/backends/admin/settings/general/something?test', RouteVerb::GET) ); } - - /** - * @expectedException \InvalidArgumentException - */ - public function testInvalidRequestType() - { - $router = new Router(); - $router->route([]); - } } diff --git a/tests/Uri/UriFactoryTest.php b/tests/Uri/UriFactoryTest.php index fcf98da43..b45a44514 100644 --- a/tests/Uri/UriFactoryTest.php +++ b/tests/Uri/UriFactoryTest.php @@ -66,7 +66,7 @@ class UriFactoryTest extends \PHPUnit\Framework\TestCase public function testBuilder() { - $uri = 'www.test-uri.com?id={@ID}&test={.mTest}&two={/path}&hash={#hash}&none=#none&found={/not}&v={/valid2}'; + $uri = 'www.test-uri.com?id={@ID}&test={.mTest}&two={/path}&hash={#hash}&none=#none&found={/not}?v={/valid2}'; $vars = [ '@ID' => 1, diff --git a/tests/Utils/Converter/NumericTest.php b/tests/Utils/Converter/NumericTest.php index ffee92f23..ef30364e2 100644 --- a/tests/Utils/Converter/NumericTest.php +++ b/tests/Utils/Converter/NumericTest.php @@ -28,6 +28,17 @@ class NumericTest extends \PHPUnit\Framework\TestCase self::assertEquals('XI', Numeric::arabicToRoman(11)); } + public function testAlphaNumeric() + { + self::assertEquals(0, Numeric::alphaToNumeric('A')); + self::assertEquals(1, Numeric::alphaToNumeric('B')); + self::assertEquals(53, Numeric::alphaToNumeric('BB')); + + self::assertEquals('A', Numeric::numericToAlpha(0)); + self::assertEquals('B', Numeric::numericToAlpha(1)); + self::assertEquals('BB', Numeric::numericToAlpha(53)); + } + public function testBase() { self::assertEquals('443', Numeric::convertBase('123', '0123456789', '01234')); diff --git a/tests/Utils/Git/CommitTest.php b/tests/Utils/Git/CommitTest.php index 0adc742de..689531b6a 100644 --- a/tests/Utils/Git/CommitTest.php +++ b/tests/Utils/Git/CommitTest.php @@ -34,7 +34,7 @@ class CommitTest extends \PHPUnit\Framework\TestCase self::assertInstanceOf('\DateTime', $commit->getDate()); } - public function testGetSet() + public function testAddRemoveFile() { $commit = new Commit(); @@ -51,21 +51,51 @@ class CommitTest extends \PHPUnit\Framework\TestCase self::assertEquals([ '/some/file/path2' => [] ], $commit->getFiles()); + } + + public function testMessage() + { + $commit = new Commit(); $commit->setMessage('My Message'); self::assertEquals('My Message', $commit->getMessage()); + } + + public function testAuthor() + { + $commit = new Commit(); $commit->setAuthor(new Author('Orange')); self::assertEquals('Orange', $commit->getAuthor()->getName()); + } + + public function testBranch() + { + $commit = new Commit(); $commit->setBranch(new Branch('develop')); self::assertEquals('develop', $commit->getBranch()->getName()); + } + + public function testTag() + { + $commit = new Commit(); $commit->setTag(new Tag('1.0.0')); self::assertEquals('1.0.0', $commit->getTag()->getName()); + } + + public function testDate() + { + $commit = new Commit(); $commit->setDate($date = new \DateTime('now')); self::assertEquals($date->format('Y-m-d'), $commit->getDate()->format('Y-m-d')); + } + + public function testRepository() + { + $commit = new Commit(); $commit->setRepository(new Repository(realpath(__DIR__ . '/../../../'))); self::assertEquals(realpath(__DIR__ . '/../../../'), $commit->getRepository()->getPath()); diff --git a/tests/Utils/IO/Csv/CsvDatabaseMapperTest.php b/tests/Utils/IO/Csv/CsvDatabaseMapperTest.php deleted file mode 100644 index 48a9fedab..000000000 --- a/tests/Utils/IO/Csv/CsvDatabaseMapperTest.php +++ /dev/null @@ -1,24 +0,0 @@ -xss

xss

xss

-

xss

+

xss

xss

xss

xss

-

xss

+

xss

xss

xss

xss

-

xss

+

xss

xss

xss

xss

-

xss

\ No newline at end of file +

xss

\ No newline at end of file diff --git a/tests/Utils/TaskSchedule/CronTest.php b/tests/Utils/TaskSchedule/CronTest.php index d828b97ad..0712511af 100644 --- a/tests/Utils/TaskSchedule/CronTest.php +++ b/tests/Utils/TaskSchedule/CronTest.php @@ -14,6 +14,7 @@ namespace phpOMS\tests\Utils\TaskSchedule; use phpOMS\Utils\TaskSchedule\Cron; +use phpOMS\Utils\TaskSchedule\CronJob; class CronTest extends \PHPUnit\Framework\TestCase { @@ -21,4 +22,33 @@ class CronTest extends \PHPUnit\Framework\TestCase { self::assertInstanceOf('\phpOMS\Utils\TaskSchedule\SchedulerAbstract', new Cron()); } + + public function testCRUD() + { + if (\stripos(PHP_OS, 'LINUX') !== false && \stripos(__DIR__, '/travis/') === false) { + self::assertTrue(Cron::guessBin()); + $cron = new Cron(); + + self::assertEquals([], $cron->getAllByName('testCronJob', false)); + + $job = new CronJob('testCronJob', 'testFile', '0 0 1 1 *'); + $cron->create($job); + + self::assertTrue(!empty($cron->getAllByName('testCronJob', false))); + if (!empty($cron->getAllByName('testCronJob', false))) { + self::assertEquals('testFile', $cron->getAllByName('testCronJob', false)[0]->getCommand()); + } + + $job->setCommand('testFile2'); + $cron->update($job); + + self::assertTrue(!empty($cron->getAllByName('testCronJob', false))); + if (!empty($cron->getAllByName('testCronJob', false))) { + self::assertEquals('testFile2', $cron->getAllByName('testCronJob', false)[0]->getCommand()); + } + + $cron->delete($job); + self::assertEquals([], $cron->getAllByName('testCronJob', false)); + } + } } diff --git a/tests/Utils/TaskSchedule/SchedulerAbstractTest.php b/tests/Utils/TaskSchedule/SchedulerAbstractTest.php index 0462f84cf..f3cacd58a 100644 --- a/tests/Utils/TaskSchedule/SchedulerAbstractTest.php +++ b/tests/Utils/TaskSchedule/SchedulerAbstractTest.php @@ -19,6 +19,6 @@ class SchedulerAbstractTest extends \PHPUnit\Framework\TestCase { public function testDefault() { - self::assertEquals('', SchedulerAbstract::getBin()); + self::assertTrue(SchedulerAbstract::getBin() === '' || \file_exists(SchedulerAbstract::getBin() )); } } diff --git a/tests/Utils/TaskSchedule/TaskAbstractTest.php b/tests/Utils/TaskSchedule/TaskAbstractTest.php index 41f4ebcd2..12fae0da4 100644 --- a/tests/Utils/TaskSchedule/TaskAbstractTest.php +++ b/tests/Utils/TaskSchedule/TaskAbstractTest.php @@ -22,6 +22,11 @@ class TaskAbstractTest extends \PHPUnit\Framework\TestCase protected function setUp() { $this->class = new class('') extends TaskAbstract { + public function __toString() : string + { + return ''; + } + public static function createWith(array $jobData) : TaskAbstract { @@ -33,7 +38,6 @@ class TaskAbstractTest extends \PHPUnit\Framework\TestCase { self::assertEquals('', $this->class->getId()); self::assertEquals('', $this->class->getCommand()); - self::assertEquals('', $this->class->getRun()); self::assertEquals('', $this->class->getStatus()); self::assertInstanceOf('\DateTime', $this->class->getNextRunTime()); self::assertInstanceOf('\DateTime', $this->class->getLastRuntime()); @@ -45,9 +49,6 @@ class TaskAbstractTest extends \PHPUnit\Framework\TestCase $this->class->setCommand('Command'); self::assertEquals('Command', $this->class->getCommand()); - $this->class->setRun('Run'); - self::assertEquals('Run', $this->class->getRun()); - $this->class->setStatus('Status'); self::assertEquals('Status', $this->class->getStatus()); diff --git a/tests/Utils/TaskSchedule/TaskSchedulerTest.php b/tests/Utils/TaskSchedule/TaskSchedulerTest.php index 187ff86a0..3f18146fa 100644 --- a/tests/Utils/TaskSchedule/TaskSchedulerTest.php +++ b/tests/Utils/TaskSchedule/TaskSchedulerTest.php @@ -14,6 +14,7 @@ namespace phpOMS\tests\Utils\TaskSchedule; use phpOMS\Utils\TaskSchedule\TaskScheduler; +use phpOMS\Utils\TaskSchedule\Schedule; class TaskSchedulerTest extends \PHPUnit\Framework\TestCase { @@ -21,4 +22,33 @@ class TaskSchedulerTest extends \PHPUnit\Framework\TestCase { self::assertInstanceOf('\phpOMS\Utils\TaskSchedule\SchedulerAbstract', new TaskScheduler()); } + + public function testCRUD() + { + if (\stristr(PHP_OS, 'WIN')) { + self::assertTrue(TaskScheduler::guessBin()); + $cron = new TaskScheduler(); + + self::assertEquals([], $cron->getAllByName('testCronJob', false)); + + $job = new Schedule('testCronJob', 'testFile', '0 0 1 1 *'); + $cron->create($job); + + self::assertTrue(!empty($cron->getAllByName('testCronJob', false))); + if (!empty($cron->getAllByName('testCronJob', false))) { + self::assertEquals('testFile', $cron->getAllByName('testCronJob', false)[0]->getCommand()); + } + + $job->setCommand('testFile2'); + $cron->update($job); + + self::assertTrue(!empty($cron->getAllByName('testCronJob', false))); + if (!empty($cron->getAllByName('testCronJob', false))) { + self::assertEquals('testFile2', $cron->getAllByName('testCronJob', false)[0]->getCommand()); + } + + $cron->delete($job); + self::assertEquals([], $cron->getAllByName('testCronJob', false)); + } + } } diff --git a/tests/phpunit_no_coverage.xml b/tests/phpunit_no_coverage.xml new file mode 100644 index 000000000..06ec01ec2 --- /dev/null +++ b/tests/phpunit_no_coverage.xml @@ -0,0 +1,37 @@ + + + + + ./ + ./tests/ + Module/ModuleManagerTest.php + ./vendor + ./Build + + + + + .* + + ../* + ../* + ../* + ./Build + *vendor* + ./vendor + ../vendor + + + + + + +