From 3e83504f9cbc5a4814ad87735e33a57b379d1be2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:47:41 +0000 Subject: [PATCH 1/3] Bump github/codeql-action from 3.28.0 to 3.28.1 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.0 to 3.28.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/48ab28a6f5dbc2a99bf1e0131198dd8f1df78169...b6a472f63d85b9c78a3ac5e89422239fc15e9b3c) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index dd333f76a..ba71b6f44 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -64,7 +64,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -94,6 +94,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index ecb355b49..db535e5c9 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -65,6 +65,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 with: sarif_file: results.sarif From 507634a28b2ae2dbe3035b63408159d9d07360c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:47:44 +0000 Subject: [PATCH 2/3] Bump actions/upload-artifact from 4.5.0 to 4.6.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.5.0 to 4.6.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/6f51ac03b9356f520e9adb1b1b7802705f340c2b...65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/php-tester-include-skipped.yml | 2 +- .github/workflows/php.yml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/tls.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/php-tester-include-skipped.yml b/.github/workflows/php-tester-include-skipped.yml index cc271d698..5a1c30780 100644 --- a/.github/workflows/php-tester-include-skipped.yml +++ b/.github/workflows/php-tester-include-skipped.yml @@ -30,7 +30,7 @@ jobs: if: failure() run: for i in $(find ./app/tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done - name: Upload test code coverage - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 if: success() with: name: Test code coverage (PHP ${{ matrix.php-version }}) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 4ff7961d2..5cdbdc919 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -196,7 +196,7 @@ jobs: if: failure() run: for i in $(find ./app/tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done - name: Upload test code coverage - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 if: success() with: name: Test code coverage (PHP ${{ matrix.php-version }}) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index db535e5c9..ce24bafc7 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -56,7 +56,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/tls.yml b/.github/workflows/tls.yml index b3078f41b..6c98d2959 100644 --- a/.github/workflows/tls.yml +++ b/.github/workflows/tls.yml @@ -43,7 +43,7 @@ jobs: uses: mbogh/test-ssl-action@6bad4e83e29bca36d5570a00736a0b9d63e52643 # v3.0.2 with: host: ${{ matrix.url }} - - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 if: always() with: name: testssl.sh reports From 1358a1bd3e66f35fb2b25a0b538f9d803e75b595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0pa=C4=8Dek?= Date: Mon, 13 Jan 2025 22:10:21 +0100 Subject: [PATCH 3/3] Update packages - nette/bootstrap updated from v3.2.4 to v3.2.5 patch See changes: https://github.com/nette/bootstrap/compare/v3.2.4...v3.2.5 Release notes: https://github.com/nette/bootstrap/releases/tag/v3.2.5 - nette/database updated from v3.2.5 to v3.2.6 patch See changes: https://github.com/nette/database/compare/v3.2.5...v3.2.6 Release notes: https://github.com/nette/database/releases/tag/v3.2.6 - nette/http updated from v3.3.1 to v3.3.2 patch See changes: https://github.com/nette/http/compare/v3.3.1...v3.3.2 Release notes: https://github.com/nette/http/releases/tag/v3.3.2 --- app/composer.lock | 36 +- app/vendor/composer/installed.json | 42 +- app/vendor/composer/installed.php | 22 +- .../bootstrap/src/Bootstrap/Configurator.php | 1 + .../Bridges/DatabaseTracy/ConnectionPanel.php | 30 +- .../Bridges/DatabaseTracy/dist/panel.phtml | 83 ++++ .../src/Bridges/DatabaseTracy/dist/tab.phtml | 13 + .../src/Bridges/DatabaseTracy/panel.latte | 71 ++++ .../src/Bridges/DatabaseTracy/tab.latte | 6 + .../templates/ConnectionPanel.panel.phtml | 66 ---- .../templates/ConnectionPanel.tab.phtml | 12 - .../nette/database/src/Database/Driver.php | 4 +- .../src/Database/Drivers/MsSqlDriver.php | 2 +- .../src/Database/Drivers/OciDriver.php | 2 +- .../src/Database/Drivers/OdbcDriver.php | 2 +- .../src/Database/Drivers/PgSqlDriver.php | 2 +- .../src/Database/Drivers/SqliteDriver.php | 4 +- .../src/Database/Drivers/SqlsrvDriver.php | 2 +- .../nette/database/src/Database/Explorer.php | 17 + .../nette/database/src/Database/Helpers.php | 4 +- .../nette/database/src/Database/ResultSet.php | 8 +- .../database/src/Database/SqlPreprocessor.php | 368 ++++++++++-------- .../src/Database/Table/GroupedSelection.php | 1 - .../database/src/Database/Table/Selection.php | 32 +- .../src/Database/Table/SqlBuilder.php | 19 +- .../src/Bridges/HttpTracy/SessionPanel.php | 4 +- .../src/Bridges/HttpTracy/dist/panel.phtml | 32 ++ .../http/src/Bridges/HttpTracy/dist/tab.phtml | 9 + .../http/src/Bridges/HttpTracy/panel.latte | 32 ++ .../http/src/Bridges/HttpTracy/tab.latte | 5 + .../templates/SessionPanel.panel.phtml | 38 -- .../templates/SessionPanel.tab.phtml | 11 - app/vendor/nette/http/src/Http/FileUpload.php | 15 +- app/vendor/nette/http/src/Http/Url.php | 38 +- .../nette/http/src/Http/UrlImmutable.php | 103 +++-- app/vendor/nette/http/src/Http/UrlScript.php | 37 +- 36 files changed, 709 insertions(+), 464 deletions(-) create mode 100644 app/vendor/nette/database/src/Bridges/DatabaseTracy/dist/panel.phtml create mode 100644 app/vendor/nette/database/src/Bridges/DatabaseTracy/dist/tab.phtml create mode 100644 app/vendor/nette/database/src/Bridges/DatabaseTracy/panel.latte create mode 100644 app/vendor/nette/database/src/Bridges/DatabaseTracy/tab.latte delete mode 100644 app/vendor/nette/database/src/Bridges/DatabaseTracy/templates/ConnectionPanel.panel.phtml delete mode 100644 app/vendor/nette/database/src/Bridges/DatabaseTracy/templates/ConnectionPanel.tab.phtml create mode 100644 app/vendor/nette/http/src/Bridges/HttpTracy/dist/panel.phtml create mode 100644 app/vendor/nette/http/src/Bridges/HttpTracy/dist/tab.phtml create mode 100644 app/vendor/nette/http/src/Bridges/HttpTracy/panel.latte create mode 100644 app/vendor/nette/http/src/Bridges/HttpTracy/tab.latte delete mode 100644 app/vendor/nette/http/src/Bridges/HttpTracy/templates/SessionPanel.panel.phtml delete mode 100644 app/vendor/nette/http/src/Bridges/HttpTracy/templates/SessionPanel.tab.phtml diff --git a/app/composer.lock b/app/composer.lock index 1226cd483..6f55cc676 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -352,16 +352,16 @@ }, { "name": "nette/bootstrap", - "version": "v3.2.4", + "version": "v3.2.5", "source": { "type": "git", "url": "https://github.com/nette/bootstrap.git", - "reference": "4876d25955b4164d714bc17c265f664f6594685b" + "reference": "91d08432cb33d6c08d58b215c769d04f20580624" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/bootstrap/zipball/4876d25955b4164d714bc17c265f664f6594685b", - "reference": "4876d25955b4164d714bc17c265f664f6594685b", + "url": "https://api.github.com/repos/nette/bootstrap/zipball/91d08432cb33d6c08d58b215c769d04f20580624", + "reference": "91d08432cb33d6c08d58b215c769d04f20580624", "shasum": "" }, "require": { @@ -427,9 +427,9 @@ ], "support": { "issues": "https://github.com/nette/bootstrap/issues", - "source": "https://github.com/nette/bootstrap/tree/v3.2.4" + "source": "https://github.com/nette/bootstrap/tree/v3.2.5" }, - "time": "2024-06-18T22:13:57+00:00" + "time": "2024-11-14T00:49:46+00:00" }, { "name": "nette/caching", @@ -631,16 +631,16 @@ }, { "name": "nette/database", - "version": "v3.2.5", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/nette/database.git", - "reference": "3f9e12a5488615194d1e1d093c982cc07a85be68" + "reference": "cb825ac1cffe1ede98388a9c4e6c4445c26b961d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/database/zipball/3f9e12a5488615194d1e1d093c982cc07a85be68", - "reference": "3f9e12a5488615194d1e1d093c982cc07a85be68", + "url": "https://api.github.com/repos/nette/database/zipball/cb825ac1cffe1ede98388a9c4e6c4445c26b961d", + "reference": "cb825ac1cffe1ede98388a9c4e6c4445c26b961d", "shasum": "" }, "require": { @@ -700,9 +700,9 @@ ], "support": { "issues": "https://github.com/nette/database/issues", - "source": "https://github.com/nette/database/tree/v3.2.5" + "source": "https://github.com/nette/database/tree/v3.2.6" }, - "time": "2024-12-18T19:13:21+00:00" + "time": "2025-01-12T15:33:57+00:00" }, { "name": "nette/di", @@ -856,16 +856,16 @@ }, { "name": "nette/http", - "version": "v3.3.1", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/nette/http.git", - "reference": "5f8f07d4242cb48c6b33d89daa3ea6ddf9668cfc" + "reference": "3e2587b34beb66f238f119b12fbb4f0b9ab2d6d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/http/zipball/5f8f07d4242cb48c6b33d89daa3ea6ddf9668cfc", - "reference": "5f8f07d4242cb48c6b33d89daa3ea6ddf9668cfc", + "url": "https://api.github.com/repos/nette/http/zipball/3e2587b34beb66f238f119b12fbb4f0b9ab2d6d1", + "reference": "3e2587b34beb66f238f119b12fbb4f0b9ab2d6d1", "shasum": "" }, "require": { @@ -931,9 +931,9 @@ ], "support": { "issues": "https://github.com/nette/http/issues", - "source": "https://github.com/nette/http/tree/v3.3.1" + "source": "https://github.com/nette/http/tree/v3.3.2" }, - "time": "2024-11-04T16:30:18+00:00" + "time": "2025-01-12T16:27:57+00:00" }, { "name": "nette/mail", diff --git a/app/vendor/composer/installed.json b/app/vendor/composer/installed.json index 11b17ae8b..ed227ad76 100644 --- a/app/vendor/composer/installed.json +++ b/app/vendor/composer/installed.json @@ -403,17 +403,17 @@ }, { "name": "nette/bootstrap", - "version": "v3.2.4", - "version_normalized": "3.2.4.0", + "version": "v3.2.5", + "version_normalized": "3.2.5.0", "source": { "type": "git", "url": "https://github.com/nette/bootstrap.git", - "reference": "4876d25955b4164d714bc17c265f664f6594685b" + "reference": "91d08432cb33d6c08d58b215c769d04f20580624" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/bootstrap/zipball/4876d25955b4164d714bc17c265f664f6594685b", - "reference": "4876d25955b4164d714bc17c265f664f6594685b", + "url": "https://api.github.com/repos/nette/bootstrap/zipball/91d08432cb33d6c08d58b215c769d04f20580624", + "reference": "91d08432cb33d6c08d58b215c769d04f20580624", "shasum": "" }, "require": { @@ -443,7 +443,7 @@ "nette/robot-loader": "to use Configurator::createRobotLoader()", "tracy/tracy": "to use Configurator::enableTracy()" }, - "time": "2024-06-18T22:13:57+00:00", + "time": "2024-11-14T00:49:46+00:00", "type": "library", "extra": { "branch-alias": { @@ -481,7 +481,7 @@ ], "support": { "issues": "https://github.com/nette/bootstrap/issues", - "source": "https://github.com/nette/bootstrap/tree/v3.2.4" + "source": "https://github.com/nette/bootstrap/tree/v3.2.5" }, "install-path": "../nette/bootstrap" }, @@ -694,17 +694,17 @@ }, { "name": "nette/database", - "version": "v3.2.5", - "version_normalized": "3.2.5.0", + "version": "v3.2.6", + "version_normalized": "3.2.6.0", "source": { "type": "git", "url": "https://github.com/nette/database.git", - "reference": "3f9e12a5488615194d1e1d093c982cc07a85be68" + "reference": "cb825ac1cffe1ede98388a9c4e6c4445c26b961d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/database/zipball/3f9e12a5488615194d1e1d093c982cc07a85be68", - "reference": "3f9e12a5488615194d1e1d093c982cc07a85be68", + "url": "https://api.github.com/repos/nette/database/zipball/cb825ac1cffe1ede98388a9c4e6c4445c26b961d", + "reference": "cb825ac1cffe1ede98388a9c4e6c4445c26b961d", "shasum": "" }, "require": { @@ -721,7 +721,7 @@ "phpstan/phpstan-nette": "^1.0", "tracy/tracy": "^2.9" }, - "time": "2024-12-18T19:13:21+00:00", + "time": "2025-01-12T15:33:57+00:00", "type": "library", "extra": { "branch-alias": { @@ -766,7 +766,7 @@ ], "support": { "issues": "https://github.com/nette/database/issues", - "source": "https://github.com/nette/database/tree/v3.2.5" + "source": "https://github.com/nette/database/tree/v3.2.6" }, "install-path": "../nette/database" }, @@ -928,17 +928,17 @@ }, { "name": "nette/http", - "version": "v3.3.1", - "version_normalized": "3.3.1.0", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", "source": { "type": "git", "url": "https://github.com/nette/http.git", - "reference": "5f8f07d4242cb48c6b33d89daa3ea6ddf9668cfc" + "reference": "3e2587b34beb66f238f119b12fbb4f0b9ab2d6d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/http/zipball/5f8f07d4242cb48c6b33d89daa3ea6ddf9668cfc", - "reference": "5f8f07d4242cb48c6b33d89daa3ea6ddf9668cfc", + "url": "https://api.github.com/repos/nette/http/zipball/3e2587b34beb66f238f119b12fbb4f0b9ab2d6d1", + "reference": "3e2587b34beb66f238f119b12fbb4f0b9ab2d6d1", "shasum": "" }, "require": { @@ -962,7 +962,7 @@ "ext-intl": "to support punycode by Nette\\Http\\Url", "ext-session": "to use Nette\\Http\\Session" }, - "time": "2024-11-04T16:30:18+00:00", + "time": "2025-01-12T16:27:57+00:00", "type": "library", "extra": { "branch-alias": { @@ -1006,7 +1006,7 @@ ], "support": { "issues": "https://github.com/nette/http/issues", - "source": "https://github.com/nette/http/tree/v3.3.1" + "source": "https://github.com/nette/http/tree/v3.3.2" }, "install-path": "../nette/http" }, diff --git a/app/vendor/composer/installed.php b/app/vendor/composer/installed.php index 1a7dd7acd..8953dad82 100644 --- a/app/vendor/composer/installed.php +++ b/app/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'spaze/michalspacek.cz', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => 'a847fe8c27a7eb84d574455477330e0af6f1b156', + 'reference' => '507634a28b2ae2dbe3035b63408159d9d07360c7', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -92,9 +92,9 @@ 'dev_requirement' => false, ), 'nette/bootstrap' => array( - 'pretty_version' => 'v3.2.4', - 'version' => '3.2.4.0', - 'reference' => '4876d25955b4164d714bc17c265f664f6594685b', + 'pretty_version' => 'v3.2.5', + 'version' => '3.2.5.0', + 'reference' => '91d08432cb33d6c08d58b215c769d04f20580624', 'type' => 'library', 'install_path' => __DIR__ . '/../nette/bootstrap', 'aliases' => array(), @@ -128,9 +128,9 @@ 'dev_requirement' => false, ), 'nette/database' => array( - 'pretty_version' => 'v3.2.5', - 'version' => '3.2.5.0', - 'reference' => '3f9e12a5488615194d1e1d093c982cc07a85be68', + 'pretty_version' => 'v3.2.6', + 'version' => '3.2.6.0', + 'reference' => 'cb825ac1cffe1ede98388a9c4e6c4445c26b961d', 'type' => 'library', 'install_path' => __DIR__ . '/../nette/database', 'aliases' => array(), @@ -161,9 +161,9 @@ 'dev_requirement' => false, ), 'nette/http' => array( - 'pretty_version' => 'v3.3.1', - 'version' => '3.3.1.0', - 'reference' => '5f8f07d4242cb48c6b33d89daa3ea6ddf9668cfc', + 'pretty_version' => 'v3.3.2', + 'version' => '3.3.2.0', + 'reference' => '3e2587b34beb66f238f119b12fbb4f0b9ab2d6d1', 'type' => 'library', 'install_path' => __DIR__ . '/../nette/http', 'aliases' => array(), @@ -468,7 +468,7 @@ 'spaze/michalspacek.cz' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => 'a847fe8c27a7eb84d574455477330e0af6f1b156', + 'reference' => '507634a28b2ae2dbe3035b63408159d9d07360c7', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/app/vendor/nette/bootstrap/src/Bootstrap/Configurator.php b/app/vendor/nette/bootstrap/src/Bootstrap/Configurator.php index 239dda2de..90882fd86 100644 --- a/app/vendor/nette/bootstrap/src/Bootstrap/Configurator.php +++ b/app/vendor/nette/bootstrap/src/Bootstrap/Configurator.php @@ -33,6 +33,7 @@ class Configurator public array $defaultExtensions = [ 'application' => [Nette\Bridges\ApplicationDI\ApplicationExtension::class, ['%debugMode%', ['%appDir%'], '%tempDir%/cache/nette.application']], + 'assets' => Nette\Bridges\Assets\DIExtension::class, 'cache' => [Nette\Bridges\CacheDI\CacheExtension::class, ['%tempDir%/cache']], 'constants' => Extensions\ConstantsExtension::class, 'database' => [Nette\Bridges\DatabaseDI\DatabaseExtension::class, ['%debugMode%']], diff --git a/app/vendor/nette/database/src/Bridges/DatabaseTracy/ConnectionPanel.php b/app/vendor/nette/database/src/Bridges/DatabaseTracy/ConnectionPanel.php index 2460f46e4..f1221562f 100644 --- a/app/vendor/nette/database/src/Bridges/DatabaseTracy/ConnectionPanel.php +++ b/app/vendor/nette/database/src/Bridges/DatabaseTracy/ConnectionPanel.php @@ -21,21 +21,13 @@ class ConnectionPanel implements Tracy\IBarPanel { public int $maxQueries = 100; - public string $name; - public bool|string $explain = true; - public bool $disabled = false; - public float $performanceScale = 0.25; - private float $totalTime = 0; - private int $count = 0; - private array $queries = []; - private Tracy\BlueScreen $blueScreen; @@ -78,30 +70,24 @@ private function logQuery(Connection $connection, $result): void $this->count++; - $source = null; $trace = $result instanceof \PDOException - ? $result->getTrace() + ? array_map(fn($row) => array_diff_key($row, ['args' => null]), $result->getTrace()) : debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + foreach ($trace as $row) { - if ( - (isset($row['file']) - && preg_match('~\.(php.?|phtml)$~', $row['file']) - && !$this->blueScreen->isCollapsed($row['file'])) - && ($row['class'] ?? '') !== self::class - && !is_a($row['class'] ?? '', Connection::class, allow_string: true) - ) { - $source = [$row['file'], (int) $row['line']]; + if (preg_match('~\.(php.?|phtml)$~', $row['file'] ?? '') && !$this->blueScreen->isCollapsed($row['file'])) { break; } + array_shift($trace); } if ($result instanceof Nette\Database\ResultSet) { $this->totalTime += $result->getTime(); if ($this->count < $this->maxQueries) { - $this->queries[] = [$connection, $result->getQueryString(), $result->getParameters(), $source, $result->getTime(), $result->getRowCount(), null]; + $this->queries[] = [$connection, $result->getQueryString(), $result->getParameters(), $trace, $result->getTime(), $result->getRowCount(), null]; } } elseif ($result instanceof \PDOException && $this->count < $this->maxQueries) { - $this->queries[] = [$connection, $result->queryString, null, $source, null, null, $result->getMessage()]; + $this->queries[] = [$connection, $result->queryString, null, $trace, null, null, $result->getMessage()]; } } @@ -132,7 +118,7 @@ public function getTab(): string $name = $this->name; $count = $this->count; $totalTime = $this->totalTime; - require __DIR__ . '/templates/ConnectionPanel.tab.phtml'; + require __DIR__ . '/dist/tab.phtml'; }); } @@ -170,7 +156,7 @@ public function getPanel(): ?string $count = $this->count; $totalTime = $this->totalTime; $performanceScale = $this->performanceScale; - require __DIR__ . '/templates/ConnectionPanel.panel.phtml'; + require __DIR__ . '/dist/panel.phtml'; }); } } diff --git a/app/vendor/nette/database/src/Bridges/DatabaseTracy/dist/panel.phtml b/app/vendor/nette/database/src/Bridges/DatabaseTracy/dist/panel.phtml new file mode 100644 index 000000000..84a178c30 --- /dev/null +++ b/app/vendor/nette/database/src/Bridges/DatabaseTracy/dist/panel.phtml @@ -0,0 +1,83 @@ + + + +

Queries: + +, +

+ +
+ + + + + + + + + + + + + + +
Time msSQL QueryRows
+ ERROR + + + + + +
explain + + +
trace + +
+ + + + + + $foo): ?> + + + + +
+
+
+ + + + + + + + +
+ + + +()
+
+
+ + +

...and more

+ +
diff --git a/app/vendor/nette/database/src/Bridges/DatabaseTracy/dist/tab.phtml b/app/vendor/nette/database/src/Bridges/DatabaseTracy/dist/tab.phtml new file mode 100644 index 000000000..8640ef807 --- /dev/null +++ b/app/vendor/nette/database/src/Bridges/DatabaseTracy/dist/tab.phtml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/app/vendor/nette/database/src/Bridges/DatabaseTracy/panel.latte b/app/vendor/nette/database/src/Bridges/DatabaseTracy/panel.latte new file mode 100644 index 000000000..39c66de9b --- /dev/null +++ b/app/vendor/nette/database/src/Bridges/DatabaseTracy/panel.latte @@ -0,0 +1,71 @@ + + +

Queries: {$count}{$totalTime ? sprintf(', time: %0.3f ms', $totalTime * 1000) : ''}, {$name}

+ +
+ + + + + + + + {foreach $queries as [$connection, $sql, $params, $trace, $time, $rows, $error, $command, $explain]} + + + + + + + + {/foreach} +
Time msSQL QueryRows
+ {if $error} + ERROR + {elseif $time !== null}{sprintf('%0.3f', $time * 1000)} + {/if} + + {if $explain}
explain{/if} + {if $trace}
trace{/if} +
+ {Nette\Database\Helpers::dumpSql($sql, $params, $connection)|noescape} + + {if $explain} + + + {foreach $explain[0] as $col => $foo} + + {/foreach} + + {foreach $explain as $row} + + {foreach $row as $col} + + {/foreach} + + {/foreach} +
{$col}
{$col}
+ {/if} + + {if $trace} + {substr_replace(Tracy\Helpers::editorLink($trace[0][file], $trace[0][line]), ' class="nette-DbConnectionPanel-source"', 2, 0)} + + {foreach $trace as $row} + + + + + {/foreach} +
{isset($row[file]) ? Tracy\Helpers::editorLink($row[file], $row[line]) : ''}{$row[class] ?? ''}{$row[type] ?? ''}{$row[function]}()
+ {/if} +
{$rows}
+ + {if count($queries) < $count}

...and more

{/if} +
diff --git a/app/vendor/nette/database/src/Bridges/DatabaseTracy/tab.latte b/app/vendor/nette/database/src/Bridges/DatabaseTracy/tab.latte new file mode 100644 index 000000000..dfe92c687 --- /dev/null +++ b/app/vendor/nette/database/src/Bridges/DatabaseTracy/tab.latte @@ -0,0 +1,6 @@ + + + + {$totalTime ? sprintf('%0.1f ms / ', $totalTime * 1000) : ''}{$count} + diff --git a/app/vendor/nette/database/src/Bridges/DatabaseTracy/templates/ConnectionPanel.panel.phtml b/app/vendor/nette/database/src/Bridges/DatabaseTracy/templates/ConnectionPanel.panel.phtml deleted file mode 100644 index df359af94..000000000 --- a/app/vendor/nette/database/src/Bridges/DatabaseTracy/templates/ConnectionPanel.panel.phtml +++ /dev/null @@ -1,66 +0,0 @@ - - - -

Queries:

- -
-
- - - - - - - - - -
Time msSQL QueryRows
- - ERROR - - -
explain - -
- - - - $foo): ?> - - - - - - - - - - -
- - -
-

