From 0c2fc68072de5b46219037a54d365a7eea6385c6 Mon Sep 17 00:00:00 2001 From: Artur Kyryliuk Date: Tue, 11 Aug 2026 03:00:26 +0200 Subject: [PATCH 1/4] fix(core): static analysis findings --- core/src/ManagerTheme.php | 20 +++++++++++++++---- .../Store/PackageInstallFlowService.php | 9 ++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/src/ManagerTheme.php b/core/src/ManagerTheme.php index d9ea699e83..9425b85c6d 100644 --- a/core/src/ManagerTheme.php +++ b/core/src/ManagerTheme.php @@ -563,13 +563,15 @@ public function isAuthManager() @session_destroy(); } - if (is_file(EVO_BASE_PATH . 'assets/cache/installProc.inc.php')) { - include_once(EVO_BASE_PATH . 'assets/cache/installProc.inc.php'); + $installProcFile = $this->getInstallProcFile(); + + if (is_file($installProcFile)) { + include_once($installProcFile); if (isset($installStartTime)) { if ((time() - $installStartTime) > 5 * 60) { // if install flag older than 5 minutes, discard unset($installStartTime); - @ chmod(EVO_BASE_PATH . 'assets/cache/installProc.inc.php', 0755); - unlink(EVO_BASE_PATH . 'assets/cache/installProc.inc.php'); + @ chmod($installProcFile, 0755); + unlink($installProcFile); } else { if ($_SERVER['REQUEST_METHOD'] != 'POST') { if (isset($_COOKIE[session_name()])) { @@ -601,6 +603,16 @@ public function isAuthManager() return isset($_SESSION['mgrValidated']); } + /** + * Path to the install flag file generated by the installer. + * + * @return string + */ + protected function getInstallProcFile(): string + { + return EVO_BASE_PATH . 'assets/cache/installProc.inc.php'; + } + public function hasManagerAccess() { // check if user is allowed to access manager interface diff --git a/core/src/Services/Store/PackageInstallFlowService.php b/core/src/Services/Store/PackageInstallFlowService.php index eceee69826..d10c67c79b 100644 --- a/core/src/Services/Store/PackageInstallFlowService.php +++ b/core/src/Services/Store/PackageInstallFlowService.php @@ -105,9 +105,12 @@ public function handleLegacyInstall(string $action, array $request, array $files $cwd = getcwd(); chdir($this->modulePath . '/installer/'); - ob_start(); - require "instprocessor-fast.php"; - ob_end_clean(); + $fastProcessor = $this->modulePath . '/installer/instprocessor-fast.php'; + if (is_file($fastProcessor)) { + ob_start(); + include $fastProcessor; // @todo [remove@3.7] Remove in Evolution CMS 3.7 + ob_end_clean(); + } chdir($cwd); if (is_dir(EVO_BASE_PATH . 'assets/cache/store/')) { From 219383e37129e7b4b308955a3b884cae6ba49622 Mon Sep 17 00:00:00 2001 From: Artur Kyryliuk Date: Tue, 11 Aug 2026 03:03:42 +0200 Subject: [PATCH 2/4] add(ci): SQLite based CMS install on PHP8.3 and 8.4 saved to zip, GHCR --- .github/docker/Dockerfile | 18 ++++ .github/workflows/build.yml | 200 ++++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 93 +++++++++++++++++ .gitignore | 1 + install/cli-install.php | 63 +++++++++++- 5 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 .github/docker/Dockerfile create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/ci.yml diff --git a/.github/docker/Dockerfile b/.github/docker/Dockerfile new file mode 100644 index 0000000000..7e78509663 --- /dev/null +++ b/.github/docker/Dockerfile @@ -0,0 +1,18 @@ +# Runs the pre-installed sqlite build produced by .github/workflows/build.yml. +# +# The base image is built from evolution-cms/salo2 runtimes/8.4 (Salo is the +# Evolution CMS flavour of Laravel Sail): php:8.4-apache with gd, zip, pdo and +# mod_rewrite. sqlite3/pdo_sqlite come enabled in the official php images. +ARG BASE_IMAGE=evo-salo-runtime:8.4 +FROM ${BASE_IMAGE} + +COPY build/ /var/www/html/ + +# Friendly URLs: the repository ships the rules as ht.access. +RUN if [ -f /var/www/html/ht.access ] && [ ! -f /var/www/html/.htaccess ]; then \ + cp /var/www/html/ht.access /var/www/html/.htaccess; \ + fi \ + && chown -R www-data:www-data /var/www/html \ + && a2enmod rewrite + +EXPOSE 80 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..5dc69cfd57 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,200 @@ +name: Build + +on: + push: + branches: + - '3.*.x' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: build-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + # Credentials of the pre-installed demo build. They are published together + # with the artifact, so this is a testing account and nothing else. + DEMO_ADMIN: admin + DEMO_EMAIL: admin@evo.local + DEMO_PASSWORD: Passw0rd123 + DEMO_DATABASE: evolution + DEMO_PREFIX: evo_ + +jobs: + zip: + name: Installed CMS (zip, sqlite) + runs-on: ubuntu-latest + permissions: + contents: write # required to publish the nightly release + outputs: + zip-name: ${{ steps.package.outputs.zip-name }} + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + # Built on the lowest supported version so the vendor tree stays + # usable on 8.3 and 8.4 alike. + php-version: '8.3' + extensions: ctype, dom, fileinfo, filter, iconv, intl, json, mbstring, openssl, pdo, pdo_sqlite, readline, simplexml, sqlite3, tokenizer, xml, xmlreader, zip + coverage: none + tools: composer:v2 + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache composer downloads + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-php8.3-build-${{ hashFiles('core/composer.lock') }} + restore-keys: composer-${{ runner.os }}-php8.3-build- + + - name: Install core dependencies + working-directory: core + run: composer install --no-dev --no-interaction --no-progress --prefer-dist + + # --removeInstall=y drops install/ from the build, --skipComposer=y keeps + # the dependencies installed in the previous step untouched. + - name: Install Evolution CMS on sqlite + working-directory: install + run: | + php cli-install.php \ + --typeInstall=1 \ + --databaseType=sqlite \ + --database="$DEMO_DATABASE" \ + --tablePrefix="$DEMO_PREFIX" \ + --cmsAdmin="$DEMO_ADMIN" \ + --cmsAdminEmail="$DEMO_EMAIL" \ + --cmsPassword="$DEMO_PASSWORD" \ + --language=en \ + --removeInstall=y \ + --skipComposer=y < /dev/null + + # The installer writes the absolute path of the build machine into the + # connection config. Resolve it at runtime instead, so the archive keeps + # working wherever it is unpacked. + - name: Make the sqlite path portable + run: | + config=core/config/database/connections/default.php + sed -i "s#'database' => env('DB_DATABASE', '.*'),#'database' => env('DB_DATABASE', dirname(__DIR__, 3) . '/database/${DEMO_DATABASE}.sqlite'),#" "$config" + grep -q "dirname(__DIR__, 3)" "$config" + php -l "$config" + + - name: Verify the build answers over HTTP + run: | + php -S 127.0.0.1:8899 -t . > /tmp/server.log 2>&1 & + for i in $(seq 1 15); do + sleep 1 + curl -sf -o /dev/null http://127.0.0.1:8899/ && break + done + front=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8899/) + # The manager answers 404 without an Accept-Language header by design. + manager=$(curl -s -o /dev/null -w '%{http_code}' -H 'Accept-Language: en-US,en;q=0.9' http://127.0.0.1:8899/manager/index.php) + echo "front=$front manager=$manager" + test "$front" = "200" && test "$manager" = "200" + + - name: Package + id: package + run: | + version=$(php -r '$c = json_decode(file_get_contents("core/composer.json"), true); echo $c["version"] ?? "0.0.0";') + name="evolution-cms-${version}-${GITHUB_REF_NAME//\//-}-$(git rev-parse --short HEAD)-sqlite.zip" + # install/ is left empty by --removeInstall=y; the rest is repository + # tooling that has no place in a distributable build. + rm -rf .git .github install core/tests core/phpunit.xml AGENTS.md + # A leading "." keeps dotfiles such as core/.install, which the + # manager requires to consider the CMS installed. + zip -qr "/tmp/${name}" . + mv "/tmp/${name}" . + ls -lh "${name}" + echo "zip-name=${name}" >> "$GITHUB_OUTPUT" + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: evolution-cms-sqlite + path: ${{ steps.package.outputs.zip-name }} + retention-days: 14 + compression-level: 0 # already a zip + + # Nightly release: anonymous direct download, unlike workflow artifacts. + # The tag is recreated on every push, so the link always serves the + # latest build of the branch. + - name: Publish nightly release + if: github.event_name != 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + ZIP: ${{ steps.package.outputs.zip-name }} + run: | + tag="nightly-${GITHUB_REF_NAME}" + # .git is gone by now, so every call is repo-qualified explicitly. + gh release delete "$tag" --repo "$GITHUB_REPOSITORY" --cleanup-tag --yes 2>/dev/null || true + gh release create "$tag" "$ZIP" \ + --repo "$GITHUB_REPOSITORY" \ + --prerelease \ + --target "$GITHUB_SHA" \ + --title "Nightly ${GITHUB_REF_NAME} ($(date -u +%Y-%m-%d))" \ + --notes "Automated build of \`${GITHUB_REF_NAME}\` at ${GITHUB_SHA}. + + Pre-installed Evolution CMS on sqlite — unpack into a docroot and open it, no database server needed. + + Manager: \`${DEMO_ADMIN}\` / \`${DEMO_PASSWORD}\` + + A testing build with published credentials. Not for production." + + docker: + name: Docker image (GHCR) + needs: zip + # Forked pull requests get a read-only token and cannot push packages. + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + + - name: Download the build + uses: actions/download-artifact@v4 + with: + name: evolution-cms-sqlite + path: /tmp/artifact + + - name: Unpack the build + run: | + mkdir -p build + unzip -q "/tmp/artifact/${{ needs.zip.outputs.zip-name }}" -d build + + # The runtime recipe comes from Salo, the Evolution CMS flavour of Sail: + # php:8.4-apache with gd/zip/pdo and mod_rewrite enabled. + - name: Build the Salo runtime image + run: | + git clone --depth 1 https://github.com/evolution-cms/salo2 /tmp/salo2 + docker build -t evo-salo-runtime:8.4 /tmp/salo2/runtimes/8.4 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + run: | + owner=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') + image="ghcr.io/${owner}/evolution" + docker build \ + -f .github/docker/Dockerfile \ + -t "${image}:${GITHUB_REF_NAME}" \ + -t "${image}:${GITHUB_REF_NAME}-$(git rev-parse --short HEAD)" \ + . + docker push --all-tags "${image}" + echo "### Image" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + echo "docker run --rm -p 8080:80 ${image}:${GITHUB_REF_NAME}" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..cdc678df01 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,93 @@ +name: CI + +on: + push: + branches: + - '3.*.x' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + PHP_EXTENSIONS: ctype, dom, fileinfo, filter, iconv, intl, json, mbstring, openssl, pdo, pdo_sqlite, readline, simplexml, tokenizer, xml, xmlreader, zip + +jobs: + analyze: + name: Static analysis (PHP ${{ matrix.php }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.3', '8.4'] + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.PHP_EXTENSIONS }} + coverage: none + tools: composer:v2 + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache composer downloads + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-php${{ matrix.php }}-analyze-${{ hashFiles('composer.json') }} + restore-keys: composer-${{ runner.os }}-php${{ matrix.php }}-analyze- + + # Only the root dependencies (PHPStan) are installed here: + # core/vendor is committed and provides everything phpstan.neon bootstraps. + - name: Install root dependencies + run: composer install --no-interaction --no-progress --prefer-dist + + - name: Run PHPStan + run: composer analyze + + tests: + name: Unit tests (PHP ${{ matrix.php }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.3', '8.4'] + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.PHP_EXTENSIONS }} + coverage: none + tools: composer:v2 + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache composer downloads + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-php${{ matrix.php }}-core-${{ hashFiles('core/composer.lock') }} + restore-keys: composer-${{ runner.os }}-php${{ matrix.php }}-core- + + - name: Install core dependencies + working-directory: core + run: composer install --no-interaction --no-progress --prefer-dist + + - name: Run Pest + working-directory: core + run: composer test -- --compact diff --git a/.gitignore b/.gitignore index c190ca3df1..5c330741a0 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ Desktop.ini # all dotfiles and dotfolders except do not ignore .gitignore **/.* !.gitignore +!/.github diff --git a/install/cli-install.php b/install/cli-install.php index 5ccceeaed6..335a50634a 100644 --- a/install/cli-install.php +++ b/install/cli-install.php @@ -14,7 +14,11 @@ /** * EVO Cli Installer/Updater * php cli-install.php --typeInstall=1 --databaseType=pgsql --databaseServer=localhost --database=db_name --databaseUser=serious --databasePassword=serious --tablePrefix=evo_ --cmsAdmin=admin --cmsAdminEmail=serious2008@gmail.com --cmsPassword=123456 --language=uk --removeInstall=y + * php cli-install.php --typeInstall=1 --databaseType=sqlite --database=evolution --tablePrefix=evo_ --cmsAdmin=admin --cmsAdminEmail=serious2008@gmail.com --cmsPassword=123456 --language=uk --removeInstall=y * php cli-install.php --typeInstall=2 --removeInstall=y + * + * The sqlite database is created as core/database/<--database>.sqlite + * Use --skipComposer=y to keep the already installed dependencies untouched. **/ function runCliInstall(array $argv): void @@ -41,6 +45,7 @@ class InstallEvo public $cmsPassword = ''; public $language = ''; public $removeInstall = ''; + public $skipComposer = ''; public $database_charset = 'utf8mb4'; public $database_collation = 'utf8mb4_unicode_520_ci'; public $dbh; @@ -202,7 +207,7 @@ public function install() public function checkDatabaseType() { - $dbTypes = ['pgsql', 'mysql']; + $dbTypes = ['pgsql', 'mysql', 'sqlite']; while (!in_array($this->databaseType, $dbTypes)) { $this->databaseType = $this->choice( 'Please choose your database type:', @@ -211,8 +216,20 @@ public function checkDatabaseType() } } + /** + * A file based sqlite database has no server, user or password. + */ + public function isSqlite(): bool + { + return $this->databaseType === 'sqlite'; + } + public function checkDatabaseServer() { + if ($this->isSqlite()) { + return; + } + while ($this->databaseServer === '') { $this->databaseServer = $this->ask('Please enter database server:', 'localhost'); } @@ -227,6 +244,10 @@ public function checkDatabase() public function checkDatabaseUser() { + if ($this->isSqlite()) { + return; + } + while ($this->databaseUser === '') { $this->databaseUser = $this->ask('Please enter database user:', ''); } @@ -234,6 +255,10 @@ public function checkDatabaseUser() public function checkDatabasePassword() { + if ($this->isSqlite()) { + return; + } + while ($this->databasePassword === '') { $this->databasePassword = $this->ask('Please enter database password:', ''); } @@ -312,6 +337,27 @@ public function checkRemoveInstall() public function checkConnectToDatabase() { + if ($this->isSqlite()) { + $this->checkDatabase(); + $databaseFile = sqliteDbNameToPath($this->database); + $databaseDir = dirname($databaseFile); + + if (!is_dir($databaseDir)) { + mkdir($databaseDir, 0755, true); + } + + try { + $this->dbh = new PDO('sqlite:' . $databaseFile); + } catch (PDOException $e) { + error('✖ ' . $e->getMessage()); + $this->databaseType = ''; + $this->database = ''; + $this->install(); + } + + return; + } + try { $this->dbh = new PDO($this->databaseType . ':host=' . $this->databaseServer, $this->databaseUser, $this->databasePassword); } catch (PDOException $e) { @@ -330,6 +376,11 @@ public function checkConnectToDatabase() public function checkConnectToDatabaseWithBase() { + if ($this->isSqlite()) { + // The database file is the connection itself, it was opened already. + return; + } + $error = 0; try { $dbh_alt = new PDO($this->databaseType . ':host=' . $this->databaseServer . ';dbname=' . $this->database, $this->databaseUser, $this->databasePassword); @@ -399,6 +450,11 @@ public function checkIssetTablePrefix() public function composerUpdate() { + if ($this->skipComposer === 'y') { + info('- Composer update skipped.'); + return; + } + $disabled = array_map('trim', explode(',', ini_get('disable_functions') ?: '')); $composerBin = EVO_CORE_PATH . 'vendor/bin/composer'; $workingDir = EVO_CORE_PATH; @@ -466,6 +522,11 @@ public function writeConfig() $confph['lastInstallTime'] = time(); $confph['database_engine'] = ''; switch ($this->databaseType) { + case 'sqlite': + $confph['database_port'] = ''; + $confph['connection_charset'] = ''; + $confph['connection_collation'] = ''; + break; case 'pgsql': $confph['database_port'] = '5432'; $confph['connection_charset'] = 'utf8'; From 5e7b3a551945677705783af8df105a6c8b154197 Mon Sep 17 00:00:00 2001 From: Artur Kyryliuk Date: Tue, 11 Aug 2026 03:17:04 +0200 Subject: [PATCH 3/4] fix(core): Redis alias --- core/includes/aliases.inc.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/includes/aliases.inc.php b/core/includes/aliases.inc.php index 6644c49d84..5710d046b4 100644 --- a/core/includes/aliases.inc.php +++ b/core/includes/aliases.inc.php @@ -25,7 +25,10 @@ class Lang extends \Illuminate\Support\Facades\Lang {} class View extends \Illuminate\Support\Facades\View {} class Response extends \Illuminate\Support\Facades\Response {} class Redirect extends \Illuminate\Support\Facades\Redirect {} -class Redis extends \Illuminate\Support\Facades\Redis {} +// ext-redis already provides a global Redis class, alias the facade only without it. +if (!class_exists('Redis', false)) { + class Redis extends \Illuminate\Support\Facades\Redis {} +} class Route extends \Illuminate\Support\Facades\Route {} class Str extends \Illuminate\Support\Str {} class Config extends \EvolutionCMS\Facades\ConfigService {} From aebf7501bf2594ee03c39477e43863083814e7e1 Mon Sep 17 00:00:00 2001 From: Artur Kyryliuk Date: Tue, 11 Aug 2026 03:17:40 +0200 Subject: [PATCH 4/4] fix(ci): Node version --- .github/workflows/build.yml | 12 ++++++------ .github/workflows/ci.yml | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5dc69cfd57..036af06a51 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: outputs: zip-name: ${{ steps.package.outputs.zip-name }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -49,7 +49,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" - name: Cache composer downloads - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: ${{ steps.composer-cache.outputs.dir }} key: composer-${{ runner.os }}-php8.3-build-${{ hashFiles('core/composer.lock') }} @@ -115,7 +115,7 @@ jobs: echo "zip-name=${name}" >> "$GITHUB_OUTPUT" - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: evolution-cms-sqlite path: ${{ steps.package.outputs.zip-name }} @@ -157,10 +157,10 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Download the build - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: evolution-cms-sqlite path: /tmp/artifact @@ -178,7 +178,7 @@ jobs: docker build -t evo-salo-runtime:8.4 /tmp/salo2/runtimes/8.4 - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdc678df01..5677867ce6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: matrix: php: ['8.3', '8.4'] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -41,7 +41,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" - name: Cache composer downloads - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: ${{ steps.composer-cache.outputs.dir }} key: composer-${{ runner.os }}-php${{ matrix.php }}-analyze-${{ hashFiles('composer.json') }} @@ -63,7 +63,7 @@ jobs: matrix: php: ['8.3', '8.4'] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -78,7 +78,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" - name: Cache composer downloads - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: ${{ steps.composer-cache.outputs.dir }} key: composer-${{ runner.os }}-php${{ matrix.php }}-core-${{ hashFiles('core/composer.lock') }}