diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..f71bc26 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,35 @@ +name: Lint + +on: + push: + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + + # 'push' runs on inner branches, 'pull_request' will run only on outer PRs + if: > + github.event_name == 'push' + || (github.event_name == 'pull_request' + && github.event.pull_request.head.repo.full_name != github.repository) + + permissions: + contents: read + steps: + - name: Code Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.2" + extensions: gd, zip, intl, yaml, pdo_mysql, rdkafka, imagick + tools: composer:v2 + coverage: none + + - name: Install dependencies + run: composer install --no-interaction --no-progress --no-suggest --prefer-dist + + - name: PHP CS Fixer + run: vendor/bin/php-cs-fixer fix --dry-run --diff diff --git a/.gitignore b/.gitignore index dea414f..728d2fb 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ /logs/* !/logs/.gitkeep + +.php-cs-fixer.cache \ No newline at end of file diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..80031ac --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,14 @@ +in(__DIR__) +; + +return (new PhpCsFixer\Config()) + ->setRules([ + '@PER-CS2x0' => true, + '@PHP8x2Migration' => true, + ]) + ->setFinder($finder) + ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()) +; diff --git a/bin/chandler-latte-lint b/bin/chandler-latte-lint new file mode 100755 index 0000000..0e31b07 --- /dev/null +++ b/bin/chandler-latte-lint @@ -0,0 +1,21 @@ +#!/usr/bin/env php +ignite(true); + +$path = $argv[1] ?? '.'; + +use Chandler\MVC\SimplePresenter; + +final class TestPresenter extends SimplePresenter {} + +$presenter = new TestPresenter(); +$latte = $presenter->getTemplatingEngine(); +$latte->addExtension(new \Latte\Essential\TranslatorExtension(tr(...))); +$latte->setStrictParsing(); +$linter = new Latte\Tools\Linter(engine: $latte); + +$ok = $linter->scanDirectory($path); +exit($ok ? 0 : 1); diff --git a/chandler.iml b/chandler.iml deleted file mode 100644 index c7541f9..0000000 --- a/chandler.iml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/chandler.ipr b/chandler.ipr deleted file mode 100644 index 911bf4d..0000000 --- a/chandler.ipr +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/chandler.iws b/chandler.iws deleted file mode 100644 index de9d8bd..0000000 --- a/chandler.iws +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - $PROJECT_DIR$/composer.json - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1660915095386 - - - - - - \ No newline at end of file diff --git a/chandler/Bootstrap.php b/chandler/Bootstrap.php index 9ef30ab..7269317 100644 --- a/chandler/Bootstrap.php +++ b/chandler/Bootstrap.php @@ -1,14 +1,16 @@ - * @internal */ @@ -16,76 +18,81 @@ class Bootstrap { /** * Starts Tracy debugger session and installs panels. - * + * * @internal * @return void */ private function registerDebugger(): void { Debugger::enable((CHANDLER_ROOT_CONF["debug"] ? Debugger::DEVELOPMENT : Debugger::PRODUCTION), __DIR__ . "/../logs"); - Debugger::getBar()->addPanel(new Chandler\Debug\DatabasePanel); + Debugger::getBar()->addPanel(new Chandler\Debug\DatabasePanel()); } - + private function loadConfig(): void { - if(!file_exists($conf = CHANDLER_ROOT . "/chandler.yml")) - if(!file_exists($conf = CHANDLER_ROOT . "/../chandler.yml")) - if(!file_exists($conf = "/etc/chandler.d/chandler.yml")) + if (!file_exists($conf = CHANDLER_ROOT . "/chandler.yml")) { + if (!file_exists($conf = CHANDLER_ROOT . "/../chandler.yml")) { + if (!file_exists($conf = "/etc/chandler.d/chandler.yml")) { exit("Configuration file not found... Have you forgotten to rename it?"); - + } + } + } + define("CHANDLER_ROOT_CONF", chandler_parse_yaml($conf)["chandler"]); } - + /** * Loads procedural APIs. - * + * * @internal * @return void */ private function registerFunctions(): void { - foreach(glob(CHANDLER_ROOT . "/chandler/procedural/*.php") as $procDef) + foreach (glob(CHANDLER_ROOT . "/chandler/procedural/*.php") as $procDef) { require $procDef; + } } - + /** * Set ups autoloaders. - * + * * @internal * @return void */ private function registerAutoloaders(): void { - spl_autoload_register(function($class): void - { - if(strpos($class, "Chandler\\") !== 0) return; - + spl_autoload_register(function ($class): void { + if (strpos($class, "Chandler\\") !== 0) { + return; + } + require_once(str_replace("\\", "/", str_replace("Chandler\\", CHANDLER_ROOT . "/chandler/", $class)) . ".php"); }, true, true); } - + /** * Defines constant CONNECTING_IP, that stores end user's IP address. * Uses X-Forwarded-For if present. - * + * * @internal * @return void */ private function defineIP(): void { - if(isset($_SERVER["HTTP_X_FORWARDED_FOR"])) { + if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) { $path = explode(", ", $_SERVER["HTTP_X_FORWARDED_FOR"]); - $ip = $path[0]; + $ip = $path[0]; } else { $ip = $_SERVER["REMOTE_ADDR"]; } - + define("CONNECTING_IP", $ip, false); } - + /** * Initializes GeoIP, sets DB directory. - * + * * @internal * @return void */ @@ -93,10 +100,10 @@ private function setupGeoIP(): void { geoip_setup_custom_directory(CHANDLER_ROOT . "/3rdparty/maxmind/"); } - + /** * Bootstraps extensions. - * + * * @internal * @return void */ @@ -104,10 +111,10 @@ private function igniteExtensions(): void { Chandler\Extensions\ExtensionManager::i(); } - + /** * Starts router and serves request. - * + * * @internal * @param string $url Request URL * @return void @@ -115,25 +122,26 @@ private function igniteExtensions(): void private function route(string $url): void { ob_start(); - + $router = Chandler\MVC\Routing\Router::i(); - if(($output = $router->execute($url, NULL)) !== null) + if (($output = $router->execute($url, null)) !== null) { echo $output; - else + } else { chandler_http_panic(404, "Not Found", "No routes for $url."); - + } + ob_flush(); ob_end_flush(); flush(); } - + /** * Starts framework. - * + * * @internal * @return void */ - function ignite(bool $headless = false): void + public function ignite(bool $headless = false): void { $this->registerFunctions(); $this->registerAutoloaders(); @@ -141,7 +149,7 @@ function ignite(bool $headless = false): void $this->registerDebugger(); $this->igniteExtensions(); - if(!$headless) { + if (!$headless) { header("Referrer-Policy: strict-origin-when-cross-origin"); $this->defineIP(); \Chandler\Database\CurrentUser::get(CONNECTING_IP, $_SERVER["HTTP_USER_AGENT"]); @@ -150,4 +158,4 @@ function ignite(bool $headless = false): void } } -return new Bootstrap; +return new Bootstrap(); diff --git a/chandler/ControlPanel/includes/assert_user.php b/chandler/ControlPanel/includes/assert_user.php index 8c6449e..ba19b5b 100644 --- a/chandler/ControlPanel/includes/assert_user.php +++ b/chandler/ControlPanel/includes/assert_user.php @@ -1,13 +1,14 @@ -getUser(); - if(!$user) return NULL; - - return $user->can("access")->model("admin")->whichBelongsTo(NULL); -}); \ No newline at end of file + if (!$user) { + return null; + } + + return $user->can("access")->model("admin")->whichBelongsTo(null); +}); diff --git a/chandler/Database/CurrentUser.php b/chandler/Database/CurrentUser.php index fdc8781..daa0926 100644 --- a/chandler/Database/CurrentUser.php +++ b/chandler/Database/CurrentUser.php @@ -1,4 +1,6 @@ -ip = $ip; + } - if ($useragent) + if ($useragent) { $this->useragent = $useragent; + } } public static function get($ip, $useragent) { - if (self::$instance === null) self::$instance = new self($ip, $useragent); + if (self::$instance === null) { + self::$instance = new self($ip, $useragent); + } return self::$instance; } diff --git a/chandler/Database/DBEntity.php b/chandler/Database/DBEntity.php index 05d7069..7c3451f 100644 --- a/chandler/Database/DBEntity.php +++ b/chandler/Database/DBEntity.php @@ -1,4 +1,6 @@ -getTable()->getName(); - if ($_table !== $this->tableName) + if ($_table !== $this->tableName) { throw new ISE("Invalid data supplied for model: table $_table is not compatible with table" . $this->tableName); + } $this->record = $row; $this->user = Authenticator::i()->getUser(); } - - function __call(string $fName, array $args) + + public function __call(string $fName, array $args) { if (substr($fName, 0, 3) === "set") { $field = mb_strtolower(substr($fName, 3)); @@ -41,46 +46,48 @@ function __call(string $fName, array $args) throw new \Error("Call to undefined method " . get_class($this) . "::$fName"); } } - + private function getTable(): Selection { return DatabaseConnection::i()->getContext()->table($this->tableName); } - + protected function getRecord(): ?ActiveRow { return $this->record; } - + protected function stateChanges(string $column, $value): void { - if (!is_null($this->record)) - $t = $this->record->{$column}; #Test if column exists - + if (!is_null($this->record)) { + $t = $this->record->{$column}; + } #Test if column exists + $this->changes[$column] = $value; } - - function getId() + + public function getId() { return $this->getRecord()->id; } - - function isDeleted(): bool + + public function isDeleted(): bool { - return (bool)$this->getRecord()->deleted; + return (bool) $this->getRecord()->deleted; } - - function unwrap(): object + + public function unwrap(): object { - return (object)$this->getRecord()->toArray(); + return (object) $this->getRecord()->toArray(); } - - function delete(bool $softly = true): void + + public function delete(bool $softly = true): void { - if (is_null($this->record)) + if (is_null($this->record)) { throw new ISE("Can't delete a model, that hasn't been flushed to DB. Have you forgotten to call save() first?"); + } - (new Logs)->create($this->user->getId(), $this->getTable()->getName(), get_class($this), 2, $this->record->toArray(), $this->changes); + (new Logs())->create($this->user->getId(), $this->getTable()->getName(), get_class($this), 2, $this->record->toArray(), $this->changes); if ($softly) { $this->record = $this->getTable()->where("id", $this->record->id)->update(["deleted" => true]); @@ -89,18 +96,19 @@ function delete(bool $softly = true): void $this->deleted = true; } } - - function undelete(): void + + public function undelete(): void { - if (is_null($this->record)) + if (is_null($this->record)) { throw new ISE("Can't undelete a model, that hasn't been flushed to DB. Have you forgotten to call save() first?"); + } - (new Logs)->create($this->user->getId(), $this->getTable()->getName(), get_class($this), 3, $this->record->toArray(), ["deleted" => false]); + (new Logs())->create($this->user->getId(), $this->getTable()->getName(), get_class($this), 3, $this->record->toArray(), ["deleted" => false]); $this->getTable()->where("id", $this->record->id)->update(["deleted" => false]); } - function save(?bool $log = false): void + public function save(?bool $log = false): void { if ($log) { $user_id = Authenticator::i()->getUser()->getId(); @@ -110,28 +118,26 @@ function save(?bool $log = false): void $this->record = $this->getTable()->insert($this->changes); if ($log && $this->getTable()->getName() !== "ChandlerLogs" && CHANDLER_ROOT_CONF["preferences"]["logs"]["enabled"]) { - (new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 0, $this->record->toArray(), $this->changes); + (new Logs())->create($user_id, $this->getTable()->getName(), get_class($this), 0, $this->record->toArray(), $this->changes); } } else { if ($log && $this->getTable()->getName() !== "ChandlerLogs" && CHANDLER_ROOT_CONF["preferences"]["logs"]["enabled"]) { - (new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 1, $this->record->toArray(), $this->changes); + (new Logs())->create($user_id, $this->getTable()->getName(), get_class($this), 1, $this->record->toArray(), $this->changes); } if ($this->deleted) { - $this->record = $this->getTable()->insert((array)$this->record); + $this->record = $this->getTable()->insert((array) $this->record); } else { $this->getTable()->get($this->record->id)->update($this->changes); $this->record = $this->getTable()->get($this->record->id); } } - + $this->changes = []; } - function getTableName(): string + public function getTableName(): string { return $this->getTable()->getName(); } - - use \Nette\SmartObject; } diff --git a/chandler/Database/DatabaseConnection.php b/chandler/Database/DatabaseConnection.php index 558d3b9..182d657 100644 --- a/chandler/Database/DatabaseConnection.php +++ b/chandler/Database/DatabaseConnection.php @@ -1,70 +1,77 @@ -getCode() === "42000") + } catch (Database\ConnectionException $ex) { + if ($ex->getCode() === "42000") { chandler_db_busy(); - else + } else { chandler_http_panic(503, "Service Temporarily Unavailable", "Error estabilishing database connection: " . $ex->getMessage()); + } } - + $storage = new FileStorage($tmpFolder ?? (CHANDLER_ROOT . "/tmp/cache/database")); $structure = new Database\Structure($connection, $storage); $conventions = new DiscoveredConventions($structure); $context = new Database\Context($connection, $structure, $conventions, $storage); - + $this->connection = $connection; $this->context = $context; - - if(CHANDLER_ROOT_CONF["debug"]) + + if (CHANDLER_ROOT_CONF["debug"]) { $this->connection->onQuery = $this->getQueryCallback(); + } } - + private function __clone() {} public function __wakeup() {} - + protected function getQueryCallback(): array { - return [(function($connection, $result) { - if($result instanceof \Nette\Database\DriverException) + return [(function ($connection, $result) { + if ($result instanceof \Nette\Database\DriverException) { return; - - if(!isset($GLOBALS["dbgSqlQueries"])) { + } + + if (!isset($GLOBALS["dbgSqlQueries"])) { $GLOBALS["dbgSqlQueries"] = []; $GLOBALS["dbgSqlTime"] = 0; } - + $params = $result->getParameters(); $GLOBALS["dbgSqlQueries"][] = str_replace(str_split(str_repeat("?", sizeof($params))), $params, $result->getQueryString()); $GLOBALS["dbgSqlTime"] += $result->getTime(); })]; } - - function getConnection(): Database\Connection + + public function getConnection(): Database\Connection { return $this->connection; } - - function getContext(): Database\Context + + public function getContext(): Database\Context { return $this->context; } - - static function i(): DatabaseConnection + + public static function i(): DatabaseConnection { return static::$self ?? static::$self = new static( CHANDLER_ROOT_CONF["database"]["dsn"], @@ -72,19 +79,19 @@ static function i(): DatabaseConnection CHANDLER_ROOT_CONF["database"]["password"] ); } - - static function connect(array $options): DatabaseConnection + + public static function connect(array $options): DatabaseConnection { $id = sha1(serialize($options)) . "__DATABASECONNECTION\$feafccc"; - if(!isset($GLOBALS[$id])) { + if (!isset($GLOBALS[$id])) { $GLOBALS[$id] = new static( $options["dsn"], $options["user"], $options["password"], - isset($options["caching"]) ? ($options["caching"]["folder"] ?? NULL) : NULL + isset($options["caching"]) ? ($options["caching"]["folder"] ?? null) : null ); } - + return $GLOBALS[$id]; } } diff --git a/chandler/Database/Log.php b/chandler/Database/Log.php index 27d312e..6854201 100644 --- a/chandler/Database/Log.php +++ b/chandler/Database/Log.php @@ -1,5 +1,9 @@ -getRecord()->id; } - function getUser(): string + public function getUser(): string { return $this->getRecord()->user; } - function getObjectTable(): string + public function getObjectTable(): string { return $this->getRecord()->object_table; } - function getObjectId(): int + public function getObjectId(): int { return $this->getRecord()->object_id; } - function getObject() + public function getObject() { $model = $this->getRecord()->object_model; return new $model(DatabaseConnection::i()->getContext()->table($this->getObjectTable())->get($this->getObjectId())); } - function getTypeRaw(): int + public function getTypeRaw(): int { return $this->getRecord()->type; } - function getType(): string + public function getType(): string { return ["logs_added", "logs_edited", "logs_removed", "logs_restored"][$this->getTypeRaw()]; } - function getTypeNom(): string + public function getTypeNom(): string { return ["logs_adding", "logs_editing", "logs_removing", "logs_restoring"][$this->getTypeRaw()]; } - function getObjectType(): string + public function getObjectType(): string { $type = tr("log_" . $this->getObjectTable()); if ($type === "@log_" . $this->getObjectTable()) { @@ -58,54 +62,59 @@ function getObjectType(): string } } - function getObjectName(): string + public function getObjectName(): string { $object = $this->getObject(); - if (method_exists($object, 'getCanonicalName')) + if (method_exists($object, 'getCanonicalName')) { return $object->getCanonicalName(); - else return "[#" . $this->getObjectId() . "] " . $this->getObjectType(); + } else { + return "[#" . $this->getObjectId() . "] " . $this->getObjectType(); + } } - function getLogsText(): string + public function getLogsText(): string { return $this->getRecord()->logs_text; } - function getObjectURL(): string + public function getObjectURL(): string { $object = $this->getObject(); - if (method_exists($object, "getURL") && $this->getObjectTable() !== "videos") + if (method_exists($object, "getURL") && $this->getObjectTable() !== "videos") { return $this->getObject()->getURL(); - else + } else { return "#"; + } } - function getObjectAvatar(): ?string + public function getObjectAvatar(): ?string { $object = $this->getObject(); - if (method_exists($object, 'getAvatarURL')) + if (method_exists($object, 'getAvatarURL')) { return $object->getAvatarURL("normal"); - else return NULL; + } else { + return null; + } } - function getOldValue(): ?array + public function getOldValue(): ?array { return (array) json_decode($this->getRecord()->xdiff_old, true, JSON_UNESCAPED_UNICODE) ?? null; } - function getNewValue(): ?array + public function getNewValue(): ?array { return (array) json_decode($this->getRecord()->xdiff_new, true, JSON_UNESCAPED_UNICODE) ?? null; } - function getTime(): int + public function getTime(): int { return $this->getRecord()->ts; } - function diff($old, $new): array + public function diff($old, $new): array { - $matrix = array(); + $matrix = []; $maxlen = 0; foreach ($old as $oindex => $ovalue) { $nkeys = array_keys($new, $ovalue); @@ -119,27 +128,32 @@ function diff($old, $new): array } } } - if ($maxlen == 0) return array(array('d' => $old, 'i' => $new)); + if ($maxlen == 0) { + return [['d' => $old, 'i' => $new]]; + } return array_merge( $this->diff(array_slice($old, 0, $omax), array_slice($new, 0, $nmax)), array_slice($new, $nmax, $maxlen), - $this->diff(array_slice($old, $omax + $maxlen), array_slice($new, $nmax + $maxlen))); + $this->diff(array_slice($old, $omax + $maxlen), array_slice($new, $nmax + $maxlen)) + ); } - function htmlDiff($old, $new): string + public function htmlDiff($old, $new): string { $ret = ''; $diff = $this->diff(preg_split("/[\s]+/", $old), preg_split("/[\s]+/", $new)); foreach ($diff as $k) { - if (is_array($k)) + if (is_array($k)) { $ret .= (!empty($k['d']) ? "" . implode(' ', $k['d']) . " " : '') . (!empty($k['i']) ? "" . implode(' ', $k['i']) . " " : ''); - else $ret .= $k . ' '; + } else { + $ret .= $k . ' '; + } } return $ret; } - function getChanges(): array + public function getChanges(): array { $result = $this->getOldValue(); $_changes = []; @@ -151,31 +165,31 @@ function getChanges(): array $new_value = xdiff_string_patch((string) $result[$field], (string) $value); $diff = preg_replace_callback("/<((?!\/?(?:del|ins)\b)[^>]*)>/i", function ($matches) { return '[' . str_replace(['<', '>'], ['[', ']'], $matches[1]) . ']'; - }, $this->htmlDiff((string)$result[$field], (string)$new_value)); + }, $this->htmlDiff((string) $result[$field], (string) $new_value)); $_changes[$field] = [ "field" => $field, "old_value" => $result[$field], "new_value" => strlen($new_value) > 0 ? $new_value : "(empty)", "ts" => $this->getTime(), - "diff" => $diff + "diff" => $diff, ]; } - } else if ($this->getTypeRaw() === 0) { // create + } elseif ($this->getTypeRaw() === 0) { // create foreach ($result as $field => $value) { $_changes[$field] = [ "field" => $field, "old_value" => $value, - "ts" => $this->getTime() + "ts" => $this->getTime(), ]; } - } else if ($this->getTypeRaw() === 2) { // delete + } elseif ($this->getTypeRaw() === 2) { // delete $_changes[] = [ "field" => "deleted", "old_value" => 0, "new_value" => 1, "ts" => $this->getTime(), - "diff" => $this->htmlDiff("0", "1") + "diff" => $this->htmlDiff("0", "1"), ]; } diff --git a/chandler/Database/Logs.php b/chandler/Database/Logs.php index 6850daa..512fb3a 100644 --- a/chandler/Database/Logs.php +++ b/chandler/Database/Logs.php @@ -1,5 +1,9 @@ -context = DatabaseConnection::i()->getContext(); $this->logs = $this->context->table("ChandlerLogs"); @@ -18,15 +22,15 @@ function __construct() private function toLog(?ActiveRow $ar): ?Log { - return is_null($ar) ? NULL : new Log($ar); + return is_null($ar) ? null : new Log($ar); } - function get(int $id): ?Log + public function get(int $id): ?Log { return $this->toLog($this->logs->get($id)); } - function create(string $user, string $table, string $model, int $type, $object, $changes, ?string $ip = NULL, ?string $useragent = NULL): void + public function create(string $user, string $table, string $model, int $type, $object, $changes, ?string $ip = null, ?string $useragent = null): void { if ($model !== "Chandler\Database\Log") { $fobject = (is_array($object) ? $object : $object->unwrap()); @@ -39,22 +43,28 @@ function create(string $user, string $table, string $model, int $type, $object, } foreach (array_diff_assoc($nobject, $changes) as $field => $value) { - if (str_starts_with($field, "rate_limit")) continue; - if ($field === "online") continue; - $_changes[$field] = xdiff_string_diff((string)$nobject[$field], (string)$changes[$field]); + if (str_starts_with($field, "rate_limit")) { + continue; + } + if ($field === "online") { + continue; + } + $_changes[$field] = xdiff_string_diff((string) $nobject[$field], (string) $changes[$field]); } - if (count($_changes) === 0) return; - } else if ($type === 0) { // if new + if (count($_changes) === 0) { + return; + } + } elseif ($type === 0) { // if new $nobject = $fobject; foreach ($fobject as $field => $value) { - $_changes[$field] = xdiff_string_diff("", (string)$value); + $_changes[$field] = xdiff_string_diff("", (string) $value); } - } else if ($type === 2 || $type === 3) { // if deleting or restoring - $_changes["deleted"] = (int)($type === 2); + } elseif ($type === 2 || $type === 3) { // if deleting or restoring + $_changes["deleted"] = (int) ($type === 2); } - $log = new Log; + $log = new Log(); $log->setUser($user); $log->setType($type); $log->setObject_Table($table); @@ -69,17 +79,19 @@ function create(string $user, string $table, string $model, int $type, $object, } } - function search($filter): \Traversable + public function search($filter): \Traversable { - foreach ($this->logs->where($filter)->order("id DESC") as $log) + foreach ($this->logs->where($filter)->order("id DESC") as $log) { yield new Log($log); + } } - function getTypes(): array + public function getTypes(): array { $types = []; - foreach ($this->context->query("SELECT DISTINCT(`object_model`) AS `object_model` FROM `ChandlerLogs`")->fetchAll() as $type) + foreach ($this->context->query("SELECT DISTINCT(`object_model`) AS `object_model` FROM `ChandlerLogs`")->fetchAll() as $type) { $types[] = str_replace(CHANDLER_ROOT_CONF["preferences"]["logs"]["entitiesNamespace"], "", $type->object_model); + } return $types; } diff --git a/chandler/Debug/DatabasePanel.php b/chandler/Debug/DatabasePanel.php index 7a900a7..079ce99 100644 --- a/chandler/Debug/DatabasePanel.php +++ b/chandler/Debug/DatabasePanel.php @@ -1,49 +1,54 @@ - - $svg - $count queries ($time ms) - -EOF; + + $svg + $count queries ($time ms) + + EOF; } - + public function getPanel() { - if(!isset($GLOBALS["dbgSqlQueries"])) + if (!isset($GLOBALS["dbgSqlQueries"])) { return "No queries were made..."; - + } + $html = <<Queries: -
-
- -HTML; - - foreach($GLOBALS["dbgSqlQueries"] as $query) { +

Queries:

+
+
+
+ HTML; + + foreach ($GLOBALS["dbgSqlQueries"] as $query) { $query = DbHelpers::dumpSql($query); $html .= ""; } - + $html .= "
$query
"; - + return $html; } } diff --git a/chandler/Email/Email.php b/chandler/Email/Email.php index 8f1cd59..2fb7ce0 100644 --- a/chandler/Email/Email.php +++ b/chandler/Email/Email.php @@ -1,5 +1,9 @@ -sendEmail( CHANDLER_ROOT_CONF["email"]["postmark"]["user"], $to, $subject, $html, strip_tags($html), - NULL, + null, true, - NULL, - NULL, - NULL, + null, + null, + null, ["Sensitivity" => "Company-Confidential"], - NULL, + null, "None", - NULL, + null, CHANDLER_ROOT_CONF["email"]["postmark"]["stream"] ); } else { - $transport = new Swift_SmtpTransport(CHANDLER_ROOT_CONF["email"]["host"], CHANDLER_ROOT_CONF["email"]["port"], CHANDLER_ROOT_CONF["email"]["ssl"] ? "ssl" : NULL); + $transport = new Swift_SmtpTransport(CHANDLER_ROOT_CONF["email"]["host"], CHANDLER_ROOT_CONF["email"]["port"], CHANDLER_ROOT_CONF["email"]["ssl"] ? "ssl" : null); $transport->setUsername(CHANDLER_ROOT_CONF["email"]["user"] ?? CHANDLER_ROOT_CONF["email"]["addr"]); $transport->setPassword(CHANDLER_ROOT_CONF["email"]["pass"]); diff --git a/chandler/Eventing/EventDispatcher.php b/chandler/Eventing/EventDispatcher.php index e176997..4317065 100644 --- a/chandler/Eventing/EventDispatcher.php +++ b/chandler/Eventing/EventDispatcher.php @@ -1,33 +1,40 @@ -hooks[] = $hook; - + return true; } - - function pushEvent(Events\Event $event): Events\Event + + public function pushEvent(Events\Event $event): Events\Event { - foreach($hooks as $hook) { - if($event instanceof Events\Cancelable) - if($event->isCancelled()) + foreach ($hooks as $hook) { + if ($event instanceof Events\Cancelable) { + if ($event->isCancelled()) { break; - + } + } + $method = "on" . str_replace("Event", "", get_class($event)); - if(!method_exists($hook, $methodName)) continue; - + if (!method_exists($hook, $methodName)) { + continue; + } + $hook->$method($event); } - + return $event; } - - use TSimpleSingleton; } diff --git a/chandler/Eventing/Events/Cancelable.php b/chandler/Eventing/Events/Cancelable.php index 0cfd8e8..71362b2 100644 --- a/chandler/Eventing/Events/Cancelable.php +++ b/chandler/Eventing/Events/Cancelable.php @@ -1,11 +1,14 @@ -data = $data; $this->code = $code; $this->time = time(); } - - function getData() + + public function getData() { return $this->data; } - - function getCode() + + public function getCode() { return $this->code; } - - function getTime() + + public function getTime() { return $this->time; } - - function isTainted() + + public function isTainted() { return !$this->pristine; } diff --git a/chandler/Extensions/ExtensionManager.php b/chandler/Extensions/ExtensionManager.php index 5cfd996..a3ba7d1 100644 --- a/chandler/Extensions/ExtensionManager.php +++ b/chandler/Extensions/ExtensionManager.php @@ -1,5 +1,9 @@ -in(CHANDLER_EXTENSIONS_AVAILABLE) as $directory) { + foreach (Finder::findDirectories("*")->in(CHANDLER_EXTENSIONS_AVAILABLE) as $directory) { $extensionName = $directory->getFilename(); $directory = $directory->getRealPath(); $config = "$directory/manifest.yml"; - - if(!file_exists($config)) { + + if (!file_exists($config)) { trigger_error("Skipping $extensionName for not having a valid configuration file ($config is not found)", E_USER_WARNING); continue; } - + $this->extensions[$extensionName] = (object) chandler_parse_yaml($config); $this->extensions[$extensionName]->id = $extensionName; $this->extensions[$extensionName]->rawName = $directory; $this->extensions[$extensionName]->enabled = CHANDLER_ROOT_CONF["extensions"]["allEnabled"]; } - - if(!CHANDLER_ROOT_CONF["extensions"]["allEnabled"]) { - foreach(Finder::find("*")->in(CHANDLER_EXTENSIONS_ENABLED) as $directory) { #findDirectories doesn't work with symlinks - if(!is_dir($directory->getRealPath())) continue; - + + if (!CHANDLER_ROOT_CONF["extensions"]["allEnabled"]) { + foreach (Finder::find("*")->in(CHANDLER_EXTENSIONS_ENABLED) as $directory) { #findDirectories doesn't work with symlinks + if (!is_dir($directory->getRealPath())) { + continue; + } + $extension = $directory->getFilename(); - - if(!array_key_exists($extension, $this->extensions)) { + + if (!array_key_exists($extension, $this->extensions)) { trigger_error("Extension $extension is enabled, but not available, skipping", E_USER_WARNING); continue; } - + $this->extensions[$extension]->enabled = true; } } - - if(!array_key_exists(CHANDLER_ROOT_CONF["rootApp"], $this->extensions) || !$this->extensions[CHANDLER_ROOT_CONF["rootApp"]]->enabled) { + + if (!array_key_exists(CHANDLER_ROOT_CONF["rootApp"], $this->extensions) || !$this->extensions[CHANDLER_ROOT_CONF["rootApp"]]->enabled) { trigger_error("Selected root app is not available", E_USER_ERROR); } - + $this->rootApp = CHANDLER_ROOT_CONF["rootApp"]; $this->eventLoop = EventDispatcher::i(); $this->router = Router::i(); - + $this->init(); } - + private function init(): void { - foreach($this->getExtensions(true) as $name => $configuration) { - spl_autoload_register(function($class) use ($name) { - if(substr($class, 0, strlen("$name\\")) !== "$name\\") + foreach ($this->getExtensions(true) as $name => $configuration) { + spl_autoload_register(function ($class) use ($name) { + if (substr($class, 0, strlen("$name\\")) !== "$name\\") { return false; - + } + include_once CHANDLER_EXTENSIONS_ENABLED . "/" . str_replace("\\", "/", $class) . ".php"; }); - + define(str_replace("-", "_", mb_strtoupper($name)) . "_ROOT", CHANDLER_EXTENSIONS_ENABLED . "/$name", false); define(str_replace("-", "_", mb_strtoupper($name)) . "_ROOT_CONF", chandler_parse_yaml(CHANDLER_EXTENSIONS_ENABLED . "/$name/$name.yml"), false); - - if(isset($configuration->init)) { + + if (isset($configuration->init)) { $init = require(CHANDLER_EXTENSIONS_ENABLED . "/$name/" . $configuration->init); - if(is_callable($init)) + if (is_callable($init)) { $init(); + } } - - if(is_dir($hooks = CHANDLER_EXTENSIONS_ENABLED . "/$name/Hooks")) { - foreach(Finder::findFiles("*Hook.php")->in($hooks) as $hookFile) { + + if (is_dir($hooks = CHANDLER_EXTENSIONS_ENABLED . "/$name/Hooks")) { + foreach (Finder::findFiles("*Hook.php")->in($hooks) as $hookFile) { $hookClassName = "$name\\Hooks\\" . str_replace(".php", "", end(explode("/", $hookFile))); - $hook = new $hookClassName; - + $hook = new $hookClassName(); + $this->eventLoop->addListener($hook); } } - - if(is_dir($app = CHANDLER_EXTENSIONS_ENABLED . "/$name/Web")) #"app" means "web app", thus variable is called $app + + if (is_dir($app = CHANDLER_EXTENSIONS_ENABLED . "/$name/Web")) { #"app" means "web app", thus variable is called $app $this->router->readRoutes("$app/routes.yml", $name, $this->rootApp !== $name); + } } } - - function getExtensions(bool $onlyEnabled = false): array + + public function getExtensions(bool $onlyEnabled = false): array { return $onlyEnabled - ? array_filter($this->extensions, function($e) { return $e->enabled; }) + ? array_filter($this->extensions, function ($e) { return $e->enabled; }) : $this->extensions; } - - function getExtension(string $name): ?object + + public function getExtension(string $name): ?object { return @$this->extensions[$name]; } - - function disableExtension(string $name): void + + public function disableExtension(string $name): void { - if(!array_key_exists($name, $this->getExtensions(true))) return; - - if(!unlink(CHANDLER_EXTENSIONS_ENABLED . "/$name")) throw new \Exception("Could not disable extension"); + if (!array_key_exists($name, $this->getExtensions(true))) { + return; + } + + if (!unlink(CHANDLER_EXTENSIONS_ENABLED . "/$name")) { + throw new \Exception("Could not disable extension"); + } } - - function enableExtension(string $name): void + + public function enableExtension(string $name): void { - if(CHANDLER_ROOT_CONF["extensions"]["allEnabled"]) return; - - if(array_key_exists($name, $this->getExtensions(true))) return; - + if (CHANDLER_ROOT_CONF["extensions"]["allEnabled"]) { + return; + } + + if (array_key_exists($name, $this->getExtensions(true))) { + return; + } + $path = CHANDLER_EXTENSIONS_AVAILABLE . "/$name"; - if(!is_dir($path)) throw new \Exception("Extension doesn't exist"); - - if(!symlink($path, str_replace("available", "enabled", $path))) throw new \Exception("Could not enable extension"); + if (!is_dir($path)) { + throw new \Exception("Extension doesn't exist"); + } + + if (!symlink($path, str_replace("available", "enabled", $path))) { + throw new \Exception("Could not enable extension"); + } } - - use TSimpleSingleton; } diff --git a/chandler/MVC/Exceptions/InterruptedException.php b/chandler/MVC/Exceptions/InterruptedException.php index b95c387..6d0735d 100644 --- a/chandler/MVC/Exceptions/InterruptedException.php +++ b/chandler/MVC/Exceptions/InterruptedException.php @@ -1,5 +1,7 @@ - CssNode::create(...), + 'script' => ScriptNode::create(...), + 'presenter' => PresenterNode::create(...), + ]; + } + + public function getProviders(): array + { + return [ + 'chandlerPresenter' => $this->presenter, + 'chandlerDomain' => explode("\\", $this->presenter)[0], + ]; + } +} diff --git a/chandler/MVC/Latte/CssNode.php b/chandler/MVC/Latte/CssNode.php new file mode 100644 index 0000000..541fac8 --- /dev/null +++ b/chandler/MVC/Latte/CssNode.php @@ -0,0 +1,45 @@ +expectArguments(); + $node = new self(); + $node->file = $tag->parser->parseUnquotedStringOrExpression(); + return $node; + } + + public function print(\Latte\Compiler\PrintContext $context): string + { + return $context->format( + <<<'XX' + %line + $__domain = $this->global->chandlerDomain; + $__file = %node; + $__realpath = CHANDLER_EXTENSIONS_ENABLED . "/$__domain/Web/static/$__file"; + + if (file_exists($__realpath)) { + $__hash = "sha384-" . base64_encode(hash_file("sha384", $__realpath, true)); + $__mod = base_convert((string) filemtime($__realpath), 10, 32); + echo ""; + } else { + echo ""; + } + XX, + $this->position, + $this->file + ); + } + + public function &getIterator(): \Generator + { + yield $this->file; + } +} diff --git a/chandler/MVC/Latte/PresenterNode.php b/chandler/MVC/Latte/PresenterNode.php new file mode 100644 index 0000000..e1edaa8 --- /dev/null +++ b/chandler/MVC/Latte/PresenterNode.php @@ -0,0 +1,43 @@ +expectArguments(); + $node = new self(); + $node->input = $tag->parser->parseArguments(); + return $node; + } + + public function print(\Latte\Compiler\PrintContext $context): string + { + return $context->format( + <<<'XX' + %line + $__input = %node; + + echo ""; + + $__router = \Chandler\MVC\Routing\Router::i(); + $__out = $__router->execute($__router->reverse(...$__input), $this->global->chandlerPresenter); + echo $__out; + + echo ""; + XX, + $this->position, + $this->input + ); + } + + public function &getIterator(): \Generator + { + yield $this->input; + } +} diff --git a/chandler/MVC/Latte/ScriptNode.php b/chandler/MVC/Latte/ScriptNode.php new file mode 100644 index 0000000..82881ac --- /dev/null +++ b/chandler/MVC/Latte/ScriptNode.php @@ -0,0 +1,45 @@ +expectArguments(); + $node = new self(); + $node->file = $tag->parser->parseUnquotedStringOrExpression(); + return $node; + } + + public function print(\Latte\Compiler\PrintContext $context): string + { + return $context->format( + <<<'XX' + %line + $__domain = $this->global->chandlerDomain; + $__file = %node; + $__realpath = CHANDLER_EXTENSIONS_ENABLED . "/$__domain/Web/static/$__file"; + + if (file_exists($__realpath)) { + $__hash = "sha384-" . base64_encode(hash_file("sha384", $__realpath, true)); + $__mod = base_convert((string) filemtime($__realpath), 10, 32); + echo ""; + } else { + echo ""; + } + XX, + $this->position, + $this->file + ); + } + + public function &getIterator(): \Generator + { + yield $this->file; + } +} diff --git a/chandler/MVC/Routing/Exceptions/UnknownTypeAliasException.php b/chandler/MVC/Routing/Exceptions/UnknownTypeAliasException.php index ead1b83..c5e1fd5 100644 --- a/chandler/MVC/Routing/Exceptions/UnknownTypeAliasException.php +++ b/chandler/MVC/Routing/Exceptions/UnknownTypeAliasException.php @@ -1,5 +1,7 @@ -)%"; - const ALIAS_REGEX = "%{(\??\!?([A-z]++))}%"; - - private $url = NULL; + use TSimpleSingleton; + public const HANDLER_DELIMITER = "%([#@❤]|\->)%"; + public const ALIAS_REGEX = "%{(\??\!?([A-z]++))}%"; + + private $url = null; private $routes = []; private $statics = []; private $scope = []; - + private $events; - + protected $types = [ "num" => "(-?\d++)", "text" => "([A-z0-9]++)", "slug" => "([A-z0-9А-я\-_ ]++)", ]; - + private function __construct() { $this->events = EventDispatcher::i(); } - - private function computeRegExp(string $route, array $customAliases = [], ?string $prefix = NULL): string + + private function computeRegExp(string $route, array $customAliases = [], ?string $prefix = null): string { - $regexp = preg_replace_callback(Router::ALIAS_REGEX, function($matches) use ($customAliases) { - if($matches[1][0] === "?") { + $regexp = preg_replace_callback(Router::ALIAS_REGEX, function ($matches) use ($customAliases) { + if ($matches[1][0] === "?") { $replacement = !isset($customAliases[$matches[2]]) - ? NULL + ? null : ($matches[1][1] !== "!" ? "(" : "(?:") . $customAliases[$matches[2]] . ")"; } else { $replacement = $this->types[$matches[1]]; } - - if(!$replacement) { + + if (!$replacement) { $exMessage = "Unknown type alias: $matches[1]."; $exMessage .= " (Available options are: " . implode(", ", array_keys($this->types)); - if(sizeof($customAliases) > 0) + if (sizeof($customAliases) > 0) { $exMessage .= " or any of these user-defined aliases: " . implode(", ", array_keys($customAliases)) . ")"; - else + } else { $exMessage .= ")"; - + } + throw new Exceptions\UnknownTypeAliasException($exMessage); } - + return $replacement; }, addslashes($route)); - - if(!is_null($prefix)) { + + if (!is_null($prefix)) { $regexp = "\\/$prefix\\" . ($route === "/" ? "/" : "/$regexp"); } - + return "%^$regexp$%"; } - - function makeCSRFToken(Route $route, string $nonce): string + + public function makeCSRFToken(Route $route, string $nonce): string { $key = hash("snefru", CHANDLER_ROOT_CONF["security"]["secret"] . bin2hex($nonce)); - + $data = $route->namespace; $data .= Session::i()->get("tok", -1); - + return hash_hmac("snefru", $data, $key) . "#" . bin2hex($nonce); } - + private function setCSRFStatus(Route $route): void { - if(CHANDLER_ROOT_CONF["security"]["csrfProtection"] === "disabled") { + if (CHANDLER_ROOT_CONF["security"]["csrfProtection"] === "disabled") { $GLOBALS["csrfCheck"] = true; } else { $GLOBALS["csrfCheck"] = false; - + $hash = ($_GET["hash"] ?? ($_POST["hash"] ?? false)); - if($hash !== false) { + if ($hash !== false) { $data = explode("#", $hash); - + try { - if(!isset($data[0]) || !isset($data[1])) throw new \SodiumException; + if (!isset($data[0]) || !isset($data[1])) { + throw new \SodiumException(); + } [$hash, $nonce] = $data; - - if(sodium_memcmp($this->makeCSRFToken($route, hex2bin($nonce)), "$hash#$nonce") === 0) { - if(CHANDLER_ROOT_CONF["security"]["csrfProtection"] === "permissive") + + if (sodium_memcmp($this->makeCSRFToken($route, hex2bin($nonce)), "$hash#$nonce") === 0) { + if (CHANDLER_ROOT_CONF["security"]["csrfProtection"] === "permissive") { $GLOBALS["csrfCheck"] = true; - else if(CHANDLER_ROOT_CONF["security"]["csrfProtection"] === "strict") + } elseif (CHANDLER_ROOT_CONF["security"]["csrfProtection"] === "strict") { $GLOBALS["csrfCheck"] = parse_url($_SERVER["HTTP_REFERER"], PHP_URL_HOST) === $_SERVER["HTTP_HOST"]; - else + } else { trigger_error("Bad value for chandler.security.csrfProtection: disabled, permissive or strict expected.", E_USER_ERROR); + } } - } catch(\SodiumException $ex) {} + } catch (\SodiumException $ex) { + } } } - + $GLOBALS["csrfToken"] = $this->makeCSRFToken($route, openssl_random_pseudo_bytes(4)); } - + private function getDI(string $namespace): DI\Container { $loader = new DI\ContainerLoader(CHANDLER_ROOT . "/tmp/cache/di_$namespace", true); - $class = $loader->load(function($compiler) use ($namespace) { - $fileLoader = new \Nette\DI\Config\Loader; + $class = $loader->load(function ($compiler) use ($namespace) { + $fileLoader = new \Nette\DI\Config\Loader(); $fileLoader->addAdapter("yml", \Nette\DI\Config\Adapters\NeonAdapter::class); - + $compiler->loadConfig(CHANDLER_EXTENSIONS_ENABLED . "/$namespace/Web/di.yml", $fileLoader); }); - - return new $class; + + return new $class(); } - + private function getPresenter(string $namespace, string $presenterName): ?IPresenter { $di = $this->getDI($namespace); - - $services = $di->findByType("\\$namespace\\Web\\Presenters\\$presenterName" . "Presenter", false); - return $di->getService($services[0], false); + + $services = $di->findByType("\\$namespace\\Web\\Presenters\\$presenterName" . "Presenter"); + return $di->getService($services[0]); } - + private function delegateView(string $filename, IPresenter $presenter): string { return $presenter->getTemplatingEngine()->renderToString($filename, $this->scope); } - + private function delegateController(string $namespace, string $presenterName, string $action, array $parameters = []): string { $presenter = $this->getPresenter($namespace, $presenterName); $action = ucfirst($action); - + try { $presenter->onStartup(); $presenter->{"render$action"}(...$parameters); $presenter->onBeforeRender(); - + $this->scope += array_merge_recursive($presenter->getTemplateScope(), []); #TODO: add default parameters - #TODO: move this to delegateView - - $tpl = $this->scope["_template"] ?? "$presenterName/$action.xml"; - if($tpl[0] !== "/") { + #TODO: move this to delegateView + + $tpl = $this->scope["_template"] ?? "$presenterName/$action.latte"; + if ($tpl[0] !== "/") { $dir = CHANDLER_EXTENSIONS_ENABLED . "/$namespace/Web/Presenters/templates"; $tpl = "$dir/$tpl"; - if(isset($this->scope["_templatePath"])) + if (isset($this->scope["_templatePath"])) { $tpl = str_replace($dir, $this->scope["_templatePath"], $tpl); + } } - - if(!file_exists($tpl)) { + + if (!file_exists($tpl)) { trigger_error("Could not open $tpl as template, falling back.", E_USER_NOTICE); - $tpl = CHANDLER_EXTENSIONS_ENABLED . "/$namespace/Web/Presenters/templates/$presenterName/$action.xml"; + $tpl = CHANDLER_EXTENSIONS_ENABLED . "/$namespace/Web/Presenters/templates/$presenterName/$action.latte"; } //if(str_contains($presenterName, "Poll")) return json_encode($this->scope); $output = $this->delegateView($tpl, $presenter); - + $presenter->onAfterRender(); - } catch(InterruptedException $ex) {} - + } catch (InterruptedException $ex) { + } + $presenter->onStop(); $presenter->onDestruction(); - $presenter = NULL; - + $presenter = null; + return $output; } - + private function delegateRoute(Route $route, array $matches): string { $parameters = []; - - foreach($matches as $param) + + foreach ($matches as $param) { $parameters[] = is_numeric($param) ? (int) $param : $param; - + } + $this->setCSRFStatus($route); return $this->delegateController($route->namespace, $route->presenter, $route->action, $parameters); } - - function delegateStatic(string $namespace, string $path): string + + public function delegateStatic(string $namespace, string $path): string { $static = $static = $this->statics[$namespace]; - if(!isset($static)) return "Fatal error: no route"; - - if(!file_exists($file = "$static/$path")) + if (!isset($static)) { + return "Fatal error: no route"; + } + + if (!file_exists($file = "$static/$path")) { return "Fatal error: no resource"; - + } + $hash = "W/\"" . hash_file("snefru", $file) . "\""; - if(isset($_SERVER["HTTP_IF_NONE_MATCH"])) - if($_SERVER["HTTP_IF_NONE_MATCH"] === $hash) + if (isset($_SERVER["HTTP_IF_NONE_MATCH"])) { + if ($_SERVER["HTTP_IF_NONE_MATCH"] === $hash) { exit(header("HTTP/1.1 304")); - + } + } + header("Content-Type: " . system_extension_mime_type($file) ?? "text/plain; charset=unknown-8bit"); header("Content-Size: " . filesize($file)); header("Cache-Control: public, must-understand, immutable, max-age=628000000"); header("ETag: $hash"); - + readfile($file); - + exit; } - - function reverse(string $hotlink, ...$parameters): ?string + + public function reverse(string $hotlink, ...$parameters): ?string { - if(sizeof($j = explode("!", $hotlink)) === 2) + if (sizeof($j = explode("!", $hotlink)) === 2) { [$namespace, $hotlink] = $j; - else + } else { $namespace = explode("\\", $this->scope["parentModule"])[0]; - + } + [$presenter, $action] = preg_split(Router::HANDLER_DELIMITER, $hotlink); - - foreach($this->routes as $route) { - if($route->namespace !== $namespace || $route->presenter !== $presenter) continue; - if(!is_null($action) && $route->action != $action) continue; - + + foreach ($this->routes as $route) { + if ($route->namespace !== $namespace || $route->presenter !== $presenter) { + continue; + } + if (!is_null($action) && $route->action != $action) { + continue; + } + $count = preg_match_all(Router::ALIAS_REGEX, $route->raw); - if($count != sizeof($parameters)) continue; - + if ($count != sizeof($parameters)) { + continue; + } + $i = 0; - return preg_replace_callback(Router::ALIAS_REGEX, function() use ($parameters, &$i) { + return preg_replace_callback(Router::ALIAS_REGEX, function () use ($parameters, &$i) { return $parameters[$i++]; }, $route->raw); } - - return NULL; + + return null; } - - function push(?string $prefix, string $url, string $namespace, string $presenter, string $action, array $ph): void + + public function push(?string $prefix, string $url, string $namespace, string $presenter, string $action, array $ph): void { - $route = new Route; + $route = new Route(); $route->raw = $url; - if(!is_null($prefix)) + if (!is_null($prefix)) { $route->raw = "/$prefix" . $route->raw; - + } + $route->regex = $this->computeRegExp($url, $ph, $prefix); $route->namespace = $namespace; $route->presenter = $presenter; $route->action = $action; - + $this->routes[] = $route; } - - function pushStatic(string $namespace, string $path): void + + public function pushStatic(string $namespace, string $path): void { $this->statics[$namespace] = $path; } - - function readRoutes(string $filename, string $namespace, bool $autoprefix = true): void + + public function readRoutes(string $filename, string $namespace, bool $autoprefix = true): void { $config = chandler_parse_yaml($filename); - - if(isset($config["static"])) + + if (isset($config["static"])) { $this->pushStatic($namespace, CHANDLER_EXTENSIONS_ENABLED . "/$namespace/Web/$config[static]"); - - if(isset($config["include"])) - foreach($config["include"] as $include) + } + + if (isset($config["include"])) { + foreach ($config["include"] as $include) { $this->readRoutes(dirname($filename) . "/$include", $namespace, $autoprefix); - - foreach($config["routes"] as $route) { + } + } + + foreach ($config["routes"] as $route) { $route = (object) $route; $placeholders = $route->placeholders ?? []; [$presenter, $action] = preg_split(Router::HANDLER_DELIMITER, $route->handler); - - $this->push($autoprefix ? $namespace : NULL, $route->url, $namespace, $presenter, $action, $placeholders); + + $this->push($autoprefix ? $namespace : null, $route->url, $namespace, $presenter, $action, $placeholders); } } - - function getMatchingRoute(string $url): ?array + + public function getMatchingRoute(string $url): ?array { - foreach($this->routes as $route) - if(preg_match($route->regex, $url, $matches)) + foreach ($this->routes as $route) { + if (preg_match($route->regex, $url, $matches)) { return [$route, array_slice($matches, 1)]; - - return NULL; + } + } + + return null; } - - function execute(string $url, ?string $parentModule = null): ?string + + public function execute(string $url, ?string $parentModule = null): ?string { $this->scope = []; $this->url = chandler_escape_url((string) parse_url(preg_replace("%/+%", "/", $url), PHP_URL_PATH)); - - if(!is_null($parentModule)) { + + if (!is_null($parentModule)) { $GLOBALS["parentModule"] = $parentModule; $this->scope["parentModule"] = $GLOBALS["parentModule"]; } - - if(preg_match("%^\/assets\/packages\/static\/([A-z_\\-]++)\/(.++)$%", $this->url, $matches)) { + + if (preg_match("%^\/assets\/packages\/static\/([A-z_\\-]++)\/(.++)$%", $this->url, $matches)) { [$j, $namespace, $file] = $matches; return $this->delegateStatic($namespace, $file); } - + $match = $this->getMatchingRoute($this->url); - if(!$match) - return NULL; - + if (!$match) { + return null; + } + return $this->delegateRoute(...$match); } - - use TSimpleSingleton; } diff --git a/chandler/MVC/SimplePresenter.php b/chandler/MVC/SimplePresenter.php index 065b9ce..4de60a4 100644 --- a/chandler/MVC/SimplePresenter.php +++ b/chandler/MVC/SimplePresenter.php @@ -1,102 +1,75 @@ -template = (object) []; } - - function getTemplatingEngine(): TemplatingEngine + + public function getTemplatingEngine(): TemplatingEngine { - $latte = new TemplatingEngine; - $macros = new \Latte\Macros\MacroSet($latte->getCompiler()); + $latte = new TemplatingEngine(); + $latte->setTempDirectory(CHANDLER_ROOT . "/tmp/cache/templates"); - - $macros->addMacro("css", ' - $domain = "' . explode("\\", static::class)[0] . '"; - $file = (%node.array)[0]; - $realpath = CHANDLER_EXTENSIONS_ENABLED . "/$domain/Web/static/$file"; - if(file_exists($realpath)) { - $hash = "sha384-" . base64_encode(hash_file("sha384", $realpath, true)); - $mod = base_convert((string) (filemtime($realpath)), 10, 32); - echo ""; - } else { - echo ""; - } - '); - $macros->addMacro("script", ' - $domain = "' . explode("\\", static::class)[0] . '"; - $file = (%node.array)[0]; - $realpath = CHANDLER_EXTENSIONS_ENABLED . "/$domain/Web/static/$file"; - if(file_exists($realpath)) { - $hash = "sha384-" . base64_encode(hash_file("sha384", $realpath, true)); - $mod = base_convert((string) (filemtime($realpath)), 10, 32); - echo ""; - } else { - echo ""; - } - '); - $macros->addMacro("presenter", ' - $input = (%node.array); - - echo ""; - - $router = \Chandler\MVC\Routing\Router::i(); - $__out = $router->execute($router->reverse(...$input), "' . static::class . '"); - echo $__out; - - echo ""; - ' - ); - + $latte->addExtension(new \Latte\Bridges\Tracy\TracyExtension()); + $latte->addExtension(new \Latte\Essential\RawPhpExtension()); + + $latte->addExtension(new ChandlerExtension(static::class)); + return $latte; } - + protected function throwError(int $code = 400, string $desc = "Bad Request", string $message = ""): void { - if(!is_null($this->errorTemplate)) { + if (!is_null($this->errorTemplate)) { header("HTTP/1.0 $code $desc"); - - $ext = explode("\\", get_class($this))[0]; - $path = CHANDLER_EXTENSIONS_ENABLED . "/$ext/Web/Presenters/templates/" . $this->errorTemplate . ".xml"; - + + $ext = explode("\\", get_class($this))[0]; + $path = CHANDLER_EXTENSIONS_ENABLED . "/$ext/Web/Presenters/templates/" . $this->errorTemplate . ".latte"; + $latte = $this->getTemplatingEngine(); $latte->render($path, array_merge_recursive([ "code" => $code, "desc" => $desc, - "msg" => $message, + "msg" => $message, ], $this->getTemplateScope())); exit; } else { chandler_http_panic($code, $desc, $message); } } - + protected function assertNoCSRF(): void { - if(!$GLOBALS["csrfCheck"]) + if (!$GLOBALS["csrfCheck"]) { $this->throwError(400, "Bad Request", "CSRF token is missing or invalid."); + } } - + protected function terminate(): void { - throw new Exceptions\InterruptedException; + throw new Exceptions\InterruptedException(); } - + protected function notFound(): void { $this->throwError( @@ -105,131 +78,131 @@ protected function notFound(): void "The resource you are looking for has been deleted, had its name changed or doesn't exist." ); } - + protected function getCaller(): string { return $GLOBALS["parentModule"] ?? "libchandler:absolute.0"; } - + protected function redirect(string $location, int $code = 2): void { $code = 300 + $code; - if(($code <=> 300) !== 0 && $code > 399) return; - + if (($code <=> 300) !== 0 && $code > 399) { + return; + } + header("HTTP/1.1 $code"); header("Location: $location"); exit; } - + protected function pass(string $to, ...$args): void { - $args = array_merge([$to], $args); + $args = array_merge([$to], $args); $router = \Chandler\MVC\Routing\Router::i(); - $__out = $router->execute($router->reverse(...$args), "libchandler:absolute.0"); + $__out = $router->execute($router->reverse(...$args), "libchandler:absolute.0"); exit($__out); } - + protected function sendmail(string $to, string $template, array $params): void { - $emailDir = pathinfo($template, PATHINFO_DIRNAME); + $emailDir = pathinfo($template, PATHINFO_DIRNAME); $template .= ".eml.latte"; - - $renderedHTML = (new TemplatingEngine)->renderToString($template, $params); - $document = new \DOMDocument(); + + $renderedHTML = (new TemplatingEngine())->renderToString($template, $params); + $document = new \DOMDocument(); $document->loadHTML($renderedHTML, LIBXML_NOEMPTYTAG); - $querySel = new \DOMXPath($document); - + $querySel = new \DOMXPath($document); + $subject = $querySel->query("//title/text()")->item(0)->data; - - foreach($querySel->query("//link[@rel='stylesheet']") as $link) { + + foreach ($querySel->query("//link[@rel='stylesheet']") as $link) { $style = $document->createElement("style"); $style->setAttribute("id", uniqid("mail", true)); $style->appendChild(new \DOMText(file_get_contents("$emailDir/assets/css/" . $link->getAttribute("href")))); - + $link->parentNode->appendChild($style); $link->parentNode->removeChild($link); } - - foreach($querySel->query("//img") as $image) { + + foreach ($querySel->query("//img") as $image) { $imagePath = "$emailDir/assets/res/" . $image->getAttribute("src"); - $type = pathinfo($imagePath, PATHINFO_EXTENSION); - $contents = base64_encode(file_get_contents($imagePath)); + $type = pathinfo($imagePath, PATHINFO_EXTENSION); + $contents = base64_encode(file_get_contents($imagePath)); $image->setAttribute("src", "data:image/$type;base64,$contents"); } - + \Chandler\Email\Email::send($to, $subject, $document->saveHTML()); } - + protected function queryParam(string $index): ?string { - return $_GET[$index] ?? NULL; + return $_GET[$index] ?? null; } - + protected function postParam(string $index, bool $csrfCheck = true): ?string { - if($csrfCheck) + if ($csrfCheck) { $this->assertNoCSRF(); - - return $_POST[$index] ?? NULL; + } + + return $_POST[$index] ?? null; } - + protected function requestParam(string $index): ?string { - return $_REQUEST[$index] ?? NULL; + return $_REQUEST[$index] ?? null; } - + protected function jsonParam(string $index): ?string { - if(!isset($GLOBALS["jsonInputCache"])) + if (!isset($GLOBALS["jsonInputCache"])) { $GLOBALS["jsonInputCache"] = json_decode($this->queryParam("js") ?? file_get_contents("php://input"), true); - - return $GLOBALS["jsonInputCache"][$index] ?? NULL; + } + + return $GLOBALS["jsonInputCache"][$index] ?? null; } - + protected function findParam(string $index, array $searchIn = ["json", "post", "query"]): ?string { - $res = NULL; - foreach($searchIn as $search) { - if($search === "json") + $res = null; + foreach ($searchIn as $search) { + if ($search === "json") { $res = $this->jsonParam($index) ?? $res; - else if($search === "post") + } elseif ($search === "post") { $res = $this->postParam($index, false) ?? $res; - else if($search === "query") + } elseif ($search === "query") { $res = $this->queryParam($index) ?? $res; + } } - + return $res; } - + protected function checkbox(string $name): bool { return ($this->postParam($name) ?? "off") === "on"; } - - function getTemplateScope(): array + + public function getTemplateScope(): array { return (array) $this->template; } - - function onStartup(): void + + public function onStartup(): void { date_default_timezone_set("UTC"); } - - function onBeforeRender(): void + + public function onBeforeRender(): void { $this->template->csrfToken = $GLOBALS["csrfToken"]; } - - function onAfterRender(): void - {} - - function onStop(): void - {} - - function onDestruction(): void - {} - - use SmartObject; + + public function onAfterRender(): void {} + + public function onStop(): void {} + + public function onDestruction(): void {} } diff --git a/chandler/Patterns/ActiveRecord.php.old b/chandler/Patterns/ActiveRecord.php.old deleted file mode 100644 index fae4254..0000000 --- a/chandler/Patterns/ActiveRecord.php.old +++ /dev/null @@ -1,34 +0,0 @@ -db = DatabaseConnection::i(); - $this->table = $this->db->table($this->tableName); - if(!is_null($row)) $this->row = $row; - - $this->resetQuery(); - } - - private function resetQuery(): void - { - $this->query = clone $this->table; - } - - function __call() -} diff --git a/chandler/Patterns/TSimpleSingleton.php b/chandler/Patterns/TSimpleSingleton.php index 53b0b1c..6548046 100644 --- a/chandler/Patterns/TSimpleSingleton.php +++ b/chandler/Patterns/TSimpleSingleton.php @@ -1,16 +1,19 @@ -db = DatabaseConnection::i()->getContext(); $this->session = Session::i(); } - - private function verifySuRights(string $uId): bool - { - - } - + + private function verifySuRights(string $uId): bool {} + private function makeToken(string $user, string $ip, string $ua): string { $data = ["user" => $user, "ip" => $ip, "ua" => $ua]; @@ -27,16 +29,16 @@ private function makeToken(string $user, string $ip, string $ua): string ->table("ChandlerTokens") ->where($data) ->fetch(); - - if(!$token) { + + if (!$token) { $this->db->table("ChandlerTokens")->insert($data); $token = $this->db->table("ChandlerTokens")->where($data)->fetch(); } - + return $token->token; } - - static function verifyHash(string $input, string $hash): bool + + public static function verifyHash(string $input, string $hash): bool { try { [$hash, $salt] = explode("$", $hash); @@ -50,81 +52,94 @@ static function verifyHash(string $input, string $hash): bool SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13 ) ); - if(sodium_memcmp($hash, $userHash) !== 0) return false; - } catch(\SodiumException $ex) { + if (sodium_memcmp($hash, $userHash) !== 0) { + return false; + } + } catch (\SodiumException $ex) { return false; } - + return true; } - - function getUser(): ?User + + public function getUser(): ?User { $token = $this->session->get("tok"); - if(!$token) return null; - + if (!$token) { + return null; + } + $token = $this->db ->table("ChandlerTokens") ->where([ "token" => $token, ]) ->fetch(); - - if(!$token) return null; - + + if (!$token) { + return null; + } + $checksPassed = false; - if(CHANDLER_ROOT_CONF["security"]["extendedValidation"]) + if (CHANDLER_ROOT_CONF["security"]["extendedValidation"]) { $checksPassed = $token->ip === CONNECTING_IP && $token->ua === $_SERVER["HTTP_USER_AGENT"]; - else + } else { $checksPassed = true; - - if($checksPassed) { + } + + if ($checksPassed) { $su = $this->session->get("_su"); $user = $this->db->table("ChandlerUsers")->get($su ?? $token->user); - if(!$user) return null; - + if (!$user) { + return null; + } + return new User($user, !is_null($su)); } - + return null; } - - function authenticate(string $user): void + + public function authenticate(string $user): void { $this->session->set("tok", $this->makeToken($user, CONNECTING_IP, $_SERVER["HTTP_USER_AGENT"])); } - - function verifyCredentials(string $id, string $password): bool + + public function verifyCredentials(string $id, string $password): bool { $user = $this->db->table("ChandlerUsers")->get($id); - if(!$user) + if (!$user) { return false; - else if(!$this->verifyHash($password, $user->passwordHash)) + } elseif (!$this->verifyHash($password, $user->passwordHash)) { return false; - + } + return true; } - - function login(string $id, string $password): bool + + public function login(string $id, string $password): bool { - if(!$this->verifyCredentials($id, $password)) + if (!$this->verifyCredentials($id, $password)) { return false; - + } + $this->authenticate($id); return true; } - - function logout(bool $revoke = false): bool + + public function logout(bool $revoke = false): bool { $token = $this->session->get("tok"); - if(!$token) return false; - - if($revoke) $this->db->table("ChandlerTokens")->where("id", $token)->delete(); - - $this->session->set("tok", NULL); - + if (!$token) { + return false; + } + + if ($revoke) { + $this->db->table("ChandlerTokens")->where("id", $token)->delete(); + } + + $this->session->set("tok", null); + return true; } - - use TSimpleSingleton; } diff --git a/chandler/Security/Authorization/Permission.php b/chandler/Security/Authorization/Permission.php index 9dccc5f..e4636ee 100644 --- a/chandler/Security/Authorization/Permission.php +++ b/chandler/Security/Authorization/Permission.php @@ -1,15 +1,18 @@ -perm = new Permission; - + $this->perm = new Permission(); + $this->permissionManager = $permMan; } - - function can(string $action): PermissionBuilder + + public function can(string $action): PermissionBuilder { $this->perm->action = $action; - + return $this; } - - function model(string $model): PermissionBuilder + + public function model(string $model): PermissionBuilder { $this->perm->model = $model; - + return $this; } - - function whichBelongsTo(?int $to) + + public function whichBelongsTo(?int $to) { $this->perm->context = $to; - + return is_null($this->permissionManager) ? $this : $this->permissionManager->hasPermission($this->build()); } - - function build(): Permission + + public function build(): Permission { return $this->perm; } diff --git a/chandler/Security/Authorization/Permissions.php b/chandler/Security/Authorization/Permissions.php index 7bd5536..3a3d122 100644 --- a/chandler/Security/Authorization/Permissions.php +++ b/chandler/Security/Authorization/Permissions.php @@ -1,61 +1,69 @@ -db = DatabaseConnection::i()->getContext(); $this->user = $user; - + $this->init(); } - + private function init() { $uGroups = $this->user->getRaw()->related("ChandlerACLRelations.user")->order("priority ASC")->select("group"); - $groups = array_map(function($j) { + $groups = array_map(function ($j) { return $j->group; }, iterator_to_array($uGroups)); - + $permissionsAllowed = $this->db->table("ChandlerACLGroupsPermissions")->where("group IN (?)", $groups); $permissionsDenied = iterator_to_array((clone $permissionsAllowed)->where("status", false)); $permissionsDenied = array_merge($permissionsDenied, iterator_to_array($this->db->table("ChandlerACLUsersPermissions")->where("user", $this->user->getId()))); $permissionsAllowed = $permissionsAllowed->where("status", true); - - foreach($permissionsAllowed as $perm) { - foreach($permissionsDenied as $denied) - if($denied->model === $perm->model && $denied->context === $perm->context && $denied->permission === $perm->permission) + + foreach ($permissionsAllowed as $perm) { + foreach ($permissionsDenied as $denied) { + if ($denied->model === $perm->model && $denied->context === $perm->context && $denied->permission === $perm->permission) { continue 2; - - $pm = new Permission; + } + } + + $pm = new Permission(); $pm->action = $perm->permission; $pm->model = $perm->model; $pm->context = $perm->context; $pm->status = true; - + $this->perms[] = $pm; } } - - function getPermissions(): array + + public function getPermissions(): array { return $this->perms; } - - function hasPermission(Permission $pm): bool + + public function hasPermission(Permission $pm): bool { - foreach($this->perms as $perm) - if($perm->model === $pm->model && $perm->context === $pm->context && $perm->action === $pm->action) + foreach ($this->perms as $perm) { + if ($perm->model === $pm->model && $perm->context === $pm->context && $perm->action === $pm->action) { return true; - + } + } + return false; } } diff --git a/chandler/Security/User.php b/chandler/Security/User.php index f387385..7ef3899 100644 --- a/chandler/Security/User.php +++ b/chandler/Security/User.php @@ -1,5 +1,9 @@ - */ class User @@ -17,7 +21,7 @@ class User * @var \Nette\Database\Context DB Explorer */ private $db; - + /** * @var \Nette\Database\Table\ActiveRow ActiveRow that represents user */ @@ -26,21 +30,21 @@ class User * @var bool Does this user is not the one who is logged in, but substituted? */ private $tainted; - + /** * @param \Nette\Database\Table\ActiveRow $user ActiveRow that represents user * @param bool $tainted Does this user is not the one who is logged in, but substituted? */ - function __construct(ActiveRow $user, bool $tainted = false) + public function __construct(ActiveRow $user, bool $tainted = false) { $this->db = DatabaseConnection::i()->getContext(); $this->user = $user; $this->tainted = $tainted; } - + /** * Computes hash for a password. - * + * * @param string $password password * @return string hash */ @@ -55,128 +59,130 @@ private static function makeHash(string $password): string SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE, SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13 ); - + return bin2hex($hash) . "$" . bin2hex($salt); } - + /** * Get user's GUID. - * + * * @return string GUID */ - function getId(): string + public function getId(): string { return $this->user->id; } - + /** * Get user's DB data as an array. - * + * * @return array DB data in form of associative array */ - function getAttributes(): array + public function getAttributes(): array { return (array) $this->user; } - + /** * Get Permission Manager object. - * + * * @api * @see \Chandler\Security\User::can * @return \Chandler\Security\Authorization\Permissions Permission Manager */ - function getPermissions(): Permissions + public function getPermissions(): Permissions { return new Permissions($this); } - + /** * Get ActiveRow that represents user - * + * * @return \Nette\Database\Table\ActiveRow ActiveRow */ - function getRaw(): ActiveRow + public function getRaw(): ActiveRow { return $this->user; } - + /** * Checks if this user is not the one who is logged in, but substituted - * + * * @return bool Does this user is not the one who is logged in, but substituted? */ - function isTainted(): bool + public function isTainted(): bool { return $this->tainted; } - + /** * Begins to build permission for checking it's status using Permission Builder. * To get permission status you should chain methods like this: * $user->can('do something')->model('\app\Web\Models\MyModel')->whichBelongsTo(10); * In this case whichBelongsTo will automatically build permission and check if user * has it. If you need to build permission for something another use {@see \Chandler\Security\User::getPermissions}. - * + * * @api * @uses \Chandler\Security\Authorization\PermissionBuilder::can * @return \Chandler\Security\Authorization\Permissions Permission Manager */ - function can(string $action): PermissionBuilder + public function can(string $action): PermissionBuilder { $pb = new PermissionBuilder($this->getPermissions()); - + return $pb->can($action); } - + /** * Updates user password. * If $oldPassword parameter is passed it will update password only if current * user password (not the new one) matches $oldPassword. - * + * * @api * @param string $password New Password * @param string|null $oldPassword Current Password * @return bool False if token manipulation error has been thrown */ - function updatePassword(string $password, ?string $oldPassword = NULL): bool + public function updatePassword(string $password, ?string $oldPassword = null): bool { - if(!is_null($oldPassword)) - if(!Authenticator::verifyHash($oldPassword, $this->getRaw()->passwordHash)) + if (!is_null($oldPassword)) { + if (!Authenticator::verifyHash($oldPassword, $this->getRaw()->passwordHash)) { return false; - + } + } + $users = DatabaseConnection::i()->getContext()->table("ChandlerUsers"); $users->where("id", $this->getId())->update([ "passwordHash" => $this->makeHash($password), ]); - + return true; } - + /** * Creates new user if login has not been taken yet. - * + * * @api * @param string $login Login (usually an email) * @param string $password Password * @return self|null New user if successful, null otherwise */ - static function create(string $login, string $password): ?User + public static function create(string $login, string $password): ?User { $users = DatabaseConnection::i()->getContext()->table("ChandlerUsers"); $hash = self::makeHash($password); - + try { $users->insert([ "login" => $login, "passwordHash" => $hash, ]); - + $user = $users->where("login", $login)->fetch(); - } catch(UniqueConstraintViolationException $ex) { + } catch (UniqueConstraintViolationException $ex) { return null; } - + return new static($user); } } diff --git a/chandler/Session/Session.php b/chandler/Session/Session.php index 5f3c092..f0e37e8 100644 --- a/chandler/Session/Session.php +++ b/chandler/Session/Session.php @@ -1,15 +1,21 @@ - */ class Session { + use TSimpleSingleton; /** * @var array Associative array of session variables */ @@ -18,23 +24,24 @@ class Session * @var string Web-portal secret key */ private $key; - + /** * @internal */ private function __construct() { $this->key = strtr(CHANDLER_ROOT_CONF["security"]["secret"], "-_", "+/"); - - if(!isset($_COOKIE["CHANDLERSESS"])) + + if (!isset($_COOKIE["CHANDLERSESS"])) { $this->initSession(); - else + } else { $this->bootstrapData(); + } } - + /** * Sets CHANDLERSESS cookie to specified token. - * + * * @internal * @param string $token Token * @return void @@ -51,26 +58,26 @@ private function setSessionCookie(string $token): void true ); } - + /** * Calculates session token and sets session cookie value to it. * This function skips empty keys. - * + * * @internal * @return void */ private function updateSessionCookie(): void { - $this->data = array_filter($this->data, function($data) { + $this->data = array_filter($this->data, function ($data) { return !(is_null($data) && $data !== ""); }); - + $this->setSessionCookie(JWT::encode($this->data, ($this->key), "HS512")); } - + /** * Initializes session cookie with empty stub and loads no data. - * + * * @internal * @return void */ @@ -78,14 +85,14 @@ private function initSession(): void { $token = JWT::encode([], ($this->key), "HS512"); $this->setSessionCookie($token); - + $this->data = []; } - + /** * Reads data from cookie. * If cookie is corrupted, session terminates and starts again. - * + * * @internal * @uses \Chandler\Session\Session::initSession * @return void @@ -93,17 +100,17 @@ private function initSession(): void private function bootstrapData(): void { try { - $this->data = (array) JWT::decode($_COOKIE["CHANDLERSESS"], ($this->key), ["HS512"]); - } catch(\Exception $ex) { + $this->data = (array) JWT::decode($_COOKIE["CHANDLERSESS"], new Key($this->key, "HS512")); + } catch (\Exception $ex) { $this->initSession(); } } - + /** * Gets session variable. * May also set a variable if default value is present and * setting keys to default is permitted. - * + * * @api * @param string $key Session variable name * @param scalar $default Default value @@ -111,26 +118,24 @@ private function bootstrapData(): void * @uses \Chandler\Session\Session::set * @return scalar */ - function get(string $key, $default = null, bool $set = false) + public function get(string $key, $default = null, bool $set = false) { return $this->data[sha1($key)] ?? ($set ? $this->set($key, $default) : $default); } - + /** * Sets session variable. - * + * * @api * @param string $key Session variable name * @param scalar $value Value * @return scalar Value */ - function set(string $key, $value) + public function set(string $key, $value) { $this->data[sha1($key)] = $value; $this->updateSessionCookie(); - + return $value; } - - use TSimpleSingleton; } diff --git a/chandler/Signaling/SignalManager.php b/chandler/Signaling/SignalManager.php index b38793f..dbb6a54 100644 --- a/chandler/Signaling/SignalManager.php +++ b/chandler/Signaling/SignalManager.php @@ -1,15 +1,20 @@ - */ class SignalManager { + use TSimpleSingleton; /** * @var int Latest event timestamp. */ @@ -18,26 +23,26 @@ class SignalManager * @var \PDO PDO Connection to events SQLite DB. */ private $connection; - + /** * @internal */ private function __construct() { $this->since = time(); - $this->connection = new \PDO( - 'sqlite:' . CHANDLER_ROOT . '/tmp/events.bin', - null, - null, + $this->connection = new \PDO( + 'sqlite:' . CHANDLER_ROOT . '/tmp/events.bin', + null, + null, [\PDO::ATTR_PERSISTENT => true] ); $this->connection->query("CREATE TABLE IF NOT EXISTS pool(id INTEGER PRIMARY KEY AUTOINCREMENT, since INTEGER, for INTEGER, event TEXT);"); } - + /** * Waits for event for user with ID = $for. * This function is blocking. - * + * * @internal * @param int $for User ID * @return array|null Array of events if there are any, null otherwise @@ -47,93 +52,98 @@ private function eventFor(int $for): ?array $since = $this->since - 1; $statement = $this->connection->query("SELECT * FROM pool WHERE `for` = $for AND `since` > $since ORDER BY since DESC"); $event = $statement->fetch(\PDO::FETCH_LAZY); - if(!$event) return null; - + if (!$event) { + return null; + } + $this->since = time(); return [$event->id, unserialize(hex2bin($event->event))]; } - + /** * Set ups listener. * This function blocks the thread and calls $callback each time * a signal is recieved for user with ID = $for - * + * * @api * @param \Closure $callback Callback * @param int $for User ID * @uses \Chandler\Signaling\SignalManager::eventFor * @return void */ - function listen(\Closure $callback, int $for, int $time = 25): void + public function listen(\Closure $callback, int $for, int $time = 25): void { $this->since = time() - 1; - for($i = 0; $i < $time; $i++) { + for ($i = 0; $i < $time; $i++) { sleep(1); - + $event = $this->eventFor($for); - if(!$event) continue; - - list($id, $evt) = $event; - $id = crc32((string)$id); + if (!$event) { + continue; + } + + [$id, $evt] = $event; + $id = crc32((string) $id); $callback($evt, $id); } - + exit("[]"); } - + /** * Gets creation time of user's last event. * If there is no events returns 1. - * + * * @api * @param int $for User ID * @return int */ - function tipFor(int $for): int + public function tipFor(int $for): int { $statement = $this->connection->query("SELECT since FROM pool WHERE `for` = $for ORDER BY since DESC"); $result = $statement->fetch(\PDO::FETCH_LAZY); - if(!$result) return 1; - + if (!$result) { + return 1; + } + return $result->since; } - + /** * Gets history of long pool events. * If there is no events returns empty array. - * + * * @api * @param int $for User ID * @param int|null $tip last sync time * @return array */ - function getHistoryFor(int $for, ?int $tip = NULL, int $limit = 1000): array + public function getHistoryFor(int $for, ?int $tip = null, int $limit = 1000): array { $res = []; - $tip = $tip ?? $this->tipFor($for); + $tip ??= $this->tipFor($for); $query = $this->connection->query("SELECT * FROM pool WHERE `for` = $for AND `since` > $tip ORDER BY since DESC LIMIT $limit"); - foreach($query as $event) + foreach ($query as $event) { $res[] = unserialize(hex2bin($event["event"])); - + } + return $res; } - + /** * Triggers event for user and sends signal to DB and listeners. - * + * * @api * @param object $event Event * @param int $for User ID * @return bool Success state */ - function triggerEvent(object $event, int $for): bool + public function triggerEvent(object $event, int $for): bool { $event = bin2hex(serialize($event)); $since = time(); - + $this->connection->query("INSERT INTO pool VALUES (NULL, $since, $for, '$event')"); return true; } - - use TSimpleSingleton; } diff --git a/chandler/procedural/db_busy.php b/chandler/procedural/db_busy.php index a5472c7..d8963ca 100644 --- a/chandler/procedural/db_busy.php +++ b/chandler/procedural/db_busy.php @@ -1,8 +1,10 @@ - * @return void @@ -10,46 +12,46 @@ function chandler_db_busy(): void { $errPage = <<<'EOE' - - - - Resource Busy | Chandler App Server - - -
- Database Error -

Service Unavailable

- Server can't proccess your request at this moment. Please try again later. Sorry for that. -
- - -EOE; - + + + + Resource Busy | Chandler App Server + + +
+ Database Error +

Service Unavailable

+ Server can't proccess your request at this moment. Please try again later. Sorry for that. +
+ + + EOE; + header("HTTP/1.1 503 Service Unavailable"); exit($errPage); } diff --git a/chandler/procedural/escape_url.php b/chandler/procedural/escape_url.php index ae3b5f0..b40abc3 100644 --- a/chandler/procedural/escape_url.php +++ b/chandler/procedural/escape_url.php @@ -1,4 +1,6 @@ - "png", ]; - + static $types; - if(!isset($types)) + if (!isset($types)) { $types = system_extension_mime_types(); + } $ext = pathinfo($file, PATHINFO_EXTENSION); - if(!$ext) + if (!$ext) { $ext = $file; + } $ext = strtolower($ext); $ext = $aliases[$ext] ?? $ext; - return isset($types[$ext]) ? $types[$ext] : null; + return $types[$ext] ?? null; } function system_mime_type_extensions(): array @@ -46,16 +53,19 @@ function system_mime_type_extensions(): array # extension listed to be canonical). $out = []; $file = fopen('/etc/mime.types', 'r'); - while(($line = fgets($file)) !== false) { + while (($line = fgets($file)) !== false) { $line = trim(preg_replace('/#.*/', '', $line)); - if(!$line) + if (!$line) { continue; + } $parts = preg_split('/\s+/', $line); - if(count($parts) == 1) + if (count($parts) == 1) { continue; + } $type = array_shift($parts); - if(!isset($out[$type])) + if (!isset($out[$type])) { $out[$type] = array_shift($parts); + } } fclose($file); return $out; @@ -68,7 +78,8 @@ function system_mime_type_extension(string $type): ?string # # $type - the MIME type static $exts; - if(!isset($exts)) + if (!isset($exts)) { $exts = system_mime_type_extensions(); - return isset($exts[$type]) ? $exts[$type] : null; + } + return $exts[$type] ?? null; } diff --git a/chandler/procedural/panic.php b/chandler/procedural/panic.php index 39ad7d1..5346255 100644 --- a/chandler/procedural/panic.php +++ b/chandler/procedural/panic.php @@ -2,7 +2,7 @@ /** * Ends application and renders error page. - * + * * @api * @author kurotsun * @param int $code HTTP Error code @@ -13,45 +13,45 @@ function chandler_http_panic(int $code = 400, string $description = "Bad Request", string $message = ""): void { $error = << - - - - - - - - - -
- libchandler -
-
-
- Error summary - -

HTTP Error $code.0 - $description

-

$message

-
-
- - + + + + + + + + + + +
+ libchandler +
+
+
+ Error summary + +

HTTP Error $code.0 - $description

+

$message

+
+
+ + + + EOE; -EOE; - header("HTTP/1.0 $code $description"); exit($error); -} \ No newline at end of file +} diff --git a/chandler/procedural/yaml.php b/chandler/procedural/yaml.php index 47263e4..b9d368a 100644 --- a/chandler/procedural/yaml.php +++ b/chandler/procedural/yaml.php @@ -1,4 +1,5 @@ * @param string $filename Path to file @@ -20,20 +21,21 @@ function chandler_parse_yaml(string $filename): array { $cache = $GLOBALS["ymlCa"]; $id = sha1($filename); - + $result = $cache->load($id); - if(!$result) { - if(function_exists("yaml_parse_file")) + if (!$result) { + if (function_exists("yaml_parse_file")) { $result = yaml_parse_file($filename); - else + } else { $result = Yaml::parseFile($filename); - + } + $cache->save($id, $result, [ Cache::EXPIRE => "1 day", - Cache::SLIDING => TRUE, + Cache::SLIDING => true, Cache::FILES => $filename, ]); } - + return $result; } diff --git a/composer.json b/composer.json index 7ed21c6..f350728 100644 --- a/composer.json +++ b/composer.json @@ -1,22 +1,24 @@ { + "scripts": { + "fix": "php-cs-fixer fix", + "lint": "php-cs-fixer fix --dry-run --diff --verbose" + }, "require": { - "php": "~8.1", - - "nette/utils": "^3.0", - "nette/di": "^3.0", - "nette/database": "^3.0", + "php": "~8.2", + "nette/utils": "^4.0", + "nette/di": "^3.2", + "nette/database": "^3.2", "swiftmailer/swiftmailer": "^6.2", - "latte/latte": "^2.10", - "nette/safe-stream": "^2.4", - "nette/tokenizer": "^3.1", - "firebase/php-jwt": "^5.0", - "symfony/translation": "^5.0", - "symfony/yaml": "^5.3", - "guzzlehttp/guzzle": "^6.0", - "wildbit/postmark-php": "^4.0", - "tracy/tracy": "^2.10" + "latte/latte": "^3.1", + "firebase/php-jwt": "^6.11", + "symfony/yaml": "^7.3", + "wildbit/postmark-php": "^7.0", + "tracy/tracy": "^2.11" }, "suggest": { "ext-yaml": "for faster yaml parsing" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.90" } } diff --git a/composer.lock b/composer.lock index 1836506..4c29627 100644 --- a/composer.lock +++ b/composer.lock @@ -4,33 +4,34 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "95611a9a1aee4a0fcabe6cddd0d07593", + "content-hash": "5387d7805bbcc789e0484cbc3159d4a4", "packages": [ { "name": "doctrine/deprecations", - "version": "1.1.3", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -38,7 +39,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + "Doctrine\\Deprecations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -49,9 +50,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2024-01-30T19:34:25+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "doctrine/lexer", @@ -200,25 +201,31 @@ }, { "name": "firebase/php-jwt", - "version": "v5.5.1", + "version": "v6.11.1", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "83b609028194aa042ea33b5af2d41a7427de80e6" + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6", - "reference": "83b609028194aa042ea33b5af2d41a7427de80e6", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^8.0" }, "require-dev": { - "phpunit/phpunit": ">=4.8 <=9" + "guzzlehttp/guzzle": "^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" }, "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" }, "type": "library", @@ -251,43 +258,53 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v5.5.1" + "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" }, - "time": "2021-11-08T20:18:51+00:00" + "time": "2025-04-09T20:32:01+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "6.5.8", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a52f0440530b54fa079ce76e8c5d196a42cad981", - "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.9", - "php": ">=5.5", - "symfony/polyfill-intl-idn": "^1.17" + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" }, "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", - "psr/log": "^1.1" + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "6.5-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { @@ -340,19 +357,20 @@ } ], "description": "Guzzle is a PHP HTTP client library", - "homepage": "http://guzzlephp.org/", "keywords": [ "client", "curl", "framework", "http", "http client", + "psr-18", + "psr-7", "rest", "web service" ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/6.5.8" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -368,33 +386,37 @@ "type": "tidelift" } ], - "time": "2022-06-20T22:16:07+00:00" + "time": "2025-08-23T22:36:01+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.5.3", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/67ab6e18aaa14d753cc148911d273f6e6cb6721e", - "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\Promise\\": "src/" } @@ -431,7 +453,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.3" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -447,42 +469,48 @@ "type": "tidelift" } ], - "time": "2023-05-21T12:31:43+00:00" + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", - "version": "1.9.1", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b", - "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" }, "provide": { + "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "require-dev": { - "ext-zlib": "*", - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\Psr7\\": "src/" } @@ -521,6 +549,11 @@ "name": "Tobias Schultze", "email": "webmaster@tubo-world.de", "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], "description": "PSR-7 message implementation that also provides common utility methods", @@ -536,7 +569,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.9.1" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -552,40 +585,42 @@ "type": "tidelift" } ], - "time": "2023-04-17T16:00:37+00:00" + "time": "2025-08-23T21:21:41+00:00" }, { "name": "latte/latte", - "version": "v2.11.7", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/nette/latte.git", - "reference": "0ac0843a459790d471821f6a82f5d13db831a0d3" + "reference": "b6e3ad20c968b0aee9a8b7c8587592cdf1feea73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/latte/zipball/0ac0843a459790d471821f6a82f5d13db831a0d3", - "reference": "0ac0843a459790d471821f6a82f5d13db831a0d3", + "url": "https://api.github.com/repos/nette/latte/zipball/b6e3ad20c968b0aee9a8b7c8587592cdf1feea73", + "reference": "b6e3ad20c968b0aee9a8b7c8587592cdf1feea73", "shasum": "" }, "require": { "ext-json": "*", "ext-tokenizer": "*", - "php": "7.1 - 8.3" + "php": "8.2 - 8.5" }, "conflict": { - "nette/application": "<2.4.1" + "nette/application": "<3.1.7", + "nette/caching": "<3.1.4" }, "require-dev": { - "nette/php-generator": "^3.3.4", - "nette/tester": "^2.0", - "nette/utils": "^3.0", - "phpstan/phpstan": "^1", - "tracy/tracy": "^2.3" + "nette/php-generator": "^4.0", + "nette/tester": "^2.5", + "nette/utils": "^4.0", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.10" }, "suggest": { "ext-fileinfo": "to use filter |datastream", "ext-iconv": "to use filters |reverse, |substring", + "ext-intl": "to use Latte\\Engine::setLocale()", "ext-mbstring": "to use filters like lower, upper, capitalize, ...", "nette/php-generator": "to use tag {templatePrint}", "nette/utils": "to use filter |webalize" @@ -596,10 +631,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.11-dev" + "dev-master": "3.1-dev" } }, "autoload": { + "psr-4": { + "Latte\\": "src/Latte" + }, "classmap": [ "src/" ] @@ -634,34 +672,37 @@ ], "support": { "issues": "https://github.com/nette/latte/issues", - "source": "https://github.com/nette/latte/tree/v2.11.7" + "source": "https://github.com/nette/latte/tree/v3.1.0" }, - "time": "2023-10-18T17:16:11+00:00" + "time": "2025-11-26T04:28:31+00:00" }, { "name": "nette/caching", - "version": "v3.2.3", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/nette/caching.git", - "reference": "6821d74c1db82c493c02c47f6485022d79b63176" + "reference": "a1c13221b350d0db0a2bd4a77c1e7b65e0aa065d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/caching/zipball/6821d74c1db82c493c02c47f6485022d79b63176", - "reference": "6821d74c1db82c493c02c47f6485022d79b63176", + "url": "https://api.github.com/repos/nette/caching/zipball/a1c13221b350d0db0a2bd4a77c1e7b65e0aa065d", + "reference": "a1c13221b350d0db0a2bd4a77c1e7b65e0aa065d", "shasum": "" }, "require": { - "nette/finder": "^2.4 || ^3.0", - "nette/utils": "^3.2 || ~4.0.0", - "php": "8.0 - 8.3" + "nette/utils": "^4.0", + "php": "8.1 - 8.5" + }, + "conflict": { + "latte/latte": "<3.0.12" }, "require-dev": { - "latte/latte": "^2.11 || ^3.0", + "latte/latte": "^3.0.12", "nette/di": "^3.1 || ^4.0", "nette/tester": "^2.4", - "phpstan/phpstan": "^1.0", + "phpstan/phpstan-nette": "^2.0@stable", + "psr/simple-cache": "^2.0 || ^3.0", "tracy/tracy": "^2.9" }, "suggest": { @@ -670,10 +711,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.4-dev" } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -705,48 +749,48 @@ ], "support": { "issues": "https://github.com/nette/caching/issues", - "source": "https://github.com/nette/caching/tree/v3.2.3" + "source": "https://github.com/nette/caching/tree/v3.4.0" }, - "time": "2023-09-26T11:12:20+00:00" + "time": "2025-08-06T23:05:08+00:00" }, { "name": "nette/database", - "version": "v3.1.9", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/nette/database.git", - "reference": "4a21417d545e226a8fe189b95111d23a454bd2b3" + "reference": "1a84d3e61aa33461a3d6415235b25a7cd8b3f442" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/database/zipball/4a21417d545e226a8fe189b95111d23a454bd2b3", - "reference": "4a21417d545e226a8fe189b95111d23a454bd2b3", + "url": "https://api.github.com/repos/nette/database/zipball/1a84d3e61aa33461a3d6415235b25a7cd8b3f442", + "reference": "1a84d3e61aa33461a3d6415235b25a7cd8b3f442", "shasum": "" }, "require": { "ext-pdo": "*", - "nette/caching": "^3.0", - "nette/utils": "^3.2.1 || ~4.0.0", - "php": "7.2 - 8.3" - }, - "conflict": { - "nette/di": "<3.0-stable" + "nette/caching": "^3.2", + "nette/utils": "^4.0", + "php": "8.1 - 8.5" }, "require-dev": { - "jetbrains/phpstorm-attributes": "^1.0", - "mockery/mockery": "^1.3.4", - "nette/di": "^v3.0", - "nette/tester": "^2.4", - "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.4" + "jetbrains/phpstorm-attributes": "^1.2", + "mockery/mockery": "^1.6@stable", + "nette/di": "^3.1", + "nette/tester": "^2.5", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -783,45 +827,49 @@ ], "support": { "issues": "https://github.com/nette/database/issues", - "source": "https://github.com/nette/database/tree/v3.1.9" + "source": "https://github.com/nette/database/tree/v3.2.8" }, - "time": "2023-11-05T19:41:36+00:00" + "time": "2025-10-30T22:06:23+00:00" }, { "name": "nette/di", - "version": "v3.1.10", + "version": "v3.2.5", "source": { "type": "git", "url": "https://github.com/nette/di.git", - "reference": "2645ec3eaa17fa2ab87c5eb4eaacb1fe6dd28284" + "reference": "5708c328ce7658a73c96b14dd6da7b8b27bf220f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/di/zipball/2645ec3eaa17fa2ab87c5eb4eaacb1fe6dd28284", - "reference": "2645ec3eaa17fa2ab87c5eb4eaacb1fe6dd28284", + "url": "https://api.github.com/repos/nette/di/zipball/5708c328ce7658a73c96b14dd6da7b8b27bf220f", + "reference": "5708c328ce7658a73c96b14dd6da7b8b27bf220f", "shasum": "" }, "require": { + "ext-ctype": "*", "ext-tokenizer": "*", - "nette/neon": "^3.3 || ^4.0", - "nette/php-generator": "^3.5.4 || ^4.0", - "nette/robot-loader": "^3.2 || ~4.0.0", + "nette/neon": "^3.3", + "nette/php-generator": "^4.1.6", + "nette/robot-loader": "^4.0", "nette/schema": "^1.2.5", - "nette/utils": "^3.2.5 || ~4.0.0", - "php": "7.2 - 8.3" + "nette/utils": "^4.0", + "php": "8.1 - 8.5" }, "require-dev": { - "nette/tester": "^2.4", - "phpstan/phpstan": "^1.0", + "nette/tester": "^2.5.2", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -855,98 +903,31 @@ ], "support": { "issues": "https://github.com/nette/di/issues", - "source": "https://github.com/nette/di/tree/v3.1.10" - }, - "time": "2024-02-06T01:19:44+00:00" - }, - { - "name": "nette/finder", - "version": "v2.6.0", - "source": { - "type": "git", - "url": "https://github.com/nette/finder.git", - "reference": "991aefb42860abeab8e003970c3809a9d83cb932" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/finder/zipball/991aefb42860abeab8e003970c3809a9d83cb932", - "reference": "991aefb42860abeab8e003970c3809a9d83cb932", - "shasum": "" - }, - "require": { - "nette/utils": "^2.4 || ^3.0", - "php": ">=7.1" - }, - "conflict": { - "nette/nette": "<2.2" - }, - "require-dev": { - "nette/tester": "^2.0", - "phpstan/phpstan": "^0.12", - "tracy/tracy": "^2.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🔍 Nette Finder: find files and directories with an intuitive API.", - "homepage": "https://nette.org", - "keywords": [ - "filesystem", - "glob", - "iterator", - "nette" - ], - "support": { - "issues": "https://github.com/nette/finder/issues", - "source": "https://github.com/nette/finder/tree/v2.6.0" + "source": "https://github.com/nette/di/tree/v3.2.5" }, - "time": "2022-10-13T01:31:15+00:00" + "time": "2025-08-14T22:59:46+00:00" }, { "name": "nette/neon", - "version": "v3.4.4", + "version": "v3.4.6", "source": { "type": "git", "url": "https://github.com/nette/neon.git", - "reference": "3411aa86b104e2d5b7e760da4600865ead963c3c" + "reference": "36e3f4f89fd8a7b89ada74c7a678baa9f7cc7719" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/neon/zipball/3411aa86b104e2d5b7e760da4600865ead963c3c", - "reference": "3411aa86b104e2d5b7e760da4600865ead963c3c", + "url": "https://api.github.com/repos/nette/neon/zipball/36e3f4f89fd8a7b89ada74c7a678baa9f7cc7719", + "reference": "36e3f4f89fd8a7b89ada74c7a678baa9f7cc7719", "shasum": "" }, "require": { "ext-json": "*", - "php": "8.0 - 8.4" + "php": "8.0 - 8.5" }, "require-dev": { "nette/tester": "^2.4", - "phpstan/phpstan": "^1.0", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.7" }, "bin": [ @@ -959,6 +940,9 @@ } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -980,7 +964,7 @@ } ], "description": "🍸 Nette NEON: encodes and decodes NEON file format.", - "homepage": "https://ne-on.org", + "homepage": "https://neon.nette.org", "keywords": [ "export", "import", @@ -990,33 +974,33 @@ ], "support": { "issues": "https://github.com/nette/neon/issues", - "source": "https://github.com/nette/neon/tree/v3.4.4" + "source": "https://github.com/nette/neon/tree/v3.4.6" }, - "time": "2024-10-04T22:00:08+00:00" + "time": "2025-11-25T13:12:06+00:00" }, { "name": "nette/php-generator", - "version": "v4.1.6", + "version": "v4.2.0", "source": { "type": "git", "url": "https://github.com/nette/php-generator.git", - "reference": "c90961e782ae86e517fe5ed732eb2b512945565b" + "reference": "4707546a1f11badd72f5d82af4f8a6bc64bd56ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/php-generator/zipball/c90961e782ae86e517fe5ed732eb2b512945565b", - "reference": "c90961e782ae86e517fe5ed732eb2b512945565b", + "url": "https://api.github.com/repos/nette/php-generator/zipball/4707546a1f11badd72f5d82af4f8a6bc64bd56ac", + "reference": "4707546a1f11badd72f5d82af4f8a6bc64bd56ac", "shasum": "" }, "require": { - "nette/utils": "^3.2.9 || ^4.0", - "php": "8.0 - 8.4" + "nette/utils": "^4.0.6", + "php": "8.1 - 8.5" }, "require-dev": { - "jetbrains/phpstorm-attributes": "dev-master", + "jetbrains/phpstorm-attributes": "^1.2", "nette/tester": "^2.4", - "nikic/php-parser": "^4.18 || ^5.0", - "phpstan/phpstan": "^1.0", + "nikic/php-parser": "^5.0", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.8" }, "suggest": { @@ -1025,10 +1009,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -1049,7 +1036,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.3 features.", + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.5 features.", "homepage": "https://nette.org", "keywords": [ "code", @@ -1059,42 +1046,44 @@ ], "support": { "issues": "https://github.com/nette/php-generator/issues", - "source": "https://github.com/nette/php-generator/tree/v4.1.6" + "source": "https://github.com/nette/php-generator/tree/v4.2.0" }, - "time": "2024-09-10T09:31:55+00:00" + "time": "2025-08-06T18:24:31+00:00" }, { "name": "nette/robot-loader", - "version": "v3.4.2", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/nette/robot-loader.git", - "reference": "970c8f82be98ec54180c88a468cd2b057855d993" + "reference": "805fb81376c24755d50bdb8bc69ca4db3def71d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/robot-loader/zipball/970c8f82be98ec54180c88a468cd2b057855d993", - "reference": "970c8f82be98ec54180c88a468cd2b057855d993", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/805fb81376c24755d50bdb8bc69ca4db3def71d1", + "reference": "805fb81376c24755d50bdb8bc69ca4db3def71d1", "shasum": "" }, "require": { "ext-tokenizer": "*", - "nette/finder": "^2.5 || ^3.0", - "nette/utils": "^3.0", - "php": ">=7.1" + "nette/utils": "^4.0", + "php": "8.1 - 8.5" }, "require-dev": { - "nette/tester": "^2.0", - "phpstan/phpstan": "^0.12", - "tracy/tracy": "^2.3" + "nette/tester": "^2.4", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-master": "4.1-dev" } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -1126,41 +1115,45 @@ ], "support": { "issues": "https://github.com/nette/robot-loader/issues", - "source": "https://github.com/nette/robot-loader/tree/v3.4.2" + "source": "https://github.com/nette/robot-loader/tree/v4.1.0" }, - "time": "2022-12-14T15:41:06+00:00" + "time": "2025-08-06T18:34:21+00:00" }, { - "name": "nette/safe-stream", - "version": "v2.5.1", + "name": "nette/schema", + "version": "v1.3.3", "source": { "type": "git", - "url": "https://github.com/nette/safe-stream.git", - "reference": "96c57055927d0f2b4d0fe545896a7a0335adbeb5" + "url": "https://github.com/nette/schema.git", + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/safe-stream/zipball/96c57055927d0f2b4d0fe545896a7a0335adbeb5", - "reference": "96c57055927d0f2b4d0fe545896a7a0335adbeb5", + "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", "shasum": "" }, "require": { - "php": ">=7.1" + "nette/utils": "^4.0", + "php": "8.1 - 8.5" }, "require-dev": { - "nette/tester": "^2.0", - "phpstan/phpstan": "^0.12", - "tracy/tracy": "^2.3" + "nette/tester": "^2.5.2", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "1.3-dev" } }, "autoload": { - "files": [ - "src/loader.php" + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1179,52 +1172,63 @@ "homepage": "https://nette.org/contributors" } ], - "description": "Nette SafeStream: provides isolation for thread safe manipulation with files via native PHP functions.", + "description": "📐 Nette Schema: validating data structures against a given Schema.", "homepage": "https://nette.org", "keywords": [ - "atomic", - "filesystem", - "isolation", - "nette", - "safe", - "thread safe" + "config", + "nette" ], "support": { - "issues": "https://github.com/nette/safe-stream/issues", - "source": "https://github.com/nette/safe-stream/tree/v2.5.1" + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.3" }, - "time": "2022-12-12T17:17:33+00:00" + "time": "2025-10-30T22:57:59+00:00" }, { - "name": "nette/schema", - "version": "v1.2.5", + "name": "nette/utils", + "version": "v4.0.9", "source": { "type": "git", - "url": "https://github.com/nette/schema.git", - "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a" + "url": "https://github.com/nette/utils.git", + "reference": "505a30ad386daa5211f08a318e47015b501cad30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a", - "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a", + "url": "https://api.github.com/repos/nette/utils/zipball/505a30ad386daa5211f08a318e47015b501cad30", + "reference": "505a30ad386daa5211f08a318e47015b501cad30", "shasum": "" }, "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": "7.1 - 8.3" + "php": "8.0 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" }, "require-dev": { - "nette/tester": "^2.3 || ^2.4", - "phpstan/phpstan-nette": "^1.0", - "tracy/tracy": "^2.7" + "jetbrains/phpstorm-attributes": "^1.2", + "nette/tester": "^2.5", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "4.0-dev" } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -1245,173 +1249,149 @@ "homepage": "https://nette.org/contributors" } ], - "description": "📐 Nette Schema: validating data structures against a given Schema.", + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", "homepage": "https://nette.org", "keywords": [ - "config", - "nette" + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" ], "support": { - "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.5" + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.9" }, - "time": "2023-10-05T20:37:59+00:00" + "time": "2025-10-31T00:45:47+00:00" }, { - "name": "nette/tokenizer", - "version": "v3.1.1", + "name": "psr/http-client", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/nette/tokenizer.git", - "reference": "370c5e4e2e10eb4d3e406d3a90526f821de98190" + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/tokenizer/zipball/370c5e4e2e10eb4d3e406d3a90526f821de98190", - "reference": "370c5e4e2e10eb4d3e406d3a90526f821de98190", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "require-dev": { - "nette/tester": "~2.0", - "phpstan/phpstan": "^0.12", - "tracy/tracy": "^2.3" + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" + "MIT" ], "authors": [ { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Nette Tokenizer", - "homepage": "https://nette.org", + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], "support": { - "source": "https://github.com/nette/tokenizer/tree/v3.1.1" + "source": "https://github.com/php-fig/http-client" }, - "abandoned": true, - "time": "2022-02-09T22:28:54+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { - "name": "nette/utils", - "version": "v3.2.10", + "name": "psr/http-factory", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/nette/utils.git", - "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2" + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/a4175c62652f2300c8017fb7e640f9ccb11648d2", - "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.2 <8.4" - }, - "conflict": { - "nette/di": "<3.0.6" - }, - "require-dev": { - "jetbrains/phpstorm-attributes": "dev-master", - "nette/tester": "~2.0", - "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" - }, - "suggest": { - "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", - "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", - "ext-json": "to use Nette\\Utils\\Json", - "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" + "MIT" ], "authors": [ { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", - "homepage": "https://nette.org", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ - "array", - "core", - "datetime", - "images", - "json", - "nette", - "paginator", - "password", - "slugify", - "string", - "unicode", - "utf-8", - "utility", - "validation" + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" ], "support": { - "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.10" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-07-30T15:38:18+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", - "version": "1.1", + "version": "2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { @@ -1420,7 +1400,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1435,7 +1415,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -1449,9 +1429,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/1.1" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "time": "2023-04-04T09:50:52+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { "name": "ralouphie/getallheaders", @@ -1575,16 +1555,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -1592,12 +1572,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -1622,7 +1602,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -1638,11 +1618,11 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -1666,8 +1646,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1701,7 +1681,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -1712,6 +1692,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -1721,16 +1705,16 @@ }, { "name": "symfony/polyfill-iconv", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956" + "reference": "5f3b930437ae03ae5dff61269024d8ea1b3774aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/48becf00c920479ca2e910c22a5a39e5d47ca956", - "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/5f3b930437ae03ae5dff61269024d8ea1b3774aa", + "reference": "5f3b930437ae03ae5dff61269024d8ea1b3774aa", "shasum": "" }, "require": { @@ -1745,8 +1729,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1781,7 +1765,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.33.0" }, "funding": [ { @@ -1792,25 +1776,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-17T14:58:18+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { @@ -1823,8 +1811,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1864,7 +1852,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" }, "funding": [ { @@ -1875,16 +1863,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -1905,8 +1897,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1945,7 +1937,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -1956,6 +1948,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -1965,19 +1961,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -1989,8 +1986,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2025,7 +2022,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -2036,25 +2033,2017 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/90208e2fc6f68f613eae7ca25a2458a931b1bacc", + "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-09-27T09:00:46+00:00" + }, + { + "name": "tracy/tracy", + "version": "v2.11.0", + "source": { + "type": "git", + "url": "https://github.com/nette/tracy.git", + "reference": "eec57bcf2ff11d79f519a19da9d7ae1e2c63c42e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/tracy/zipball/eec57bcf2ff11d79f519a19da9d7ae1e2c63c42e", + "reference": "eec57bcf2ff11d79f519a19da9d7ae1e2c63c42e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-session": "*", + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/di": "<3.0" + }, + "require-dev": { + "latte/latte": "^2.5 || ^3.0", + "nette/di": "^3.0", + "nette/http": "^3.0", + "nette/mail": "^3.0 || ^4.0", + "nette/tester": "^2.2", + "nette/utils": "^3.0 || ^4.0", + "phpstan/phpstan-nette": "^2.0@stable", + "psr/log": "^1.0 || ^2.0 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.11-dev" + } + }, + "autoload": { + "files": [ + "src/Tracy/functions.php" + ], + "psr-4": { + "Tracy\\": "src" + }, + "classmap": [ + "src" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "😎 Tracy: the addictive tool to ease debugging PHP code for cool developers. Friendly design, logging, profiler, advanced features like debugging AJAX calls or CLI support. You will love it.", + "homepage": "https://tracy.nette.org", + "keywords": [ + "Xdebug", + "debug", + "debugger", + "nette", + "profiler" + ], + "support": { + "issues": "https://github.com/nette/tracy/issues", + "source": "https://github.com/nette/tracy/tree/v2.11.0" + }, + "time": "2025-10-31T00:12:50+00:00" + }, + { + "name": "wildbit/postmark-php", + "version": "v7.0.0", + "source": { + "type": "git", + "url": "https://github.com/ActiveCampaign/postmark-php.git", + "reference": "f0a53b741833d4b2d1a3827c0bd66e70f4d26d62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ActiveCampaign/postmark-php/zipball/f0a53b741833d4b2d1a3827c0bd66e70f4d26d62", + "reference": "f0a53b741833d4b2d1a3827c0bd66e70f4d26d62", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.8", + "php": "~8.1 || ~8.2|| ~8.3 || ~8.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.40", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Postmark\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The officially supported client for Postmark (http://postmarkapp.com)", + "support": { + "issues": "https://github.com/ActiveCampaign/postmark-php/issues", + "source": "https://github.com/ActiveCampaign/postmark-php/tree/v7.0.0" + }, + "time": "2025-07-11T11:40:30+00:00" + } + ], + "packages-dev": [ + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.90.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "ad732c2e9299c9743f9c55ae53cc0e7642ab1155" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/ad732c2e9299c9743f9c55ae53cc0e7642ab1155", + "reference": "ad732c2e9299c9743f9c55ae53cc0e7642ab1155", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.3", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.3", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.6", + "react/event-loop": "^1.5", + "react/socket": "^1.16", + "react/stream": "^1.4", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.33", + "symfony/polyfill-php80": "^1.33", + "symfony/polyfill-php81": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.7", + "infection/infection": "^0.31.0", + "justinrainbow/json-schema": "^6.5", + "keradus/cli-executor": "^2.2", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.9", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", + "phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34", + "symfony/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2 || ^8.0", + "symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "exclude-from-classmap": [ + "src/Fixer/Internal/*" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.90.0" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2025-11-20T15:15:16+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.6", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.6" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-01-01T16:37:48+00:00" + }, + { + "name": "react/dns", + "version": "v1.14.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.14.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-18T19:34:28+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-17T20:46:25+00:00" + }, + { + "name": "react/promise", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" + }, + { + "name": "react/socket", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.17.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-19T20:47:34+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "symfony/console", + "version": "v7.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.3.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-04T01:21:42+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-08-13T11:49:31+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a", + "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.3.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-05T09:52:27+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "9f696d2f1e340484b4683f7853b273abff94421f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", + "reference": "9f696d2f1e340484b4683f7853b273abff94421f", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-10-15T18:45:57+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-08-05T10:16:07+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -2063,8 +4052,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2105,7 +4094,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -2116,73 +4105,50 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { - "name": "symfony/translation", - "version": "v5.4.44", + "name": "symfony/polyfill-php81", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "6fed3a20b5b87ee9cdd9dacf545922b8fd475921" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/6fed3a20b5b87ee9cdd9dacf545922b8fd475921", - "reference": "6fed3a20b5b87ee9cdd9dacf545922b8fd475921", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/translation-contracts": "^2.3" - }, - "conflict": { - "symfony/config": "<4.4", - "symfony/console": "<5.3", - "symfony/dependency-injection": "<5.0", - "symfony/http-kernel": "<5.0", - "symfony/twig-bundle": "<5.0", - "symfony/yaml": "<4.4" - }, - "provide": { - "symfony/translation-implementation": "2.3" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/http-client-contracts": "^1.1|^2.0|^3.0", - "symfony/http-kernel": "^5.0|^6.0", - "symfony/intl": "^4.4|^5.0|^6.0", - "symfony/polyfill-intl-icu": "^1.21", - "symfony/service-contracts": "^1.1.2|^2|^3", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log-implementation": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" + "php": ">=7.2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { "files": [ - "Resources/functions.php" + "bootstrap.php" ], "psr-4": { - "Symfony\\Component\\Translation\\": "" + "Symfony\\Polyfill\\Php81\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2191,18 +4157,24 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides tools to internationalize your application", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/translation/tree/v5.4.44" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" }, "funding": [ { @@ -2213,47 +4185,51 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-15T08:12:35+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/translation-contracts", - "version": "v2.5.3", + "name": "symfony/polyfill-php84", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/translation-contracts.git", - "reference": "b0073a77ac0b7ea55131020e87b1e3af540f4664" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b0073a77ac0b7ea55131020e87b1e3af540f4664", - "reference": "b0073a77ac0b7ea55131020e87b1e3af540f4664", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", "shasum": "" }, "require": { - "php": ">=7.2.5" - }, - "suggest": { - "symfony/translation-implementation": "" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Contracts\\Translation\\": "" - } + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2269,18 +4245,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to translation", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.5.3" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" }, "funding": [ { @@ -2291,48 +4265,38 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-01-23T13:51:25+00:00" + "time": "2025-06-24T13:30:11+00:00" }, { - "name": "symfony/yaml", - "version": "v5.4.44", + "name": "symfony/process", + "version": "v7.3.4", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "7025b964f123bbf1896d7563db6ec7f1f63e918a" + "url": "https://github.com/symfony/process.git", + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/7025b964f123bbf1896d7563db6ec7f1f63e918a", - "reference": "7025b964f123bbf1896d7563db6ec7f1f63e918a", + "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<5.3" - }, - "require-dev": { - "symfony/console": "^5.3|^6.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "php": ">=8.2" }, - "bin": [ - "Resources/bin/yaml-lint" - ], "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Yaml\\": "" + "Symfony\\Component\\Process\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2352,10 +4316,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Loads and dumps YAML files", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.44" + "source": "https://github.com/symfony/process/tree/v7.3.4" }, "funding": [ { @@ -2366,136 +4330,265 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-16T14:36:56+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { - "name": "tracy/tracy", - "version": "v2.10.8", + "name": "symfony/service-contracts", + "version": "v3.6.1", "source": { "type": "git", - "url": "https://github.com/nette/tracy.git", - "reference": "0e0f3312708fb9c179a92072ebacc24aeee7e2e8" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/tracy/zipball/0e0f3312708fb9c179a92072ebacc24aeee7e2e8", - "reference": "0e0f3312708fb9c179a92072ebacc24aeee7e2e8", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { - "ext-json": "*", - "ext-session": "*", - "php": "8.0 - 8.4" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "nette/di": "<3.0" - }, - "require-dev": { - "latte/latte": "^2.5 || ^3.0", - "nette/di": "^3.0", - "nette/http": "^3.0", - "nette/mail": "^3.0 || ^4.0", - "nette/tester": "^2.2", - "nette/utils": "^3.0 || ^4.0", - "phpstan/phpstan": "^1.0", - "psr/log": "^1.0 || ^2.0 || ^3.0" + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "2.10-dev" + "dev-main": "3.6-dev" } }, "autoload": { - "files": [ - "src/Tracy/functions.php" - ], - "classmap": [ - "src" + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "😎 Tracy: the addictive tool to ease debugging PHP code for cool developers. Friendly design, logging, profiler, advanced features like debugging AJAX calls or CLI support. You will love it.", - "homepage": "https://tracy.nette.org", + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", "keywords": [ - "Xdebug", - "debug", - "debugger", - "nette", - "profiler" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "issues": "https://github.com/nette/tracy/issues", - "source": "https://github.com/nette/tracy/tree/v2.10.8" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, - "time": "2024-08-07T02:04:53+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" }, { - "name": "wildbit/postmark-php", - "version": "v4.0.5", + "name": "symfony/stopwatch", + "version": "v7.3.0", "source": { "type": "git", - "url": "https://github.com/ActiveCampaign/postmark-php.git", - "reference": "b71efba061de7cf7e1f853d211b1c5edce4e3c5b" + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-24T10:49:57+00:00" + }, + { + "name": "symfony/string", + "version": "v7.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f96476035142921000338bad71e5247fbc138872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ActiveCampaign/postmark-php/zipball/b71efba061de7cf7e1f853d211b1c5edce4e3c5b", - "reference": "b71efba061de7cf7e1f853d211b1c5edce4e3c5b", + "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", + "reference": "f96476035142921000338bad71e5247fbc138872", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "^6.0|^7.0", - "php": ">=7.0.0" + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "phpunit/phpunit": "^6.0.0" + "symfony/emoji": "^7.1", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { - "psr-0": { - "Postmark\\": "src/" - } + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "The officially supported client for Postmark (http://postmarkapp.com)", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], "support": { - "issues": "https://github.com/ActiveCampaign/postmark-php/issues", - "source": "https://github.com/ActiveCampaign/postmark-php/tree/v4.0.5" + "source": "https://github.com/symfony/string/tree/v7.3.4" }, - "time": "2023-02-03T15:00:17+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-09-11T14:36:48+00:00" } ], - "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "~7.3||~8.1" + "php": "~8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/htdocs/index.php b/htdocs/index.php index a6b22cc..c6d65ee 100644 --- a/htdocs/index.php +++ b/htdocs/index.php @@ -1,4 +1,6 @@ -