...and more

-
-
diff --git a/app/vendor/nette/database/src/Bridges/DatabaseTracy/templates/ConnectionPanel.tab.phtml b/app/vendor/nette/database/src/Bridges/DatabaseTracy/templates/ConnectionPanel.tab.phtml deleted file mode 100644 index b7a7722c7..000000000 --- a/app/vendor/nette/database/src/Bridges/DatabaseTracy/templates/ConnectionPanel.tab.phtml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/vendor/nette/database/src/Database/Driver.php b/app/vendor/nette/database/src/Database/Driver.php index 8ade59077..526b804e9 100644 --- a/app/vendor/nette/database/src/Database/Driver.php +++ b/app/vendor/nette/database/src/Database/Driver.php @@ -20,7 +20,6 @@ interface Driver SupportSelectUngroupedColumns = 'ungrouped_cols', SupportMultiInsertAsSelect = 'insert_as_select', SupportMultiColumnAsOrCondition = 'multi_column_as_or', - SupportSubselect = 'subselect', SupportSchema = 'schema'; /** @deprecated use Driver::Support* */ @@ -30,7 +29,8 @@ interface Driver SUPPORT_MULTI_INSERT_AS_SELECT = 'insert_as_select', SUPPORT_MULTI_COLUMN_AS_OR_COND = 'multi_column_as_or', SUPPORT_SUBSELECT = 'subselect', - SUPPORT_SCHEMA = 'schema'; + SUPPORT_SCHEMA = 'schema', + SupportSubselect = 'subselect'; /** * Checks if the engine supports a specific feature. diff --git a/app/vendor/nette/database/src/Database/Drivers/MsSqlDriver.php b/app/vendor/nette/database/src/Database/Drivers/MsSqlDriver.php index 3509cb434..5c71a7d87 100644 --- a/app/vendor/nette/database/src/Database/Drivers/MsSqlDriver.php +++ b/app/vendor/nette/database/src/Database/Drivers/MsSqlDriver.php @@ -28,7 +28,7 @@ public function initialize(Nette\Database\Connection $connection, array $options public function isSupported(string $feature): bool { - return $feature === self::SupportSubselect; + return false; } diff --git a/app/vendor/nette/database/src/Database/Drivers/OciDriver.php b/app/vendor/nette/database/src/Database/Drivers/OciDriver.php index ba48033ca..1976f62bc 100644 --- a/app/vendor/nette/database/src/Database/Drivers/OciDriver.php +++ b/app/vendor/nette/database/src/Database/Drivers/OciDriver.php @@ -30,7 +30,7 @@ public function initialize(Nette\Database\Connection $connection, array $options public function isSupported(string $feature): bool { - return $feature === self::SupportSequence || $feature === self::SupportSubselect; + return $feature === self::SupportSequence; } diff --git a/app/vendor/nette/database/src/Database/Drivers/OdbcDriver.php b/app/vendor/nette/database/src/Database/Drivers/OdbcDriver.php index d687fc915..da1aa3142 100644 --- a/app/vendor/nette/database/src/Database/Drivers/OdbcDriver.php +++ b/app/vendor/nette/database/src/Database/Drivers/OdbcDriver.php @@ -24,7 +24,7 @@ public function initialize(Nette\Database\Connection $connection, array $options public function isSupported(string $feature): bool { - return $feature === self::SupportSubselect; + return false; } diff --git a/app/vendor/nette/database/src/Database/Drivers/PgSqlDriver.php b/app/vendor/nette/database/src/Database/Drivers/PgSqlDriver.php index f2f8ebe1b..2c2756162 100644 --- a/app/vendor/nette/database/src/Database/Drivers/PgSqlDriver.php +++ b/app/vendor/nette/database/src/Database/Drivers/PgSqlDriver.php @@ -28,7 +28,7 @@ public function initialize(Nette\Database\Connection $connection, array $options public function isSupported(string $feature): bool { - return $feature === self::SupportSequence || $feature === self::SupportSubselect || $feature === self::SupportSchema; + return $feature === self::SupportSequence || $feature === self::SupportSchema; } diff --git a/app/vendor/nette/database/src/Database/Drivers/SqliteDriver.php b/app/vendor/nette/database/src/Database/Drivers/SqliteDriver.php index db886de85..c50caa16c 100644 --- a/app/vendor/nette/database/src/Database/Drivers/SqliteDriver.php +++ b/app/vendor/nette/database/src/Database/Drivers/SqliteDriver.php @@ -30,7 +30,7 @@ public function initialize(Nette\Database\Connection $connection, array $options public function isSupported(string $feature): bool { - return $feature === self::SupportMultiInsertAsSelect || $feature === self::SupportSubselect || $feature === self::SupportMultiColumnAsOrCondition; + return $feature === self::SupportMultiInsertAsSelect || $feature === self::SupportMultiColumnAsOrCondition; } @@ -155,7 +155,7 @@ public function getColumns(string $table): array $columns[] = [ 'name' => $column, 'table' => $table, - 'nativetype' => strtoupper($typeInfo['type']), + 'nativetype' => strtoupper($typeInfo['type'] ?? 'BLOB'), 'size' => $typeInfo['length'], 'nullable' => $row['notnull'] == 0, 'default' => $row['dflt_value'], diff --git a/app/vendor/nette/database/src/Database/Drivers/SqlsrvDriver.php b/app/vendor/nette/database/src/Database/Drivers/SqlsrvDriver.php index e34a50bb3..f00c5745b 100644 --- a/app/vendor/nette/database/src/Database/Drivers/SqlsrvDriver.php +++ b/app/vendor/nette/database/src/Database/Drivers/SqlsrvDriver.php @@ -28,7 +28,7 @@ public function initialize(Nette\Database\Connection $connection, array $options public function isSupported(string $feature): bool { - return $feature === self::SupportSubselect; + return false; } diff --git a/app/vendor/nette/database/src/Database/Explorer.php b/app/vendor/nette/database/src/Database/Explorer.php index 7a8ec3ef9..99b152b6e 100644 --- a/app/vendor/nette/database/src/Database/Explorer.php +++ b/app/vendor/nette/database/src/Database/Explorer.php @@ -106,6 +106,23 @@ public function getConventions(): Conventions } + public function createActiveRow(array $data, Table\Selection $selection): Table\ActiveRow + { + return new Table\ActiveRow($data, $selection); + } + + + /** @internal */ + public function createGroupedSelection( + Table\Selection $refSelection, + string $table, + string $column, + ): Table\GroupedSelection + { + return new Table\GroupedSelection($this, $this->conventions, $table, $column, $refSelection, $this->cacheStorage); + } + + /********************* shortcuts ****************d*g**/ diff --git a/app/vendor/nette/database/src/Database/Helpers.php b/app/vendor/nette/database/src/Database/Helpers.php index 7e6e69ebe..5878beea8 100644 --- a/app/vendor/nette/database/src/Database/Helpers.php +++ b/app/vendor/nette/database/src/Database/Helpers.php @@ -389,12 +389,12 @@ public static function findDuplicates(\PDOStatement $statement): string } - /** @return array{type: string, length: ?null, scale: ?null, parameters: ?string} */ + /** @return array{type: ?string, length: ?null, scale: ?null, parameters: ?string} */ public static function parseColumnType(string $type): array { preg_match('/^([^(]+)(?:\((?:(\d+)(?:,(\d+))?|([^)]+))\))?/', $type, $m, PREG_UNMATCHED_AS_NULL); return [ - 'type' => $m[1], + 'type' => $m[1] ?? null, 'length' => isset($m[2]) ? (int) $m[2] : null, 'scale' => isset($m[3]) ? (int) $m[3] : null, 'parameters' => $m[4] ?? null, diff --git a/app/vendor/nette/database/src/Database/ResultSet.php b/app/vendor/nette/database/src/Database/ResultSet.php index 14d3dca13..3489cead0 100644 --- a/app/vendor/nette/database/src/Database/ResultSet.php +++ b/app/vendor/nette/database/src/Database/ResultSet.php @@ -204,7 +204,7 @@ public function fetchAssoc(?string $path = null): ?array /** - * Returns the next row as an object Row or null if there are no more rows. + * Returns the next row as a Row object or null if there are no more rows. */ public function fetch(): ?Row { @@ -248,7 +248,9 @@ public function fetchFields(): ?array /** - * Returns all rows as associative array. + * Returns all rows as associative array, where first argument specifies key column and second value column. + * For duplicate keys, the last value is used. When using null as key, array is indexed from zero. + * Alternatively accepts callback returning value or key-value pairs. */ public function fetchPairs(string|int|\Closure|null $keyOrCallback = null, string|int|null $value = null): array { @@ -257,7 +259,7 @@ public function fetchPairs(string|int|\Closure|null $keyOrCallback = null, strin /** - * Returns all rows. + * Returns all remaining rows as array of Row objects. * @return Row[] */ public function fetchAll(): array diff --git a/app/vendor/nette/database/src/Database/SqlPreprocessor.php b/app/vendor/nette/database/src/Database/SqlPreprocessor.php index 53e733eb2..d63fcb8c8 100644 --- a/app/vendor/nette/database/src/Database/SqlPreprocessor.php +++ b/app/vendor/nette/database/src/Database/SqlPreprocessor.php @@ -14,6 +14,7 @@ /** * Processes SQL queries with parameter substitution. + * Supports named parameters, array expansions and other SQL preprocessing features. */ class SqlPreprocessor { @@ -24,11 +25,9 @@ class SqlPreprocessor ModeValues = 'values', // (key, key, ...) VALUES (value, value, ...) ModeOrder = 'order', // key, key DESC, ... ModeList = 'list', // value, value, ... | (tuple), (tuple), ... - ModeAuto = 'auto'; // arrayMode for arrays + ModeName = 'name'; // `name` - private const Modes = [self::ModeAnd, self::ModeOr, self::ModeSet, self::ModeValues, self::ModeOrder, self::ModeList]; - - private const ArrayModes = [ + private const CommandToMode = [ 'INSERT' => self::ModeValues, 'REPLACE' => self::ModeValues, 'KEY UPDATE' => self::ModeSet, @@ -67,6 +66,7 @@ public function __construct(Connection $connection) /** + * Processes SQL query with parameter substitution. * @return array{string, array} */ public function process(array $params, bool $useParams = false): array @@ -83,7 +83,7 @@ public function process(array $params, bool $useParams = false): array $param = $params[$this->counter++]; if (($this->counter === 2 && count($params) === 2) || !is_scalar($param)) { - $res[] = $this->formatValue($param, self::ModeAuto); + $res[] = $this->formatParameter($param); } elseif (is_string($param) && $this->counter > $prev + 1) { $prev = $this->counter; @@ -102,7 +102,7 @@ public function process(array $params, bool $useParams = false): array |--[^\n]* ~Dsix X, - $this->callback(...), + $this->parsePart(...), ); } else { throw new Nette\InvalidArgumentException('There are more parameters than placeholders.'); @@ -113,215 +113,241 @@ public function process(array $params, bool $useParams = false): array } - private function callback(array $m): string + /** + * Handles SQL placeholders and skips string literals and comments. + */ + private function parsePart(array $match): string { - $m = $m[0]; - if ($m[0] === '?') { // placeholder - if ($this->counter >= count($this->params)) { - throw new Nette\InvalidArgumentException('There are more placeholders than passed parameters.'); - } - - return $this->formatValue($this->params[$this->counter++], substr($m, 1) ?: self::ModeAuto); - - } elseif ($m[0] === "'" || $m[0] === '"' || $m[0] === '/' || $m[0] === '-') { // string or comment - return $m; - - } elseif (preg_match('~^IN\s~i', $m)) { // IN (?) - if ($this->counter >= count($this->params)) { - throw new Nette\InvalidArgumentException('There are more placeholders than passed parameters.'); - } - - $param = $this->params[$this->counter++]; - return 'IN (' . $this->formatValue($param, is_array($param) ? self::ModeList : null) . ')'; + $match = $match[0]; + if (in_array($match[0], ["'", '"', '/', '-'], true)) { // string or comment + return $match; + + } elseif (!str_contains($match, '?')) { // command + $command = ltrim(strtoupper($match), "\t\n\r ("); + $this->arrayMode = self::CommandToMode[$command] ?? null; + $this->useParams = isset(self::ParametricCommands[$command]) || $this->useParams; + return $match; + + } elseif ($this->counter >= count($this->params)) { + throw new Nette\InvalidArgumentException('There are more placeholders than passed parameters.'); + } - } else { // command - $cmd = ltrim(strtoupper($m), "\t\n\r ("); - $this->arrayMode = self::ArrayModes[$cmd] ?? null; - $this->useParams = isset(self::ParametricCommands[$cmd]) || $this->useParams; - return $m; + $param = $this->params[$this->counter++]; + if ($match[0] === '?') { // ?[mode] + return $this->formatParameter($param, substr($match, 1) ?: null); + } else { // IN (?) + return 'IN (' . (is_array($param) ? $this->formatList($param) : $this->formatValue($param)) . ')'; } } - private function formatValue(mixed $value, ?string $mode = null): string + /** + * Formats a value for use in SQL query where ? placeholder is used. + * For arrays, the formatting is determined by $mode or last SQL keyword before the placeholder + */ + private function formatParameter(mixed $value, ?string $mode = null): string { - if (!$mode || $mode === self::ModeAuto) { - if (is_scalar($value) || is_resource($value)) { - if ($this->useParams) { - $this->remaining[] = $value; - return '?'; + if ($value instanceof \Traversable && !$value instanceof \Stringable) { + $value = iterator_to_array($value); + } + if (is_array($value)) { + $mode ??= $this->arrayMode ?? self::ModeSet; + } - } elseif (is_int($value) || is_bool($value)) { - return (string) (int) $value; + $check = fn($value, $type) => "is_$type"($value) + ? $value + : throw new Nette\InvalidArgumentException("Placeholder ?$mode expects $type, " . get_debug_type($value) . ' given.'); + + return match ($mode) { + null => $this->formatValue($value), + self::ModeValues => array_key_exists(0, $check($value, 'iterable')) ? $this->formatMultiInsert($value) : $this->formatInsert($value), + self::ModeSet => $this->formatSet($check($value, 'iterable')), + self::ModeList => $this->formatList($check($value, 'iterable')), + self::ModeAnd, self::ModeOr => $this->formatWhere($check($value, 'iterable'), $mode), + self::ModeOrder => $this->formatOrderBy($check($value, 'iterable')), + self::ModeName => $this->delimit($check($value, 'string')), + default => throw new Nette\InvalidArgumentException("Unknown placeholder ?$mode."), + }; + } - } elseif (is_float($value)) { - return rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.'); - } elseif (is_resource($value)) { - return $this->connection->quote(stream_get_contents($value)); + /** + * Formats a single value for use in SQL query. + */ + private function formatValue(mixed $value): string + { + if ($this->useParams && (is_scalar($value) || is_resource($value))) { + $this->remaining[] = $value; + return '?'; + } - } else { - return $this->connection->quote((string) $value); - } - } elseif ($value === null) { - return 'NULL'; + return match (true) { + is_int($value) => (string) $value, + is_bool($value) => (string) (int) $value, + is_float($value) => rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.'), + is_resource($value) => $this->connection->quote(stream_get_contents($value)), + is_string($value) => $this->connection->quote($value), + $value === null => 'NULL', + $value instanceof SqlLiteral => $this->formatLiteral($value), + $value instanceof Table\ActiveRow => $this->formatValue($value->getPrimary()), + $value instanceof \DateTimeInterface => $this->driver->formatDateTime($value), + $value instanceof \DateInterval => $this->driver->formatDateInterval($value), + $value instanceof \BackedEnum && is_scalar($value->value) => $this->formatValue($value->value), + $value instanceof \Stringable => $this->formatValue((string) $value), + default => throw new Nette\InvalidArgumentException('Unexpected type of parameter: ' . get_debug_type($value)) + }; + } - } elseif ($value instanceof Table\ActiveRow) { - $this->remaining[] = $value->getPrimary(); - return '?'; - } elseif ($value instanceof SqlLiteral) { - $prep = clone $this; - [$res, $params] = $prep->process(array_merge([$value->__toString()], $value->getParameters()), $this->useParams); - $this->remaining = array_merge($this->remaining, $params); - return $res; + /** + * Output: value, value, ... | (tuple), (tuple), ... + */ + private function formatList(array $values): string + { + $res = []; + foreach ($values as $v) { + $res[] = is_array($v) + ? '(' . $this->formatList($v) . ')' + : $this->formatValue($v); + } - } elseif ($value instanceof \DateTimeInterface) { - return $this->driver->formatDateTime($value); + return implode(', ', $res); + } - } elseif ($value instanceof \DateInterval) { - return $this->driver->formatDateInterval($value); - } elseif ($value instanceof \BackedEnum && is_scalar($value->value)) { - $this->remaining[] = $value->value; - return '?'; + /** + * Output format: (key, key, ...) VALUES (value, value, ...) + */ + private function formatInsert(array $items): string + { + $cols = $vals = []; + foreach ($items as $k => $v) { + $cols[] = $this->delimit($k); + $vals[] = $this->formatValue($v); + } - } elseif ($value instanceof \Stringable) { - $this->remaining[] = (string) $value; - return '?'; - } - } elseif ($mode === 'name') { - if (!is_string($value)) { - $type = get_debug_type($value); - throw new Nette\InvalidArgumentException("Placeholder ?$mode expects string, $type given."); - } + return '(' . implode(', ', $cols) . ') VALUES (' . implode(', ', $vals) . ')'; + } - return $this->delimite($value); - } - if ($value instanceof \Traversable && !$value instanceof Table\ActiveRow) { - $value = iterator_to_array($value); + /** + * Output format: (key, key, ...) VALUES (value, value, ...), (value, value, ...), ... + */ + private function formatMultiInsert(array $groups): string + { + if (!is_array($groups[0]) && !$groups[0] instanceof Row) { + throw new Nette\InvalidArgumentException('Automaticaly detected multi-insert, but values aren\'t array. If you need try to change ?mode.'); } - if ($mode && is_array($value)) { - $vx = $kx = []; - if ($mode === self::ModeAuto) { - $mode = $this->arrayMode ?? self::ModeSet; + $cols = array_keys(is_array($groups[0]) ? $groups[0] : iterator_to_array($groups[0])); + $vals = []; + foreach ($groups as $group) { + $rowVals = []; + foreach ($cols as $k) { + $rowVals[] = $this->formatValue($group[$k]); } - if ($mode === self::ModeValues) { // (key, key, ...) VALUES (value, value, ...) - if (array_key_exists(0, $value)) { // multi-insert - if (!is_array($value[0]) && !$value[0] instanceof Row) { - throw new Nette\InvalidArgumentException( - 'Automaticaly detected multi-insert, but values aren\'t array. If you need try to change mode like "?[' - . implode('|', self::Modes) . ']". Mode "' . $mode . '" was used.', - ); - } - - foreach ($value[0] as $k => $v) { - $kx[] = $this->delimite($k); - } - - foreach ($value as $val) { - $vx2 = []; - foreach ($value[0] as $k => $foo) { - $vx2[] = $this->formatValue($val[$k]); - } - - $vx[] = implode(', ', $vx2); - } + $vals[] = implode(', ', $rowVals); + } - $select = $this->driver->isSupported(Driver::SupportMultiInsertAsSelect); - return '(' . implode(', ', $kx) . ($select ? ') SELECT ' : ') VALUES (') - . implode($select ? ' UNION ALL SELECT ' : '), (', $vx) . ($select ? '' : ')'); - } + $useSelect = $this->driver->isSupported(Driver::SupportMultiInsertAsSelect); + return '(' . implode(', ', array_map($this->delimit(...), $cols)) + . ($useSelect ? ') SELECT ' : ') VALUES (') + . implode($useSelect ? ' UNION ALL SELECT ' : '), (', $vals) + . ($useSelect ? '' : ')'); + } - foreach ($value as $k => $v) { - $kx[] = $this->delimite($k); - $vx[] = $this->formatValue($v); - } - return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')'; - - } elseif ($mode === self::ModeSet) { - foreach ($value as $k => $v) { - if (is_int($k)) { // value, value, ... - $vx[] = $this->formatValue($v); - } elseif (str_ends_with($k, '=')) { // key+=value, key-=value, ... - $k2 = $this->delimite(substr($k, 0, -2)); - $vx[] = $k2 . '=' . $k2 . ' ' . substr($k, -2, 1) . ' ' . $this->formatValue($v); - } else { // key=value, key=value, ... - $vx[] = $this->delimite($k) . '=' . $this->formatValue($v); - } - } + /** + * Output format: key=value, key=value, ... + */ + private function formatSet(array $items): string + { + $res = []; + foreach ($items as $k => $v) { + if (is_int($k)) { // value, value, ... + $res[] = $this->formatValue($v); + } elseif (str_ends_with($k, '=')) { // key+=value, key-=value, ... + $col = $this->delimit(substr($k, 0, -2)); + $res[] = $col . '=' . $col . ' ' . substr($k, -2, 1) . ' ' . $this->formatValue($v); + } else { // key=value, key=value, ... + $res[] = $this->delimit($k) . '=' . $this->formatValue($v); + } + } - return implode(', ', $vx); + return implode(', ', $res); + } - } elseif ($mode === self::ModeList) { // value, value, ... | (tuple), (tuple), ... - foreach ($value as $v) { - $vx[] = is_array($v) - ? '(' . $this->formatValue($v, self::ModeList) . ')' - : $this->formatValue($v); - } - return implode(', ', $vx); + /** + * Output format: (key [operator] value) AND/OR ... + */ + private function formatWhere(array $items, string $mode): string + { + $default = '1=1'; + $res = []; + foreach ($items as $k => $v) { + if (is_int($k)) { + $res[] = $this->formatValue($v); + continue; + } - } elseif ($mode === self::ModeAnd || $mode === self::ModeOr) { // (key [operator] value) AND ... - foreach ($value as $k => $v) { - if (is_int($k)) { - $vx[] = $this->formatValue($v); - continue; - } + [$k, $operator] = explode(' ', $k, 2) + [1 => '']; + $k = $this->delimit($k); + if (is_array($v)) { + $kind = ['' => true, 'IN' => true, 'NOT' => false, 'NOT IN' => false][$operator] ?? null; + if ($v || $kind === null) { + $res[] = $k . ' ' . ($kind === null ? $operator : ($kind ? 'IN' : 'NOT IN')) . ' (' . $this->formatList(array_values($v)) . ')'; - [$k, $operator] = explode(' ', $k . ' '); - $k = $this->delimite($k); - if (is_array($v)) { - if ($v) { - $vx[] = $k . ' ' . ($operator ? $operator . ' ' : '') . 'IN (' . $this->formatValue(array_values($v), self::ModeList) . ')'; - } elseif ($operator === 'NOT') { - } else { - $vx[] = '1=0'; - } - } else { - $v = $this->formatValue($v); - $operator = $v === 'NULL' - ? ($operator === 'NOT' ? 'IS NOT' : ($operator ?: 'IS')) - : ($operator ?: '='); - $vx[] = $k . ' ' . $operator . ' ' . $v; + } else { + $default = $kind ? '1=0' : '1=1'; + if ($kind === ($mode === self::ModeAnd)) { + return "($default)"; } } - return $value - ? '(' . implode(') ' . strtoupper($mode) . ' (', $vx) . ')' - : '1=1'; - - } elseif ($mode === self::ModeOrder) { // key, key DESC, ... - foreach ($value as $k => $v) { - $vx[] = $this->delimite($k) . ($v > 0 ? '' : ' DESC'); - } - - return implode(', ', $vx); - } else { - throw new Nette\InvalidArgumentException("Unknown placeholder ?$mode."); + $v = $this->formatValue($v); + $operator = ['' => ['=', 'IS'], 'NOT' => ['!=', 'IS NOT']][$operator][$v === 'NULL'] ?? $operator; + $res[] = $k . ' ' . $operator . ' ' . $v; } - } elseif (in_array($mode, self::Modes, strict: true)) { - $type = get_debug_type($value); - throw new Nette\InvalidArgumentException("Placeholder ?$mode expects array or Traversable object, $type given."); + } - } elseif ($mode && $mode !== self::ModeAuto) { - throw new Nette\InvalidArgumentException("Unknown placeholder ?$mode."); + return count($res) > 1 + ? '((' . implode(') ' . strtoupper($mode) . ' (', $res) . '))' + : '(' . ($res[0] ?? $default) . ')'; + } - } else { - throw new Nette\InvalidArgumentException('Unexpected type of parameter: ' . get_debug_type($value)); + + /** + * Output format: key, key DESC, ... + */ + private function formatOrderBy(array $items): string + { + $res = []; + foreach ($items as $k => $v) { + $res[] = $this->delimit($k) . ($v > 0 ? '' : ' DESC'); } + + return implode(', ', $res); + } + + + /** + * Incorporates literal into SQL query. + */ + private function formatLiteral(SqlLiteral $value): string + { + [$res, $params] = (clone $this)->process([$value->getSql(), ...$value->getParameters()], $this->useParams); + $this->remaining = array_merge($this->remaining, $params); + return $res; } /** - * Adds delimiters around database identifier. + * Escapes and delimits identifier for use in SQL query. */ - private function delimite(string $name): string + private function delimit(string $name): string { return implode('.', array_map($this->driver->delimite(...), explode('.', $name))); } diff --git a/app/vendor/nette/database/src/Database/Table/GroupedSelection.php b/app/vendor/nette/database/src/Database/Table/GroupedSelection.php index 6e9f4c160..f82c2cc0b 100644 --- a/app/vendor/nette/database/src/Database/Table/GroupedSelection.php +++ b/app/vendor/nette/database/src/Database/Table/GroupedSelection.php @@ -215,7 +215,6 @@ protected function loadRefCache(): void $this->observeCache = &$referencing['observeCache']; $this->refCacheCurrent = &$referencing[$hash]; $this->accessedColumns = &$referencing[$hash]['accessed']; - $this->specificCacheKey = &$referencing[$hash]['specificCacheKey']; $this->rows = &$referencing[$hash]['rows']; if (isset($referencing[$hash]['data'][$this->active])) { diff --git a/app/vendor/nette/database/src/Database/Table/Selection.php b/app/vendor/nette/database/src/Database/Table/Selection.php index 081d839ff..6029e1b78 100644 --- a/app/vendor/nette/database/src/Database/Table/Selection.php +++ b/app/vendor/nette/database/src/Database/Table/Selection.php @@ -131,6 +131,7 @@ public function getPrimarySequence(): ?string } + /** @return static */ public function setPrimarySequence(string $sequence): static { $this->primarySequence = $sequence; @@ -183,7 +184,7 @@ public function get(mixed $key): ?ActiveRow /** - * Fetches next row of result. + * Returns the next row or null if there are no more rows. * @return T|null */ public function fetch(): ?ActiveRow @@ -215,7 +216,10 @@ public function fetchField(?string $column = null): mixed /** - * Fetches all rows as associative array. + * Returns all rows as associative array, where first argument specifies key column and second value column. + * For duplicate keys, the last value is used. When using null as key, array is indexed from zero. + * Alternatively accepts callback returning value or key-value pairs. + * @return array */ public function fetchPairs(string|int|\Closure|null $keyOrCallback = null, string|int|null $value = null): array { @@ -224,7 +228,7 @@ public function fetchPairs(string|int|\Closure|null $keyOrCallback = null, strin /** - * Fetches all rows. + * Returns all rows. * @return T[] */ public function fetchAll(): array @@ -235,6 +239,7 @@ public function fetchAll(): array /** * Returns all rows as associative tree. + * @deprecated */ public function fetchAssoc(string $path): array { @@ -249,6 +254,7 @@ public function fetchAssoc(string $path): array /** * Adds select clause, more calls append to the end. * @param string $columns for example "column, MD5(column) AS column_md5" + * @return static */ public function select(string $columns, ...$params): static { @@ -260,6 +266,7 @@ public function select(string $columns, ...$params): static /** * Adds condition for primary key. + * @return static */ public function wherePrimary(mixed $key): static { @@ -284,6 +291,7 @@ public function wherePrimary(mixed $key): static /** * Adds where condition, more calls append with AND. * @param string|array $condition possibly containing ? + * @return static */ public function where(string|array $condition, ...$params): static { @@ -296,6 +304,7 @@ public function where(string|array $condition, ...$params): static * Adds ON condition when joining specified table, more calls appends with AND. * @param string $tableChain table chain or table alias for which you need additional left join condition * @param string $condition possibly containing ? + * @return static */ public function joinWhere(string $tableChain, string $condition, ...$params): static { @@ -332,6 +341,7 @@ protected function condition(string|array $condition, array $params, ?string $ta * More calls appends with AND. * @param array $parameters ['column1' => 1, 'column2 > ?' => 2, 'full condition'] * @throws Nette\InvalidArgumentException + * @return static */ public function whereOr(array $parameters): static { @@ -366,6 +376,7 @@ public function whereOr(array $parameters): static /** * Adds ORDER BY clause, more calls appends to the end. * @param string $columns for example 'column1, column2 DESC' + * @return static */ public function order(string $columns, ...$params): static { @@ -377,6 +388,7 @@ public function order(string $columns, ...$params): static /** * Sets LIMIT clause, more calls rewrite old values. + * @return static */ public function limit(?int $limit, ?int $offset = null): static { @@ -388,6 +400,7 @@ public function limit(?int $limit, ?int $offset = null): static /** * Sets OFFSET using page number, more calls rewrite old values. + * @return static */ public function page(int $page, int $itemsPerPage, &$numOfPages = null): static { @@ -405,6 +418,7 @@ public function page(int $page, int $itemsPerPage, &$numOfPages = null): static /** * Sets GROUP BY clause, more calls rewrite old value. + * @return static */ public function group(string $columns, ...$params): static { @@ -416,6 +430,7 @@ public function group(string $columns, ...$params): static /** * Sets HAVING clause, more calls rewrite old value. + * @return static */ public function having(string $having, ...$params): static { @@ -427,6 +442,7 @@ public function having(string $having, ...$params): static /** * Aliases table. Example ':book:book_tag.tag', 'tg' + * @return static */ public function alias(string $tableChain, string $alias): static { @@ -548,21 +564,24 @@ protected function execute(): void } + /** @deprecated */ protected function createRow(array $row): ActiveRow { - return new ActiveRow($row, $this); + return $this->explorer->createActiveRow($row, $this); } + /** @deprecated */ public function createSelectionInstance(?string $table = null): self { - return new self($this->explorer, $this->conventions, $table ?: $this->name, $this->cache?->getStorage()); + return $this->explorer->table($table ?: $this->name); } + /** @deprecated */ protected function createGroupedSelectionInstance(string $table, string $column): GroupedSelection { - return new GroupedSelection($this->explorer, $this->conventions, $table, $column, $this, $this->cache?->getStorage()); + return $this->explorer->createGroupedSelection($this, $table, $column); } @@ -952,6 +971,7 @@ public function getReferencingTable( if (!$prototype) { $prototype = $this->createGroupedSelectionInstance($table, $column); $prototype->where("$table.$column", array_keys((array) $this->rows)); + $prototype->getSpecificCacheKey(); } $clone = clone $prototype; diff --git a/app/vendor/nette/database/src/Database/Table/SqlBuilder.php b/app/vendor/nette/database/src/Database/Table/SqlBuilder.php index 6b0c198b8..a48d64e9e 100644 --- a/app/vendor/nette/database/src/Database/Table/SqlBuilder.php +++ b/app/vendor/nette/database/src/Database/Table/SqlBuilder.php @@ -352,18 +352,11 @@ protected function addCondition( } } - if ($this->driver->isSupported(Driver::SupportSubselect)) { - $arg = null; - $subSelectPlaceholderCount = substr_count($clone->getSql(), '?'); - $replace = $match[2][0] . '(' . $clone->getSql() . (!$subSelectPlaceholderCount && count($clone->getSqlBuilder()->getParameters()) === 1 ? ' ?' : '') . ')'; - if (count($clone->getSqlBuilder()->getParameters())) { - array_unshift($params, ...$clone->getSqlBuilder()->getParameters()); - } - } else { - $arg = []; - foreach ($clone as $row) { - $arg[] = array_values(iterator_to_array($row)); - } + $arg = null; + $subSelectPlaceholderCount = substr_count($clone->getSql(), '?'); + $replace = $match[2][0] . '(' . $clone->getSql() . (!$subSelectPlaceholderCount && count($clone->getSqlBuilder()->getParameters()) === 1 ? ' ?' : '') . ')'; + if (count($clone->getSqlBuilder()->getParameters())) { + array_unshift($params, ...$clone->getSqlBuilder()->getParameters()); } } @@ -842,7 +835,7 @@ private function getConditionHash(string $condition, array $parameters): string if ($parameter instanceof Selection) { $parameter = $this->getConditionHash($parameter->getSql(), $parameter->getSqlBuilder()->getParameters()); } elseif ($parameter instanceof SqlLiteral) { - $parameter = $this->getConditionHash($parameter->__toString(), $parameter->getParameters()); + $parameter = $this->getConditionHash($parameter->getSql(), $parameter->getParameters()); } elseif ($parameter instanceof \Stringable) { $parameter = $parameter->__toString(); } elseif (is_array($parameter) || $parameter instanceof \ArrayAccess) { diff --git a/app/vendor/nette/http/src/Bridges/HttpTracy/SessionPanel.php b/app/vendor/nette/http/src/Bridges/HttpTracy/SessionPanel.php index b1590eecf..ec71bbc26 100644 --- a/app/vendor/nette/http/src/Bridges/HttpTracy/SessionPanel.php +++ b/app/vendor/nette/http/src/Bridges/HttpTracy/SessionPanel.php @@ -24,7 +24,7 @@ class SessionPanel implements Tracy\IBarPanel public function getTab(): string { return Nette\Utils\Helpers::capture(function () { - require __DIR__ . '/templates/SessionPanel.tab.phtml'; + require __DIR__ . '/dist/tab.phtml'; }); } @@ -35,7 +35,7 @@ public function getTab(): string public function getPanel(): string { return Nette\Utils\Helpers::capture(function () { - require __DIR__ . '/templates/SessionPanel.panel.phtml'; + require __DIR__ . '/dist/panel.phtml'; }); } } diff --git a/app/vendor/nette/http/src/Bridges/HttpTracy/dist/panel.phtml b/app/vendor/nette/http/src/Bridges/HttpTracy/dist/panel.phtml new file mode 100644 index 000000000..8af6af4f9 --- /dev/null +++ b/app/vendor/nette/http/src/Bridges/HttpTracy/dist/panel.phtml @@ -0,0 +1,32 @@ + + + +

Session # +… (Lifetime: +)

+ +
+

empty

+ + $v): ?> + + + + + + + +
Nette Session true]) ?> +
+ true]) ?> +
+
diff --git a/app/vendor/nette/http/src/Bridges/HttpTracy/dist/tab.phtml b/app/vendor/nette/http/src/Bridges/HttpTracy/dist/tab.phtml new file mode 100644 index 000000000..6a84413ab --- /dev/null +++ b/app/vendor/nette/http/src/Bridges/HttpTracy/dist/tab.phtml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/vendor/nette/http/src/Bridges/HttpTracy/panel.latte b/app/vendor/nette/http/src/Bridges/HttpTracy/panel.latte new file mode 100644 index 000000000..4607e3474 --- /dev/null +++ b/app/vendor/nette/http/src/Bridges/HttpTracy/panel.latte @@ -0,0 +1,32 @@ + + +

Session #{substr(session_id(), 0, 10)}… (Lifetime: {ini_get('session.cookie_lifetime')})

+ +
+ {if empty($_SESSION)} +

empty

+ {else} + + {foreach $_SESSION as $k => $v} + {if $k === __NF} + + + + + {elseif $k !== '_tracy'} + + + + + {/if} + {/foreach} +
Nette Session{Tracy\Dumper::toHtml($v[DATA] ?? null, [Tracy\Dumper::LIVE => true])}
{$k}{Tracy\Dumper::toHtml($v, [Tracy\Dumper::LIVE => true])}
+ {/if} +
diff --git a/app/vendor/nette/http/src/Bridges/HttpTracy/tab.latte b/app/vendor/nette/http/src/Bridges/HttpTracy/tab.latte new file mode 100644 index 000000000..0574eab4a --- /dev/null +++ b/app/vendor/nette/http/src/Bridges/HttpTracy/tab.latte @@ -0,0 +1,5 @@ + + + + + diff --git a/app/vendor/nette/http/src/Bridges/HttpTracy/templates/SessionPanel.panel.phtml b/app/vendor/nette/http/src/Bridges/HttpTracy/templates/SessionPanel.panel.phtml deleted file mode 100644 index 29084fa29..000000000 --- a/app/vendor/nette/http/src/Bridges/HttpTracy/templates/SessionPanel.panel.phtml +++ /dev/null @@ -1,38 +0,0 @@ - - - -

Session #… (Lifetime: )

- -
- -

empty

- - - $v) { - if ($k === '__NF') { - $k = 'Nette Session'; - $v = $v['DATA'] ?? null; - } elseif ($k === '_tracy') { - continue; - } - echo '\n"; - }?> -
', Helpers::escapeHtml($k), '', Dumper::toHtml($v, [Dumper::LIVE => true]), "
- -
diff --git a/app/vendor/nette/http/src/Bridges/HttpTracy/templates/SessionPanel.tab.phtml b/app/vendor/nette/http/src/Bridges/HttpTracy/templates/SessionPanel.tab.phtml deleted file mode 100644 index 64e5f7da3..000000000 --- a/app/vendor/nette/http/src/Bridges/HttpTracy/templates/SessionPanel.tab.phtml +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/app/vendor/nette/http/src/Http/FileUpload.php b/app/vendor/nette/http/src/Http/FileUpload.php index 4afd6656c..76e48f753 100644 --- a/app/vendor/nette/http/src/Http/FileUpload.php +++ b/app/vendor/nette/http/src/Http/FileUpload.php @@ -42,13 +42,16 @@ final class FileUpload private readonly int $error; - public function __construct(?array $value) + public function __construct(array|string|null $value) { - foreach (['name', 'size', 'tmp_name', 'error'] as $key) { - if (!isset($value[$key]) || !is_scalar($value[$key])) { - $value = []; - break; - } + if (is_string($value)) { + $value = [ + 'name' => basename($value), + 'full_path' => $value, + 'size' => filesize($value), + 'tmp_name' => $value, + 'error' => UPLOAD_ERR_OK, + ]; } $this->name = $value['name'] ?? ''; diff --git a/app/vendor/nette/http/src/Http/Url.php b/app/vendor/nette/http/src/Http/Url.php index 151766686..de9860f02 100644 --- a/app/vendor/nette/http/src/Http/Url.php +++ b/app/vendor/nette/http/src/Http/Url.php @@ -332,7 +332,7 @@ public function isEqual(string|self|UrlImmutable $url): bool public function canonicalize(): static { $this->path = preg_replace_callback( - '#[^!$&\'()*+,/:;=@%]+#', + '#[^!$&\'()*+,/:;=@%"]+#', fn(array $m): string => rawurlencode($m[0]), self::unescape($this->path, '%/'), ); @@ -409,4 +409,40 @@ public static function parseQuery(string $s): array parse_str($s, $res); return $res[0] ?? []; } + + + /** + * Determines if URL is absolute, ie if it starts with a scheme followed by colon. + */ + public static function isAbsolute(string $url): bool + { + return (bool) preg_match('#^[a-z][a-z0-9+.-]*:#i', $url); + } + + + /** + * Normalizes a path by handling and removing relative path references like '.', '..' and directory traversal. + */ + public static function removeDotSegments(string $path): string + { + $prefix = $segment = ''; + if (str_starts_with($path, '/')) { + $prefix = '/'; + $path = substr($path, 1); + } + $segments = explode('/', $path); + $res = []; + foreach ($segments as $segment) { + if ($segment === '..') { + array_pop($res); + } elseif ($segment !== '.') { + $res[] = $segment; + } + } + + if ($segment === '.' || $segment === '..') { + $res[] = ''; + } + return $prefix . implode('/', $res); + } } diff --git a/app/vendor/nette/http/src/Http/UrlImmutable.php b/app/vendor/nette/http/src/Http/UrlImmutable.php index ea9e85b82..9702135fd 100644 --- a/app/vendor/nette/http/src/Http/UrlImmutable.php +++ b/app/vendor/nette/http/src/Http/UrlImmutable.php @@ -50,7 +50,7 @@ class UrlImmutable implements \JsonSerializable private string $path = ''; private array $query = []; private string $fragment = ''; - private string $authority = ''; + private ?string $authority = null; /** @@ -60,7 +60,6 @@ public function __construct(string|self|Url $url) { $url = is_string($url) ? new Url($url) : $url; [$this->scheme, $this->user, $this->password, $this->host, $this->port, $this->path, $this->query, $this->fragment] = $url->export(); - $this->build(); } @@ -68,7 +67,7 @@ public function withScheme(string $scheme): static { $dolly = clone $this; $dolly->scheme = $scheme; - $dolly->build(); + $dolly->authority = null; return $dolly; } @@ -83,7 +82,7 @@ public function withUser(string $user): static { $dolly = clone $this; $dolly->user = $user; - $dolly->build(); + $dolly->authority = null; return $dolly; } @@ -98,7 +97,7 @@ public function withPassword(string $password): static { $dolly = clone $this; $dolly->password = $password; - $dolly->build(); + $dolly->authority = null; return $dolly; } @@ -113,7 +112,7 @@ public function withoutUserInfo(): static { $dolly = clone $this; $dolly->user = $dolly->password = ''; - $dolly->build(); + $dolly->authority = null; return $dolly; } @@ -122,8 +121,8 @@ public function withHost(string $host): static { $dolly = clone $this; $dolly->host = $host; - $dolly->build(); - return $dolly; + $dolly->authority = null; + return $dolly->setPath($dolly->path); } @@ -149,7 +148,7 @@ public function withPort(int $port): static { $dolly = clone $this; $dolly->port = $port; - $dolly->build(); + $dolly->authority = null; return $dolly; } @@ -168,10 +167,14 @@ public function getDefaultPort(): ?int public function withPath(string $path): static { - $dolly = clone $this; - $dolly->path = $path; - $dolly->build(); - return $dolly; + return (clone $this)->setPath($path); + } + + + private function setPath(string $path): static + { + $this->path = $this->host && !str_starts_with($path, '/') ? '/' . $path : $path; + return $this; } @@ -185,7 +188,6 @@ public function withQuery(string|array $query): static { $dolly = clone $this; $dolly->query = is_array($query) ? $query : Url::parseQuery($query); - $dolly->build(); return $dolly; } @@ -220,7 +222,6 @@ public function withFragment(string $fragment): static { $dolly = clone $this; $dolly->fragment = $fragment; - $dolly->build(); return $dolly; } @@ -247,7 +248,15 @@ public function getAbsoluteUrl(): string */ public function getAuthority(): string { - return $this->authority; + return $this->authority ??= $this->host === '' + ? '' + : ($this->user !== '' + ? rawurlencode($this->user) . ($this->password === '' ? '' : ':' . rawurlencode($this->password)) . '@' + : '') + . $this->host + . ($this->port && $this->port !== $this->getDefaultPort() + ? ':' . $this->port + : ''); } @@ -256,8 +265,8 @@ public function getAuthority(): string */ public function getHostUrl(): string { - return ($this->scheme ? $this->scheme . ':' : '') - . ($this->authority !== '' ? '//' . $this->authority : ''); + return ($this->scheme === '' ? '' : $this->scheme . ':') + . ($this->host === '' ? '' : '//' . $this->getAuthority()); } @@ -273,33 +282,57 @@ public function isEqual(string|Url|self $url): bool } - public function jsonSerialize(): string + /** + * Resolves relative URLs in the same way as browser. If path is relative, it is resolved against + * base URL, if begins with /, it is resolved against the host root. + */ + public function resolve(string $reference): self { - return $this->getAbsoluteUrl(); + $ref = new self($reference); + if ($ref->scheme !== '') { + $ref->path = Url::removeDotSegments($ref->path); + return $ref; + } + + $ref->scheme = $this->scheme; + + if ($ref->host !== '') { + $ref->path = Url::removeDotSegments($ref->path); + return $ref; + } + + $ref->host = $this->host; + $ref->port = $this->port; + + if ($ref->path === '') { + $ref->path = $this->path; + $ref->query = $ref->query ?: $this->query; + } elseif (str_starts_with($ref->path, '/')) { + $ref->path = Url::removeDotSegments($ref->path); + } else { + $ref->path = Url::removeDotSegments($this->mergePath($ref->path)); + } + return $ref; } /** @internal */ - final public function export(): array + protected function mergePath(string $path): string { - return [$this->scheme, $this->user, $this->password, $this->host, $this->port, $this->path, $this->query, $this->fragment]; + $pos = strrpos($this->path, '/'); + return $pos === false ? $path : substr($this->path, 0, $pos + 1) . $path; } - protected function build(): void + public function jsonSerialize(): string { - if ($this->host && !str_starts_with($this->path, '/')) { - $this->path = '/' . $this->path; - } + return $this->getAbsoluteUrl(); + } - $this->authority = $this->host === '' - ? '' - : ($this->user !== '' - ? rawurlencode($this->user) . ($this->password === '' ? '' : ':' . rawurlencode($this->password)) . '@' - : '') - . $this->host - . ($this->port && $this->port !== $this->getDefaultPort() - ? ':' . $this->port - : ''); + + /** @internal */ + final public function export(): array + { + return [$this->scheme, $this->user, $this->password, $this->host, $this->port, $this->path, $this->query, $this->fragment]; } } diff --git a/app/vendor/nette/http/src/Http/UrlScript.php b/app/vendor/nette/http/src/Http/UrlScript.php index 9de766d66..b38865088 100644 --- a/app/vendor/nette/http/src/Http/UrlScript.php +++ b/app/vendor/nette/http/src/Http/UrlScript.php @@ -40,18 +40,30 @@ class UrlScript extends UrlImmutable public function __construct(string|Url $url = '/', string $scriptPath = '') { - $this->scriptPath = $scriptPath; parent::__construct($url); - $this->build(); + $this->setScriptPath($scriptPath); } public function withPath(string $path, string $scriptPath = ''): static { - $dolly = clone $this; - $dolly->scriptPath = $scriptPath; - $parent = UrlImmutable::withPath(...)->bindTo($dolly); - return $parent($path); + $dolly = parent::withPath($path); + $dolly->setScriptPath($scriptPath); + return $dolly; + } + + + private function setScriptPath(string $scriptPath): void + { + $path = $this->getPath(); + $scriptPath = $scriptPath ?: $path; + $pos = strrpos($scriptPath, '/'); + if ($pos === false || strncmp($scriptPath, $path, $pos + 1)) { + throw new Nette\InvalidArgumentException("ScriptPath '$scriptPath' doesn't match path '$path'"); + } + + $this->scriptPath = $scriptPath; + $this->basePath = substr($scriptPath, 0, $pos + 1); } @@ -94,16 +106,9 @@ public function getPathInfo(): string } - protected function build(): void + /** @internal */ + protected function mergePath(string $path): string { - parent::build(); - $path = $this->getPath(); - $this->scriptPath = $this->scriptPath ?: $path; - $pos = strrpos($this->scriptPath, '/'); - if ($pos === false || strncmp($this->scriptPath, $path, $pos + 1)) { - throw new Nette\InvalidArgumentException("ScriptPath '$this->scriptPath' doesn't match path '$path'"); - } - - $this->basePath = substr($this->scriptPath, 0, $pos + 1); + return $this->basePath . $path; } }