From cebdb9c108ac87eea8d7deb1860c934dfb02645a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 15:02:13 +0300 Subject: [PATCH 01/37] chore(deps-dev): bump phpstan/phpstan from 2.2.5 to 2.2.7 (#542) Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.5 to 2.2.7. - [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-version: 2.2.7 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index af5eb8f..9861087 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "744ae16dff0084e7151825d4e2157447", + "content-hash": "3465780a8769d575341db1d3e93ca51a", "packages": [ { "name": "codeinwp/themeisle-sdk", @@ -892,11 +892,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.2.5", + "version": "2.2.7", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/909c1e5fef7989ac0d0c1c5c42e32a5c4f6198a0", - "reference": "909c1e5fef7989ac0d0c1c5c42e32a5c4f6198a0", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/692db47b9dddb0487934e5236e77d48594aef921", + "reference": "692db47b9dddb0487934e5236e77d48594aef921", "shasum": "" }, "require": { @@ -952,7 +952,7 @@ "type": "github" } ], - "time": "2026-07-05T06:31:06+00:00" + "time": "2026-07-29T17:39:32+00:00" }, { "name": "phpunit/php-code-coverage", From 930ebf14280ec4d399bac28e637166a60df2da3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 15:02:16 +0300 Subject: [PATCH 02/37] chore(deps-dev): bump wp-coding-standards/wpcs from 3.4.0 to 3.4.1 (#541) Bumps [wp-coding-standards/wpcs](https://github.com/WordPress/WordPress-Coding-Standards) from 3.4.0 to 3.4.1. - [Release notes](https://github.com/WordPress/WordPress-Coding-Standards/releases) - [Changelog](https://github.com/WordPress/WordPress-Coding-Standards/blob/develop/CHANGELOG.md) - [Commits](https://github.com/WordPress/WordPress-Coding-Standards/compare/3.4.0...3.4.1) --- updated-dependencies: - dependency-name: wp-coding-standards/wpcs dependency-version: 3.4.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/composer.lock b/composer.lock index 9861087..9db1db8 100644 --- a/composer.lock +++ b/composer.lock @@ -717,21 +717,21 @@ }, { "name": "phpcsstandards/phpcsextra", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", - "reference": "b598aa890815b8df16363271b659d73280129101" + "reference": "39467533fdb742446d68c1d10ac33d625ee0311c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/b598aa890815b8df16363271b659d73280129101", - "reference": "b598aa890815b8df16363271b659d73280129101", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/39467533fdb742446d68c1d10ac33d625ee0311c", + "reference": "39467533fdb742446d68c1d10ac33d625ee0311c", "shasum": "" }, "require": { "php": ">=5.4", - "phpcsstandards/phpcsutils": "^1.2.0", + "phpcsstandards/phpcsutils": "^1.2.3", "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" }, "require-dev": { @@ -795,20 +795,20 @@ "type": "thanks_dev" } ], - "time": "2025-11-12T23:06:57+00:00" + "time": "2026-07-27T11:13:17+00:00" }, { "name": "phpcsstandards/phpcsutils", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", - "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55" + "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/c216317e96c8b3f5932808f9b0f1f7a14e3bbf55", - "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/5f35d9408c54d7b529501f3c688b6eae562aea1f", + "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f", "shasum": "" }, "require": { @@ -888,7 +888,7 @@ "type": "thanks_dev" } ], - "time": "2025-12-08T14:27:58+00:00" + "time": "2026-07-27T10:28:41+00:00" }, { "name": "phpstan/phpstan", @@ -2510,16 +2510,16 @@ }, { "name": "wp-coding-standards/wpcs", - "version": "3.4.0", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", - "reference": "469c18ceab4d642b15bad4c65ebf3b307bfd55ab" + "reference": "ec2ff942335f33683a5957a85d138753876a05cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/469c18ceab4d642b15bad4c65ebf3b307bfd55ab", - "reference": "469c18ceab4d642b15bad4c65ebf3b307bfd55ab", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/ec2ff942335f33683a5957a85d138753876a05cf", + "reference": "ec2ff942335f33683a5957a85d138753876a05cf", "shasum": "" }, "require": { @@ -2528,8 +2528,8 @@ "ext-tokenizer": "*", "ext-xmlreader": "*", "php": ">=7.2", - "phpcsstandards/phpcsextra": "^1.5.0", - "phpcsstandards/phpcsutils": "^1.2.2", + "phpcsstandards/phpcsextra": "^1.5.1", + "phpcsstandards/phpcsutils": "^1.2.3", "squizlabs/php_codesniffer": "^3.13.5" }, "require-dev": { @@ -2572,7 +2572,7 @@ "type": "custom" } ], - "time": "2026-07-16T13:05:29+00:00" + "time": "2026-07-27T11:53:23+00:00" }, { "name": "yoast/phpunit-polyfills", From b4579678add9dd46aff129d683e29a20b817295e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:01:29 +0300 Subject: [PATCH 03/37] chore(deps-dev): bump php-stubs/wordpress-stubs from 6.9.4 to 7.0.1 (#535) Bumps [php-stubs/wordpress-stubs](https://github.com/php-stubs/wordpress-stubs) from 6.9.4 to 7.0.1. - [Release notes](https://github.com/php-stubs/wordpress-stubs/releases) - [Commits](https://github.com/php-stubs/wordpress-stubs/compare/v6.9.4...v7.0.1) --- updated-dependencies: - dependency-name: php-stubs/wordpress-stubs dependency-version: 7.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index b7147d7..10184df 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "phpunit/phpunit": "^9.6", "yoast/phpunit-polyfills": "^4.0", "phpstan/phpstan": "^2.1", - "php-stubs/wordpress-stubs": "^6.8" + "php-stubs/wordpress-stubs": "^7.0" }, "autoload": { "files": [ diff --git a/composer.lock b/composer.lock index 9db1db8..5f059e1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3465780a8769d575341db1d3e93ca51a", + "content-hash": "a6b2d163a24983cd5db977355b2987cd", "packages": [ { "name": "codeinwp/themeisle-sdk", @@ -452,16 +452,16 @@ }, { "name": "php-stubs/wordpress-stubs", - "version": "v6.9.4", + "version": "v7.0.1", "source": { "type": "git", "url": "https://github.com/php-stubs/wordpress-stubs.git", - "reference": "90a9412826b9944f93b10bf41d795b5fe68abcd5" + "reference": "4d0cc9d5c4efd2a46f8c53a40c38f1679a4194b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/90a9412826b9944f93b10bf41d795b5fe68abcd5", - "reference": "90a9412826b9944f93b10bf41d795b5fe68abcd5", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/4d0cc9d5c4efd2a46f8c53a40c38f1679a4194b6", + "reference": "4d0cc9d5c4efd2a46f8c53a40c38f1679a4194b6", "shasum": "" }, "conflict": { @@ -471,7 +471,7 @@ "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "nikic/php-parser": "^5.5", "php": "^7.4 || ^8.0", - "php-stubs/generator": "^0.8.6", + "php-stubs/generator": "^0.9", "phpdocumentor/reflection-docblock": "^6.0", "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^9.5", @@ -498,9 +498,9 @@ ], "support": { "issues": "https://github.com/php-stubs/wordpress-stubs/issues", - "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.9.4" + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v7.0.1" }, - "time": "2026-05-01T20:36:01+00:00" + "time": "2026-07-10T10:10:02+00:00" }, { "name": "phpcompatibility/php-compatibility", From 86eb8274da2b9aa943f147f11d1ea5f9c38fd5ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:01:13 +0300 Subject: [PATCH 04/37] chore(deps): bump codeinwp/themeisle-sdk from 3.3.58 to 3.3.59 (#548) Bumps [codeinwp/themeisle-sdk](https://github.com/Codeinwp/themeisle-sdk) from 3.3.58 to 3.3.59. - [Release notes](https://github.com/Codeinwp/themeisle-sdk/releases) - [Changelog](https://github.com/Codeinwp/themeisle-sdk/blob/v3.3.59/CHANGELOG.md) - [Commits](https://github.com/Codeinwp/themeisle-sdk/compare/v3.3.58...v3.3.59) --- updated-dependencies: - dependency-name: codeinwp/themeisle-sdk dependency-version: 3.3.59 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 5f059e1..f8af31f 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.58", + "version": "3.3.59", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "d6807c0b7308e323bd77cced667dee3f2d5e6a82" + "reference": "7dfbff810d11edb88729090a836eb6a10d669fec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/d6807c0b7308e323bd77cced667dee3f2d5e6a82", - "reference": "d6807c0b7308e323bd77cced667dee3f2d5e6a82", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/7dfbff810d11edb88729090a836eb6a10d669fec", + "reference": "7dfbff810d11edb88729090a836eb6a10d669fec", "shasum": "" }, "require-dev": { @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.58" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.59" }, - "time": "2026-07-29T08:38:52+00:00" + "time": "2026-08-21T09:50:06+00:00" } ], "packages-dev": [ From 444a324f44f93924d1be10f289e154e8097549ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:01:17 +0300 Subject: [PATCH 05/37] chore(deps-dev): bump phpstan/phpstan from 2.2.7 to 2.2.8 (#547) Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.7 to 2.2.8. - [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-version: 2.2.8 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index f8af31f..64294dd 100644 --- a/composer.lock +++ b/composer.lock @@ -892,11 +892,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.2.7", + "version": "2.2.8", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/692db47b9dddb0487934e5236e77d48594aef921", - "reference": "692db47b9dddb0487934e5236e77d48594aef921", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e285254e60f33c21902efef4a926ca0987c06804", + "reference": "e285254e60f33c21902efef4a926ca0987c06804", "shasum": "" }, "require": { @@ -952,7 +952,7 @@ "type": "github" } ], - "time": "2026-07-29T17:39:32+00:00" + "time": "2026-08-04T22:21:45+00:00" }, { "name": "phpunit/php-code-coverage", From e0fa4e0b719a75a2b4f6794dc045494d84c6efb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:03:23 +0300 Subject: [PATCH 06/37] chore(deps): bump codeinwp/themeisle-sdk from 3.3.59 to 3.3.61 (#552) Bumps [codeinwp/themeisle-sdk](https://github.com/Codeinwp/themeisle-sdk) from 3.3.59 to 3.3.61. - [Release notes](https://github.com/Codeinwp/themeisle-sdk/releases) - [Changelog](https://github.com/Codeinwp/themeisle-sdk/blob/v3.3.61/CHANGELOG.md) - [Commits](https://github.com/Codeinwp/themeisle-sdk/compare/v3.3.59...v3.3.61) --- updated-dependencies: - dependency-name: codeinwp/themeisle-sdk dependency-version: 3.3.61 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 64294dd..077d641 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.59", + "version": "3.3.61", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "7dfbff810d11edb88729090a836eb6a10d669fec" + "reference": "9fe698b52dec768a0dd8b500fb51efe40962ee99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/7dfbff810d11edb88729090a836eb6a10d669fec", - "reference": "7dfbff810d11edb88729090a836eb6a10d669fec", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/9fe698b52dec768a0dd8b500fb51efe40962ee99", + "reference": "9fe698b52dec768a0dd8b500fb51efe40962ee99", "shasum": "" }, "require-dev": { @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.59" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.61" }, - "time": "2026-08-21T09:50:06+00:00" + "time": "2026-08-24T15:59:27+00:00" } ], "packages-dev": [ From edc41195b48db62db6b35d24f7d2cd712fd9a765 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:03:28 +0300 Subject: [PATCH 07/37] chore(deps-dev): bump phpstan/phpstan from 2.2.8 to 2.2.9 (#551) Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.8 to 2.2.9. - [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-version: 2.2.9 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 077d641..bce8c3c 100644 --- a/composer.lock +++ b/composer.lock @@ -892,11 +892,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.2.8", + "version": "2.2.9", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e285254e60f33c21902efef4a926ca0987c06804", - "reference": "e285254e60f33c21902efef4a926ca0987c06804", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/13d6b4f347bad222da436580c8304fa6f83e6bd0", + "reference": "13d6b4f347bad222da436580c8304fa6f83e6bd0", "shasum": "" }, "require": { @@ -952,7 +952,7 @@ "type": "github" } ], - "time": "2026-08-04T22:21:45+00:00" + "time": "2026-08-22T07:38:16+00:00" }, { "name": "phpunit/php-code-coverage", From 59f41d32f268d75e9478cd128e5d2396a651ae17 Mon Sep 17 00:00:00 2001 From: Marius Cristea Date: Wed, 2 Sep 2026 13:19:04 +0300 Subject: [PATCH 08/37] ci: skip PR-comment job on Dependabot PRs (no secrets, always fails) Co-Authored-By: Claude Fable 5 --- .github/workflows/build-dev-artifact.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-dev-artifact.yml b/.github/workflows/build-dev-artifact.yml index b80c6d4..f7ee671 100644 --- a/.github/workflows/build-dev-artifact.yml +++ b/.github/workflows/build-dev-artifact.yml @@ -74,7 +74,7 @@ jobs: comment-on-pr: name: Comment on PR with links to plugin ZIPs - if: ${{ github.head_ref && github.head_ref != null }} + if: ${{ github.head_ref && github.head_ref != null && github.actor != 'dependabot[bot]' }} runs-on: ubuntu-latest needs: dev-zip env: From c8fb896713fcfae005c0d081b7b3fb2566acb950 Mon Sep 17 00:00:00 2001 From: Marius Cristea Date: Wed, 2 Sep 2026 13:25:10 +0300 Subject: [PATCH 09/37] ci: revert Dependabot gate on PR-comment job (org Dependabot secret covers this repo) [skip ci] Co-Authored-By: Claude Fable 5 --- .github/workflows/build-dev-artifact.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-dev-artifact.yml b/.github/workflows/build-dev-artifact.yml index f7ee671..b80c6d4 100644 --- a/.github/workflows/build-dev-artifact.yml +++ b/.github/workflows/build-dev-artifact.yml @@ -74,7 +74,7 @@ jobs: comment-on-pr: name: Comment on PR with links to plugin ZIPs - if: ${{ github.head_ref && github.head_ref != null && github.actor != 'dependabot[bot]' }} + if: ${{ github.head_ref && github.head_ref != null }} runs-on: ubuntu-latest needs: dev-zip env: From e59b77cac4265129780b57725e30647e9beda70b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:01:29 +0300 Subject: [PATCH 10/37] chore(deps-dev): bump php-stubs/wordpress-stubs from 7.0.1 to 7.1.0 (#556) Bumps [php-stubs/wordpress-stubs](https://github.com/php-stubs/wordpress-stubs) from 7.0.1 to 7.1.0. - [Release notes](https://github.com/php-stubs/wordpress-stubs/releases) - [Commits](https://github.com/php-stubs/wordpress-stubs/compare/v7.0.1...v7.1.0) --- updated-dependencies: - dependency-name: php-stubs/wordpress-stubs dependency-version: 7.1.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.lock b/composer.lock index bce8c3c..94d1cf8 100644 --- a/composer.lock +++ b/composer.lock @@ -452,16 +452,16 @@ }, { "name": "php-stubs/wordpress-stubs", - "version": "v7.0.1", + "version": "v7.1.0", "source": { "type": "git", "url": "https://github.com/php-stubs/wordpress-stubs.git", - "reference": "4d0cc9d5c4efd2a46f8c53a40c38f1679a4194b6" + "reference": "394570301894799e69a4aa117c9c995427e3ba04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/4d0cc9d5c4efd2a46f8c53a40c38f1679a4194b6", - "reference": "4d0cc9d5c4efd2a46f8c53a40c38f1679a4194b6", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/394570301894799e69a4aa117c9c995427e3ba04", + "reference": "394570301894799e69a4aa117c9c995427e3ba04", "shasum": "" }, "conflict": { @@ -473,11 +473,11 @@ "php": "^7.4 || ^8.0", "php-stubs/generator": "^0.9", "phpdocumentor/reflection-docblock": "^6.0", - "phpstan/phpstan": "^2.1", + "phpstan/phpstan": "^2.2", "phpunit/phpunit": "^9.5", "symfony/polyfill-php80": "*", - "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1", - "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.2", + "wp-coding-standards/wpcs": "3.4.1 as 2.3.0" }, "suggest": { "paragonie/sodium_compat": "Pure PHP implementation of libsodium", @@ -498,9 +498,9 @@ ], "support": { "issues": "https://github.com/php-stubs/wordpress-stubs/issues", - "source": "https://github.com/php-stubs/wordpress-stubs/tree/v7.0.1" + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v7.1.0" }, - "time": "2026-07-10T10:10:02+00:00" + "time": "2026-08-28T00:32:06+00:00" }, { "name": "phpcompatibility/php-compatibility", From f0127c6e0f8342b3893a8cf10d5309cc66b4e2a5 Mon Sep 17 00:00:00 2001 From: Marius Cristea Date: Thu, 10 Sep 2026 20:01:21 +0300 Subject: [PATCH 11/37] fix: purge caches when the wizard enables maintenance mode (#560) * fix: purge page caches after wizard activation * chore: remove added tests from fix * ci: use PHP 8.2 for browser test environments * chore: use the validated source when enabling maintenance mode Co-Authored-By: Claude Fable 5.1 --------- Co-authored-by: Claude Fable 5.1 --- .github/workflows/test-e2e.yml | 3 +++ includes/classes/wp-maintenance-mode-admin.php | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index f0f0bb9..59ca6e5 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -15,6 +15,9 @@ jobs: e2e: name: Playwright runs-on: ubuntu-latest + env: + # PHP 7.4 images depend on the expired Debian Bullseye package index. + WP_ENV_PHP_VERSION: "8.2" if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v6 diff --git a/includes/classes/wp-maintenance-mode-admin.php b/includes/classes/wp-maintenance-mode-admin.php index 3a0dc12..cbca317 100644 --- a/includes/classes/wp-maintenance-mode-admin.php +++ b/includes/classes/wp-maintenance-mode-admin.php @@ -819,13 +819,16 @@ public function insert_template() { CSS_Handler::generate_css_file( $page_id ); } - if ( 'wizard' === $_POST['source'] ) { + if ( 'wizard' === $source ) { $this->plugin_settings['general']['status'] = 1; update_option( 'wpmm_fresh_install', false ); } update_option( 'wpmm_page_category', $category ); update_option( 'wpmm_settings', $this->plugin_settings ); + if ( 'wizard' === $source ) { + wpmm_delete_cache(); + } wp_send_json_success( array( 'pageEditURL' => get_edit_post_link( $page_id ) ) ); } From ed549ccf7cf7889a59c1b93d099a20a00cb393aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2026 15:01:32 +0300 Subject: [PATCH 12/37] chore(deps-dev): bump phpstan/phpstan from 2.2.9 to 2.2.13 (#562) Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.9 to 2.2.13. - [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-version: 2.2.13 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 94d1cf8..a97a499 100644 --- a/composer.lock +++ b/composer.lock @@ -892,11 +892,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.2.9", + "version": "2.2.13", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/13d6b4f347bad222da436580c8304fa6f83e6bd0", - "reference": "13d6b4f347bad222da436580c8304fa6f83e6bd0", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9ba9ac76ee9c5cf5b56d58eb5deec6315b7a0260", + "reference": "9ba9ac76ee9c5cf5b56d58eb5deec6315b7a0260", "shasum": "" }, "require": { @@ -952,7 +952,7 @@ "type": "github" } ], - "time": "2026-08-22T07:38:16+00:00" + "time": "2026-09-03T20:38:19+00:00" }, { "name": "phpunit/php-code-coverage", From 2cd4dc39b97f0a74ebf58a8fbb5408bdd44b22d5 Mon Sep 17 00:00:00 2001 From: Poonam Date: Fri, 18 Sep 2026 00:12:14 +0530 Subject: [PATCH 13/37] Fix maintenance mode changing front page setting --- includes/classes/wp-maintenance-mode.php | 12 +++++++++++- tests/page-state-test.php | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/includes/classes/wp-maintenance-mode.php b/includes/classes/wp-maintenance-mode.php index 4bc43db..440aaf9 100644 --- a/includes/classes/wp-maintenance-mode.php +++ b/includes/classes/wp-maintenance-mode.php @@ -126,7 +126,17 @@ private function __construct() { } } - update_option( 'show_on_front', 'page' ); + // Make the maintenance page the front page for blocked visitors without changing the site's Reading settings. + add_filter( + 'pre_option_show_on_front', + function ( $value ) { + if ( ( ! $this->check_user_role() && ! $this->check_exclude() ) && isset( $this->plugin_settings['design']['page_id'] ) && get_option( 'wpmm_new_look' ) ) { + return 'page'; + } + + return $value; + } + ); add_filter( 'pre_option_page_on_front', function ( $value ) { diff --git a/tests/page-state-test.php b/tests/page-state-test.php index a7adf55..2cc7b28 100644 --- a/tests/page-state-test.php +++ b/tests/page-state-test.php @@ -745,4 +745,23 @@ public function test_plugin_deactivation_restores_selected_page() { $this->assertSame( 'private', get_post_status( $page_id ) ); } + + public function test_maintenance_front_page_does_not_change_the_reading_setting() { + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + + update_option( 'show_on_front', 'posts' ); + wp_set_current_user( 0 ); + $this->boot_plugin( $this->make_settings( 1, $page_id ) ); + + $this->assertSame( 'page', get_option( 'show_on_front' ) ); + + $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $admin_id ); + $this->assertSame( 'posts', get_option( 'show_on_front' ) ); + } } From 1d0856e61d6af28762be6a70e794588ad5323463 Mon Sep 17 00:00:00 2001 From: Poonam Date: Fri, 18 Sep 2026 13:36:14 +0530 Subject: [PATCH 14/37] Restore front page setting after maintenance mode --- includes/classes/wp-maintenance-mode.php | 17 ++++++---------- includes/functions/helpers.php | 26 ++++++++++++++++++++++++ tests/page-state-test.php | 23 +++++++++++++++------ uninstall.php | 1 + 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/includes/classes/wp-maintenance-mode.php b/includes/classes/wp-maintenance-mode.php index 440aaf9..6d49396 100644 --- a/includes/classes/wp-maintenance-mode.php +++ b/includes/classes/wp-maintenance-mode.php @@ -126,17 +126,8 @@ private function __construct() { } } - // Make the maintenance page the front page for blocked visitors without changing the site's Reading settings. - add_filter( - 'pre_option_show_on_front', - function ( $value ) { - if ( ( ! $this->check_user_role() && ! $this->check_exclude() ) && isset( $this->plugin_settings['design']['page_id'] ) && get_option( 'wpmm_new_look' ) ) { - return 'page'; - } - - return $value; - } - ); + wpmm_record_show_on_front(); + update_option( 'show_on_front', 'page' ); add_filter( 'pre_option_page_on_front', function ( $value ) { @@ -172,6 +163,8 @@ function ( $value ) { add_action( 'wpmm_before_scripts', array( $this, 'add_bot_extras' ) ); add_action( 'wpmm_footer', array( $this, 'add_js_files' ) ); } else { + wpmm_restore_show_on_front(); + // restore the maintenance page to its original state when maintenance mode is disabled add_action( 'init', @@ -694,6 +687,8 @@ public static function single_activate( $network_wide = false ) { * @since 2.0.0 */ public static function single_deactivate() { + wpmm_restore_show_on_front(); + // give the selected page back to the site in its original state $settings = get_option( 'wpmm_settings' ); if ( ! empty( $settings['design']['page_id'] ) ) { diff --git a/includes/functions/helpers.php b/includes/functions/helpers.php index c174b05..2355003 100644 --- a/includes/functions/helpers.php +++ b/includes/functions/helpers.php @@ -598,6 +598,32 @@ function wpmm_restore_page_state( $page_id ) { delete_post_meta( $page_id, '_wpmm_applied_status' ); } +/** + * Record the site's front-page setting before maintenance mode changes it. + * The first recorded value is kept until maintenance mode is disabled. + * + * @return void + */ +function wpmm_record_show_on_front() { + add_option( 'wpmm_original_show_on_front', get_option( 'show_on_front', 'posts' ) ); +} + +/** + * Restore the front-page setting recorded when maintenance mode was enabled. + * + * @return void + */ +function wpmm_restore_show_on_front() { + $original_value = get_option( 'wpmm_original_show_on_front', false ); + + if ( false === $original_value ) { + return; + } + + update_option( 'show_on_front', $original_value ); + delete_option( 'wpmm_original_show_on_front' ); +} + /** * Get option page URL. */ diff --git a/tests/page-state-test.php b/tests/page-state-test.php index 2cc7b28..02e0abd 100644 --- a/tests/page-state-test.php +++ b/tests/page-state-test.php @@ -736,17 +736,23 @@ public function test_plugin_deactivation_restores_selected_page() { ) ); + update_option( 'show_on_front', 'posts' ); + // Maintenance mode is active: the page is taken over and published. $this->boot_plugin( $this->make_settings( 1, $page_id ) ); $this->assertSame( 'publish', get_post_status( $page_id ) ); + $this->assertSame( 'page', get_option( 'show_on_front' ) ); + $this->assertSame( 'posts', get_option( 'wpmm_original_show_on_front' ) ); - // Deactivating the plugin hands the page back, private again. + // Deactivating the plugin hands both the page and Reading setting back. WP_Maintenance_Mode::single_deactivate(); $this->assertSame( 'private', get_post_status( $page_id ) ); + $this->assertSame( 'posts', get_option( 'show_on_front' ) ); + $this->assertFalse( get_option( 'wpmm_original_show_on_front', false ) ); } - public function test_maintenance_front_page_does_not_change_the_reading_setting() { + public function test_disabling_maintenance_mode_restores_the_initial_reading_setting() { $page_id = self::factory()->post->create( array( 'post_type' => 'page', @@ -755,13 +761,18 @@ public function test_maintenance_front_page_does_not_change_the_reading_setting( ); update_option( 'show_on_front', 'posts' ); - wp_set_current_user( 0 ); $this->boot_plugin( $this->make_settings( 1, $page_id ) ); - $this->assertSame( 'page', get_option( 'show_on_front' ) ); + $this->assertSame( 'posts', get_option( 'wpmm_original_show_on_front' ) ); + + // Another active request must not overwrite the recorded initial value. + $this->boot_plugin( $this->make_settings( 1, $page_id ) ); + $this->assertSame( 'posts', get_option( 'wpmm_original_show_on_front' ) ); + + $this->boot_plugin( $this->make_settings( 0, $page_id ) ); + do_action( 'init' ); - $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); - wp_set_current_user( $admin_id ); $this->assertSame( 'posts', get_option( 'show_on_front' ) ); + $this->assertFalse( get_option( 'wpmm_original_show_on_front', false ) ); } } diff --git a/uninstall.php b/uninstall.php index 5729061..5223337 100755 --- a/uninstall.php +++ b/uninstall.php @@ -17,6 +17,7 @@ function single_uninstall() { 'wpmm_settings', 'wpmm_notice', 'wpmm_version', + 'wpmm_original_show_on_front', ); foreach ( $options_to_delete as $option ) { From 69c8f93106605e2625ea2b109c5846bd34b1d668 Mon Sep 17 00:00:00 2001 From: selul Date: Fri, 18 Sep 2026 12:26:06 +0300 Subject: [PATCH 15/37] feat: register abilities with the Abilities API Co-Authored-By: Claude Fable 5.1 --- .../classes/wp-maintenance-mode-abilities.php | 550 ++++++++++++++++++ .../classes/wp-maintenance-mode-admin.php | 18 +- includes/functions/helpers.php | 29 + wp-maintenance-mode.php | 6 + 4 files changed, 586 insertions(+), 17 deletions(-) create mode 100644 includes/classes/wp-maintenance-mode-abilities.php diff --git a/includes/classes/wp-maintenance-mode-abilities.php b/includes/classes/wp-maintenance-mode-abilities.php new file mode 100644 index 0000000..9993c10 --- /dev/null +++ b/includes/classes/wp-maintenance-mode-abilities.php @@ -0,0 +1,550 @@ + __( 'LightStart', 'wp-maintenance-mode' ), + 'description' => __( 'Maintenance mode, coming soon and landing page abilities.', 'wp-maintenance-mode' ), + ) + ); + } + + /** + * Register the abilities. + * + * @return void + */ + public static function register_abilities() { + wp_register_ability( + 'lightstart/get-mode', + array( + 'label' => __( 'Get maintenance mode', 'wp-maintenance-mode' ), + 'description' => __( 'Returns whether maintenance mode is enabled, the selected maintenance page and the bypass / exclusion rules.', 'wp-maintenance-mode' ), + 'category' => self::CATEGORY, + 'input_schema' => array( + 'type' => 'object', + 'default' => array(), + 'additionalProperties' => false, + ), + 'output_schema' => self::get_mode_schema(), + 'execute_callback' => array( __CLASS__, 'get_mode' ), + 'permission_callback' => array( __CLASS__, 'can_manage_settings' ), + 'meta' => array( + 'annotations' => array( + 'readonly' => true, + 'destructive' => false, + 'idempotent' => true, + ), + 'show_in_rest' => true, + ), + ) + ); + + wp_register_ability( + 'lightstart/set-mode', + array( + 'label' => __( 'Set maintenance mode', 'wp-maintenance-mode' ), + 'description' => __( 'Enables or disables maintenance mode and optionally updates the selected (existing) maintenance page and the bypass / exclusion rules. Only the provided fields are changed.', 'wp-maintenance-mode' ), + 'category' => self::CATEGORY, + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'enabled' => array( + 'type' => 'boolean', + 'description' => __( 'Turn maintenance mode on or off.', 'wp-maintenance-mode' ), + ), + 'page_id' => array( + 'type' => 'integer', + 'minimum' => 0, + 'description' => __( 'ID of an existing published or private page to use as the maintenance page. Use 0 to clear the selection.', 'wp-maintenance-mode' ), + ), + 'frontend_roles' => array( + 'type' => 'array', + 'items' => array( 'type' => 'string' ), + 'description' => __( 'Role slugs that bypass maintenance mode on the frontend. Administrators always bypass it.', 'wp-maintenance-mode' ), + ), + 'backend_roles' => array( + 'type' => 'array', + 'items' => array( 'type' => 'string' ), + 'description' => __( 'Role slugs that keep access to the dashboard while maintenance mode is on.', 'wp-maintenance-mode' ), + ), + 'exclude' => array( + 'type' => 'array', + 'items' => array( 'type' => 'string' ), + 'description' => __( 'Slugs, URL fragments or IP addresses excluded from maintenance mode. Replaces the current list.', 'wp-maintenance-mode' ), + ), + 'bypass_bots' => array( + 'type' => 'boolean', + 'description' => __( 'Let search engine bots bypass maintenance mode.', 'wp-maintenance-mode' ), + ), + 'dry_run' => array( + 'type' => 'boolean', + 'default' => false, + 'description' => __( 'Validate the input and return the resulting state without saving anything.', 'wp-maintenance-mode' ), + ), + ), + 'additionalProperties' => false, + ), + 'output_schema' => self::get_mode_schema( true ), + 'execute_callback' => array( __CLASS__, 'set_mode' ), + 'permission_callback' => array( __CLASS__, 'can_manage_settings' ), + 'meta' => array( + 'annotations' => array( + 'readonly' => false, + 'destructive' => true, + 'idempotent' => true, + ), + 'show_in_rest' => true, + ), + ) + ); + + wp_register_ability( + 'lightstart/list-subscribers', + array( + 'label' => __( 'List subscribers', 'wp-maintenance-mode' ), + 'description' => __( 'Returns the email addresses captured by the maintenance page subscribe form, newest first, with pagination.', 'wp-maintenance-mode' ), + 'category' => self::CATEGORY, + 'input_schema' => array( + 'type' => 'object', + 'default' => array(), + 'properties' => array( + 'page' => array( + 'type' => 'integer', + 'minimum' => 1, + 'default' => 1, + ), + 'per_page' => array( + 'type' => 'integer', + 'minimum' => 1, + 'maximum' => self::MAX_PER_PAGE, + 'default' => 20, + ), + ), + 'additionalProperties' => false, + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'subscribers' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( 'type' => 'integer' ), + 'email' => array( 'type' => 'string' ), + 'insert_date' => array( 'type' => 'string' ), + ), + ), + ), + 'total' => array( 'type' => 'integer' ), + 'total_pages' => array( 'type' => 'integer' ), + 'page' => array( 'type' => 'integer' ), + 'per_page' => array( 'type' => 'integer' ), + ), + ), + 'execute_callback' => array( __CLASS__, 'list_subscribers' ), + 'permission_callback' => array( __CLASS__, 'can_manage_subscribers' ), + 'meta' => array( + 'annotations' => array( + 'readonly' => true, + 'destructive' => false, + 'idempotent' => true, + ), + 'show_in_rest' => true, + ), + ) + ); + } + + /** + * Permission check, same capability as the settings screen. + * + * @return bool + */ + public static function can_manage_settings() { + return current_user_can( wpmm_get_capability( 'settings' ) ); + } + + /** + * Permission check, same capability as the subscribers export. + * + * @return bool + */ + public static function can_manage_subscribers() { + return current_user_can( wpmm_get_capability( 'subscribers' ) ); + } + + /** + * Execute: lightstart/get-mode + * + * @return array + */ + public static function get_mode() { + return self::format_mode( self::get_settings() ); + } + + /** + * Execute: lightstart/set-mode + * + * @param mixed $input Ability input. + * @return array|WP_Error + */ + public static function set_mode( $input = array() ) { + $input = is_array( $input ) ? $input : array(); + $dry_run = isset( $input['dry_run'] ) && wp_validate_boolean( $input['dry_run'] ); + $settings = self::get_settings(); + $general = isset( $settings['general'] ) && is_array( $settings['general'] ) ? $settings['general'] : array(); + $changed = array(); + + if ( ! array_intersect( array( 'enabled', 'page_id', 'frontend_roles', 'backend_roles', 'exclude', 'bypass_bots' ), array_keys( $input ) ) ) { + return new WP_Error( 'lightstart_nothing_to_update', __( 'Provide at least one setting to change.', 'wp-maintenance-mode' ), array( 'status' => 400 ) ); + } + + $old_status = ! empty( $general['status'] ) ? 1 : 0; + $new_status = $old_status; + + if ( isset( $input['enabled'] ) ) { + if ( ! empty( $general['network_mode'] ) ) { + return new WP_Error( 'lightstart_network_mode', __( 'Maintenance mode is controlled from the network settings and cannot be changed for this site.', 'wp-maintenance-mode' ), array( 'status' => 409 ) ); + } + + $new_status = wp_validate_boolean( $input['enabled'] ) ? 1 : 0; + if ( $new_status !== $old_status ) { + $changed[] = 'enabled'; + } + } + + foreach ( + array( + 'frontend_roles' => 'frontend_role', + 'backend_roles' => 'backend_role', + ) as $field => $setting + ) { + if ( ! isset( $input[ $field ] ) ) { + continue; + } + + $roles = self::sanitize_roles( $input[ $field ] ); + if ( is_wp_error( $roles ) ) { + return $roles; + } + + $general[ $setting ] = $roles; + $changed[] = $field; + } + + if ( isset( $input['exclude'] ) ) { + if ( ! is_array( $input['exclude'] ) || count( $input['exclude'] ) > self::MAX_EXCLUDE_ITEMS ) { + /* translators: maximum number of items */ + return new WP_Error( 'lightstart_invalid_exclude', sprintf( __( 'The exclude list must be an array of at most %d items.', 'wp-maintenance-mode' ), self::MAX_EXCLUDE_ITEMS ), array( 'status' => 400 ) ); + } + + $exclude = array(); + foreach ( $input['exclude'] as $item ) { + if ( ! is_scalar( $item ) ) { + continue; + } + $item = sanitize_textarea_field( trim( (string) $item ) ); + if ( '' !== $item ) { + $exclude[] = $item; + } + } + + $general['exclude'] = $exclude; + $changed[] = 'exclude'; + } + + if ( isset( $input['bypass_bots'] ) ) { + $general['bypass_bots'] = wp_validate_boolean( $input['bypass_bots'] ) ? 1 : 0; + $changed[] = 'bypass_bots'; + } + + $previous_page_id = isset( $settings['design']['page_id'] ) ? absint( $settings['design']['page_id'] ) : 0; + $page_id = $previous_page_id; + + if ( isset( $input['page_id'] ) ) { + $page_id = absint( $input['page_id'] ); + + $valid = self::validate_page( $page_id ); + if ( is_wp_error( $valid ) ) { + return $valid; + } + + if ( $page_id !== $previous_page_id ) { + $changed[] = 'page_id'; + } + } + + $general['status'] = $new_status; + if ( 1 === $new_status && isset( $input['enabled'] ) ) { + $general['status_date'] = date( 'Y-m-d H:i:s' ); + } + + $settings['general'] = $general; + if ( isset( $input['page_id'] ) ) { + if ( ! isset( $settings['design'] ) || ! is_array( $settings['design'] ) ) { + $settings['design'] = array(); + } + $settings['design']['page_id'] = $page_id; + } + + if ( ! $dry_run ) { + if ( isset( $input['page_id'] ) ) { + wpmm_switch_selected_page( $previous_page_id, $page_id ); + } + + // network_mode is a runtime overlay of the network option, it is never stored per site. + $to_save = $settings; + if ( is_multisite() ) { + unset( $to_save['general']['network_mode'] ); + + if ( ! empty( $general['network_mode'] ) ) { + // keep the site's own status, the active one comes from the network. + $stored = wpmm_get_option( 'wpmm_settings', array() ); + $to_save['general']['status'] = ! empty( $stored['general']['status'] ) ? 1 : 0; + } + } + update_option( 'wpmm_settings', $to_save ); + + // same rule as the settings screen: purge when it is or becomes active, or gets deactivated. + if ( 1 === $old_status || 1 === $new_status ) { + wpmm_delete_cache(); + } + } + + $result = self::format_mode( $settings ); + $result['changed'] = $changed; + $result['dry_run'] = $dry_run; + + return $result; + } + + /** + * Execute: lightstart/list-subscribers + * + * @param mixed $input Ability input. + * @return array + */ + public static function list_subscribers( $input = array() ) { + global $wpdb; + + $input = is_array( $input ) ? $input : array(); + $page = isset( $input['page'] ) ? max( 1, absint( $input['page'] ) ) : 1; + $per_page = isset( $input['per_page'] ) ? min( self::MAX_PER_PAGE, max( 1, absint( $input['per_page'] ) ) ) : 20; + $total = wpmm_get_subscribers_count(); + $rows = array(); + + if ( $total > 0 ) { + $rows = $wpdb->get_results( + $wpdb->prepare( + "SELECT id_subscriber, email, insert_date FROM {$wpdb->prefix}wpmm_subscribers ORDER BY id_subscriber DESC LIMIT %d OFFSET %d", + $per_page, + ( $page - 1 ) * $per_page + ), + ARRAY_A + ); + } + + $subscribers = array(); + foreach ( (array) $rows as $row ) { + $subscribers[] = array( + 'id' => (int) $row['id_subscriber'], + 'email' => (string) $row['email'], + 'insert_date' => (string) $row['insert_date'], + ); + } + + return array( + 'subscribers' => $subscribers, + 'total' => $total, + 'total_pages' => (int) ceil( $total / $per_page ), + 'page' => $page, + 'per_page' => $per_page, + ); + } + + /** + * Current settings, read fresh from the database with the network overlay applied. + * + * @return array + */ + private static function get_settings() { + $plugin = WP_Maintenance_Mode::get_instance(); + $settings = wpmm_get_option( 'wpmm_settings', array() ); + $settings = is_array( $settings ) ? $settings : array(); + + if ( ! isset( $settings['general'] ) || ! is_array( $settings['general'] ) ) { + $defaults = $plugin->default_settings(); + $settings['general'] = $defaults['general']; + } + + if ( is_multisite() ) { + $network = $plugin->get_plugin_network_settings(); + + $settings['general']['network_mode'] = ! empty( $network['general']['network_mode'] ) ? 1 : 0; + if ( $settings['general']['network_mode'] ) { + $settings['general']['status'] = ! empty( $network['general']['status'] ) ? 1 : 0; + } + } + + return $settings; + } + + /** + * Shape the settings for output. + * + * @param array $settings Plugin settings. + * @return array + */ + private static function format_mode( $settings ) { + $general = isset( $settings['general'] ) && is_array( $settings['general'] ) ? $settings['general'] : array(); + $page_id = isset( $settings['design']['page_id'] ) ? absint( $settings['design']['page_id'] ) : 0; + $status = $page_id ? get_post_status( $page_id ) : false; + + return array( + 'enabled' => ! empty( $general['status'] ), + 'status_date' => isset( $general['status_date'] ) ? (string) $general['status_date'] : '', + 'network_mode' => ! empty( $general['network_mode'] ), + 'page_id' => $page_id, + 'page_title' => $status ? get_the_title( $page_id ) : '', + 'page_status' => $status ? (string) $status : '', + 'frontend_roles' => isset( $general['frontend_role'] ) ? array_values( array_map( 'strval', (array) $general['frontend_role'] ) ) : array(), + 'backend_roles' => isset( $general['backend_role'] ) ? array_values( array_map( 'strval', (array) $general['backend_role'] ) ) : array(), + 'exclude' => isset( $general['exclude'] ) ? array_values( array_map( 'strval', (array) $general['exclude'] ) ) : array(), + 'bypass_bots' => ! empty( $general['bypass_bots'] ), + 'redirection' => isset( $general['redirection'] ) ? (string) $general['redirection'] : '', + ); + } + + /** + * Output schema shared by get-mode and set-mode. + * + * @param bool $with_changes Add the set-mode specific fields. + * @return array + */ + private static function get_mode_schema( $with_changes = false ) { + $strings = array( + 'type' => 'array', + 'items' => array( 'type' => 'string' ), + ); + + $properties = array( + 'enabled' => array( 'type' => 'boolean' ), + 'status_date' => array( 'type' => 'string' ), + 'network_mode' => array( 'type' => 'boolean' ), + 'page_id' => array( 'type' => 'integer' ), + 'page_title' => array( 'type' => 'string' ), + 'page_status' => array( 'type' => 'string' ), + 'frontend_roles' => $strings, + 'backend_roles' => $strings, + 'exclude' => $strings, + 'bypass_bots' => array( 'type' => 'boolean' ), + 'redirection' => array( 'type' => 'string' ), + ); + + if ( $with_changes ) { + $properties['changed'] = $strings; + $properties['dry_run'] = array( 'type' => 'boolean' ); + } + + return array( + 'type' => 'object', + 'properties' => $properties, + ); + } + + /** + * Validate role slugs against the roles offered on the settings screen. + * + * @param mixed $roles Role slugs. + * @return string[]|WP_Error + */ + private static function sanitize_roles( $roles ) { + if ( ! is_array( $roles ) ) { + return new WP_Error( 'lightstart_invalid_roles', __( 'Roles must be an array of role slugs.', 'wp-maintenance-mode' ), array( 'status' => 400 ) ); + } + + $roles = array_values( array_unique( array_map( 'sanitize_text_field', array_filter( $roles, 'is_scalar' ) ) ) ); + $unknown = array_diff( $roles, array_keys( wpmm_get_user_roles() ) ); + + if ( ! empty( $unknown ) ) { + /* translators: list of role slugs */ + return new WP_Error( 'lightstart_unknown_role', sprintf( __( 'Unknown or not selectable role(s): %s', 'wp-maintenance-mode' ), implode( ', ', $unknown ) ), array( 'status' => 400 ) ); + } + + return $roles; + } + + /** + * Validate a page the same way the settings screen dropdown limits it. + * + * @param int $page_id Page ID; 0 clears the selection. + * @return true|WP_Error + */ + private static function validate_page( $page_id ) { + if ( ! get_option( 'wpmm_new_look' ) ) { + return new WP_Error( 'lightstart_page_selection_unavailable', __( 'Selecting a maintenance page is only available with the block based (new look) maintenance page.', 'wp-maintenance-mode' ), array( 'status' => 409 ) ); + } + + if ( 0 === $page_id ) { + return true; + } + + $page = get_post( $page_id ); + if ( ! $page || 'page' !== $page->post_type || ! in_array( $page->post_status, array( 'publish', 'private' ), true ) ) { + return new WP_Error( 'lightstart_invalid_page', __( 'The page must be an existing published or private page.', 'wp-maintenance-mode' ), array( 'status' => 404 ) ); + } + + if ( ! current_user_can( 'edit_post', $page_id ) ) { + return new WP_Error( 'lightstart_cannot_edit_page', __( 'You are not allowed to edit this page.', 'wp-maintenance-mode' ), array( 'status' => 403 ) ); + } + + return true; + } + } +} diff --git a/includes/classes/wp-maintenance-mode-admin.php b/includes/classes/wp-maintenance-mode-admin.php index cbca317..da6399d 100644 --- a/includes/classes/wp-maintenance-mode-admin.php +++ b/includes/classes/wp-maintenance-mode-admin.php @@ -717,25 +717,9 @@ public function select_page() { private function switch_selected_page( $page_id ) { $previous_page_id = isset( $this->plugin_settings['design']['page_id'] ) ? absint( $this->plugin_settings['design']['page_id'] ) : 0; - if ( $previous_page_id && $previous_page_id !== $page_id ) { - // hand the previously selected page back before abandoning it, - // otherwise nothing would ever restore it - wpmm_restore_page_state( $previous_page_id ); - } + wpmm_switch_selected_page( $previous_page_id, $page_id ); $this->plugin_settings['design']['page_id'] = $page_id; - - if ( $page_id ) { - // remember the page's original state before taking it over as a maintenance page - wpmm_record_page_state( $page_id ); - - wp_update_post( - array( - 'ID' => $page_id, - 'page_template' => 'templates/wpmm-page-template.php', - ) - ); - } } /** diff --git a/includes/functions/helpers.php b/includes/functions/helpers.php index c174b05..b1ce842 100644 --- a/includes/functions/helpers.php +++ b/includes/functions/helpers.php @@ -598,6 +598,35 @@ function wpmm_restore_page_state( $page_id ) { delete_post_meta( $page_id, '_wpmm_applied_status' ); } +/** + * Switch the selected maintenance page: hand the previous page back and take + * the new one over. The caller is responsible for saving the new selection. + * + * @since 2.6.23 + * @param int $previous_page_id The currently selected page ID; 0 when none. + * @param int $page_id The newly selected page ID; 0 clears the selection. + * @return void + */ +function wpmm_switch_selected_page( $previous_page_id, $page_id ) { + if ( $previous_page_id && $previous_page_id !== $page_id ) { + // hand the previously selected page back before abandoning it, + // otherwise nothing would ever restore it + wpmm_restore_page_state( $previous_page_id ); + } + + if ( $page_id ) { + // remember the page's original state before taking it over as a maintenance page + wpmm_record_page_state( $page_id ); + + wp_update_post( + array( + 'ID' => $page_id, + 'page_template' => 'templates/wpmm-page-template.php', + ) + ); + } +} + /** * Get option page URL. */ diff --git a/wp-maintenance-mode.php b/wp-maintenance-mode.php index a892616..389ebb1 100755 --- a/wp-maintenance-mode.php +++ b/wp-maintenance-mode.php @@ -65,6 +65,12 @@ add_action( 'plugins_loaded', array( 'WP_Maintenance_Mode', 'get_instance' ) ); +/** + * ABILITIES API + */ +require_once WPMM_CLASSES_PATH . 'wp-maintenance-mode-abilities.php'; +WP_Maintenance_Mode_Abilities::init(); + /** * DASHBOARD */ From dfea960e0e88c4fc51da2543a5b66716353404e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 15:02:32 +0300 Subject: [PATCH 16/37] chore(deps): bump codeinwp/themeisle-sdk from 3.3.61 to 3.3.62 (#566) Bumps [codeinwp/themeisle-sdk](https://github.com/Codeinwp/themeisle-sdk) from 3.3.61 to 3.3.62. - [Release notes](https://github.com/Codeinwp/themeisle-sdk/releases) - [Changelog](https://github.com/Codeinwp/themeisle-sdk/blob/v3.3.62/CHANGELOG.md) - [Commits](https://github.com/Codeinwp/themeisle-sdk/compare/v3.3.61...v3.3.62) --- updated-dependencies: - dependency-name: codeinwp/themeisle-sdk dependency-version: 3.3.62 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index a97a499..5a6894d 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.61", + "version": "3.3.62", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "9fe698b52dec768a0dd8b500fb51efe40962ee99" + "reference": "8363c9cab1a233095a76cd48e96fb64ce1b29ef8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/9fe698b52dec768a0dd8b500fb51efe40962ee99", - "reference": "9fe698b52dec768a0dd8b500fb51efe40962ee99", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/8363c9cab1a233095a76cd48e96fb64ce1b29ef8", + "reference": "8363c9cab1a233095a76cd48e96fb64ce1b29ef8", "shasum": "" }, "require-dev": { @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.61" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.62" }, - "time": "2026-08-24T15:59:27+00:00" + "time": "2026-09-17T17:08:21+00:00" } ], "packages-dev": [ From 8073528e85bbea1a2bde59908c14844dea9c4b5e Mon Sep 17 00:00:00 2001 From: selul Date: Fri, 18 Sep 2026 17:15:48 +0300 Subject: [PATCH 17/37] feat: opt in to the AI Connect module Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_018GJfggt4S4RuAfF24E93EK --- wp-maintenance-mode.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/wp-maintenance-mode.php b/wp-maintenance-mode.php index 389ebb1..c5e970c 100755 --- a/wp-maintenance-mode.php +++ b/wp-maintenance-mode.php @@ -91,6 +91,30 @@ function () { } ); +add_filter( + 'wp_maintenance_mode_ai_connect_metadata', + function () { + return array( + 'name' => 'LightStart', + 'notice_cases' => array( + __( 'turn maintenance mode on or off', 'wp-maintenance-mode' ), + __( 'change who can bypass it', 'wp-maintenance-mode' ), + __( 'list your subscribers', 'wp-maintenance-mode' ), + ), + 'prompts' => array( + __( 'Is maintenance mode on right now, and which roles and URLs can bypass it?', 'wp-maintenance-mode' ), + __( 'Turn on LightStart maintenance mode and let editors keep access to the site while it is on.', 'wp-maintenance-mode' ), + __( 'List the email addresses collected by my maintenance page subscribe form, newest first.', 'wp-maintenance-mode' ), + ), + 'abilities' => array( + 'lightstart/get-mode', + 'lightstart/set-mode', + 'lightstart/list-subscribers', + ), + ); + } +); + /** * Filter products array. * From 7f82a14e65a9737cb13a5a15fc8dd75829289940 Mon Sep 17 00:00:00 2001 From: selul Date: Fri, 18 Sep 2026 21:26:47 +0300 Subject: [PATCH 18/37] chore: make the AI Connect prompts task-driven Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_018GJfggt4S4RuAfF24E93EK --- wp-maintenance-mode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp-maintenance-mode.php b/wp-maintenance-mode.php index c5e970c..745ad39 100755 --- a/wp-maintenance-mode.php +++ b/wp-maintenance-mode.php @@ -102,9 +102,9 @@ function () { __( 'list your subscribers', 'wp-maintenance-mode' ), ), 'prompts' => array( - __( 'Is maintenance mode on right now, and which roles and URLs can bypass it?', 'wp-maintenance-mode' ), __( 'Turn on LightStart maintenance mode and let editors keep access to the site while it is on.', 'wp-maintenance-mode' ), __( 'List the email addresses collected by my maintenance page subscribe form, newest first.', 'wp-maintenance-mode' ), + __( 'Is maintenance mode on right now, and which roles and URLs can bypass it?', 'wp-maintenance-mode' ), ), 'abilities' => array( 'lightstart/get-mode', From 1e42b3129a6c8bde5c4e3f138111c2c02edc60df Mon Sep 17 00:00:00 2001 From: selul Date: Mon, 21 Sep 2026 10:34:14 +0300 Subject: [PATCH 19/37] chore: let AI Connect collect the abilities by prefix Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_018GJfggt4S4RuAfF24E93EK --- wp-maintenance-mode.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/wp-maintenance-mode.php b/wp-maintenance-mode.php index 745ad39..97443a3 100755 --- a/wp-maintenance-mode.php +++ b/wp-maintenance-mode.php @@ -95,22 +95,18 @@ function () { 'wp_maintenance_mode_ai_connect_metadata', function () { return array( - 'name' => 'LightStart', - 'notice_cases' => array( + 'name' => 'LightStart', + 'notice_cases' => array( __( 'turn maintenance mode on or off', 'wp-maintenance-mode' ), __( 'change who can bypass it', 'wp-maintenance-mode' ), __( 'list your subscribers', 'wp-maintenance-mode' ), ), - 'prompts' => array( + 'prompts' => array( __( 'Turn on LightStart maintenance mode and let editors keep access to the site while it is on.', 'wp-maintenance-mode' ), __( 'List the email addresses collected by my maintenance page subscribe form, newest first.', 'wp-maintenance-mode' ), __( 'Is maintenance mode on right now, and which roles and URLs can bypass it?', 'wp-maintenance-mode' ), ), - 'abilities' => array( - 'lightstart/get-mode', - 'lightstart/set-mode', - 'lightstart/list-subscribers', - ), + 'ability_prefix' => 'lightstart', ); } ); From daba13b9616e86ba9c08148ee6c97311e9c02b47 Mon Sep 17 00:00:00 2001 From: selul Date: Mon, 21 Sep 2026 13:11:51 +0300 Subject: [PATCH 20/37] chore: make the AI Connect prompts value-driven Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_018GJfggt4S4RuAfF24E93EK --- wp-maintenance-mode.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wp-maintenance-mode.php b/wp-maintenance-mode.php index 97443a3..bb8338c 100755 --- a/wp-maintenance-mode.php +++ b/wp-maintenance-mode.php @@ -102,9 +102,9 @@ function () { __( 'list your subscribers', 'wp-maintenance-mode' ), ), 'prompts' => array( - __( 'Turn on LightStart maintenance mode and let editors keep access to the site while it is on.', 'wp-maintenance-mode' ), - __( 'List the email addresses collected by my maintenance page subscribe form, newest first.', 'wp-maintenance-mode' ), - __( 'Is maintenance mode on right now, and which roles and URLs can bypass it?', 'wp-maintenance-mode' ), + __( 'Turn on LightStart maintenance mode and let editors keep access.', 'wp-maintenance-mode' ), + __( 'Add my sales page to the maintenance-mode bypass list so the campaign stays live.', 'wp-maintenance-mode' ), + __( 'Give me the emails collected by my maintenance page since Monday.', 'wp-maintenance-mode' ), ), 'ability_prefix' => 'lightstart', ); From 0f60b1fdd3f176f44ad7210d49e479fd104b92a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:01:40 +0300 Subject: [PATCH 21/37] chore(deps-dev): bump phpstan/phpstan from 2.2.13 to 2.2.14 (#568) Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.13 to 2.2.14. - [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-version: 2.2.14 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 5a6894d..752aca4 100644 --- a/composer.lock +++ b/composer.lock @@ -892,11 +892,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.2.13", + "version": "2.2.14", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9ba9ac76ee9c5cf5b56d58eb5deec6315b7a0260", - "reference": "9ba9ac76ee9c5cf5b56d58eb5deec6315b7a0260", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9c672e7a8e791dfc3d30e55f683e73fc0b63a3ac", + "reference": "9c672e7a8e791dfc3d30e55f683e73fc0b63a3ac", "shasum": "" }, "require": { @@ -952,7 +952,7 @@ "type": "github" } ], - "time": "2026-09-03T20:38:19+00:00" + "time": "2026-09-12T21:39:33+00:00" }, { "name": "phpunit/php-code-coverage", From 64fbda9e869d2b293c180ec296fdf94d9923e38a Mon Sep 17 00:00:00 2001 From: Alexia Soare <108459992+Alexia-Soare@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:23:02 +0300 Subject: [PATCH 22/37] fix: restore the Settings link on the Plugins screen The admin constructor registered the plugin_action_links filter before init had set plugin_basename, so the filter name had no plugin file and never ran. Set the basename in the constructor again. Refs #571 Co-Authored-By: Claude Opus 5.5 (1M context) --- .../classes/wp-maintenance-mode-admin.php | 4 +- tests/plugin-action-links-test.php | 46 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 tests/plugin-action-links-test.php diff --git a/includes/classes/wp-maintenance-mode-admin.php b/includes/classes/wp-maintenance-mode-admin.php index cbca317..5845ae6 100644 --- a/includes/classes/wp-maintenance-mode-admin.php +++ b/includes/classes/wp-maintenance-mode-admin.php @@ -23,6 +23,9 @@ class WP_Maintenance_Mode_Admin { * 3, 2, 1... Start! */ private function __construct() { + // The basename needs no translations, so set it here: the action link filters below use it. + $this->plugin_basename = plugin_basename( WPMM_FILE ); + // Init. add_action( 'init', array( $this, 'load_default_settings' ) ); @@ -102,7 +105,6 @@ public function load_default_settings() { $this->plugin_slug = $plugin->get_plugin_slug(); $this->plugin_settings = $plugin->get_plugin_settings(); $this->plugin_network_settings = $plugin->get_plugin_network_settings(); - $this->plugin_basename = plugin_basename( WPMM_PATH . $this->plugin_slug . '.php' ); $this->plugin_default_settings = $plugin->default_settings(); } diff --git a/tests/plugin-action-links-test.php b/tests/plugin-action-links-test.php new file mode 100644 index 0000000..aff1d1d --- /dev/null +++ b/tests/plugin-action-links-test.php @@ -0,0 +1,46 @@ +setAccessible( true ); + $instance->setValue( null, null ); + + $admin = WP_Maintenance_Mode_Admin::get_instance(); + + // init fires after plugins_loaded, before the Plugins screen renders. + $admin->load_default_settings(); + + return $admin; + } + + /** + * The Plugins screen shows a Settings link in the plugin row. + */ + public function test_plugin_row_has_settings_link() { + $this->boot_admin(); + + // WP_Plugins_List_Table filters the row actions by the plugin file. + $actions = apply_filters( 'plugin_action_links_' . plugin_basename( WPMM_FILE ), array() ); + + $this->assertArrayHasKey( 'wpmm_settings', $actions ); + $this->assertStringContainsString( 'admin.php?page=wp-maintenance-mode', $actions['wpmm_settings'] ); + $this->assertStringContainsString( '>Settings', $actions['wpmm_settings'] ); + } +} From 1c1bfa0d21db9d7fbf6ae6f113ee9a4cff15dcd3 Mon Sep 17 00:00:00 2001 From: Alexia Soare <108459992+Alexia-Soare@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:31:16 +0300 Subject: [PATCH 23/37] test: cover the network admin Settings link Runs only in multisite mode (WP_MULTISITE=1) and skips on single site. Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/plugin-action-links-test.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/plugin-action-links-test.php b/tests/plugin-action-links-test.php index aff1d1d..b30fdb5 100644 --- a/tests/plugin-action-links-test.php +++ b/tests/plugin-action-links-test.php @@ -43,4 +43,26 @@ public function test_plugin_row_has_settings_link() { $this->assertStringContainsString( 'admin.php?page=wp-maintenance-mode', $actions['wpmm_settings'] ); $this->assertStringContainsString( '>Settings', $actions['wpmm_settings'] ); } + + /** + * The Network Admin Plugins screen shows a Settings link when the plugin is network-activated. + * + * Runs only in multisite mode: WP_MULTISITE=1. + * + * @group ms-required + */ + public function test_network_plugin_row_has_settings_link() { + $this->skipWithoutMultisite(); + + $plugin_file = plugin_basename( WPMM_FILE ); + update_site_option( 'active_sitewide_plugins', array( $plugin_file => time() ) ); + + $this->boot_admin(); + + $actions = apply_filters( 'network_admin_plugin_action_links_' . $plugin_file, array() ); + + $this->assertArrayHasKey( 'wpmm_settings', $actions ); + $this->assertStringContainsString( 'admin.php?page=wp-maintenance-mode', $actions['wpmm_settings'] ); + $this->assertStringContainsString( '>Settings', $actions['wpmm_settings'] ); + } } From d824909ec71e25448dec64b96fcdbb160678f0aa Mon Sep 17 00:00:00 2001 From: Alexia Soare <108459992+Alexia-Soare@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:48:07 +0300 Subject: [PATCH 24/37] test: share the Settings link assertions Drop the unused return value from boot_admin() and move the repeated assertions into assert_settings_link(). Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/plugin-action-links-test.php | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/tests/plugin-action-links-test.php b/tests/plugin-action-links-test.php index b30fdb5..5e02796 100644 --- a/tests/plugin-action-links-test.php +++ b/tests/plugin-action-links-test.php @@ -11,7 +11,7 @@ class Test_Plugin_Action_Links extends WP_UnitTestCase { /** * Boot a fresh admin instance, the same way plugins_loaded does on admin requests. * - * @return WP_Maintenance_Mode_Admin + * @return void */ private function boot_admin() { if ( ! class_exists( 'WP_Maintenance_Mode_Admin' ) ) { @@ -26,8 +26,18 @@ private function boot_admin() { // init fires after plugins_loaded, before the Plugins screen renders. $admin->load_default_settings(); + } - return $admin; + /** + * Assert that the row actions contain the LightStart Settings link. + * + * @param array $actions Row actions returned by the action links filter. + * @return void + */ + private function assert_settings_link( $actions ) { + $this->assertArrayHasKey( 'wpmm_settings', $actions ); + $this->assertStringContainsString( 'admin.php?page=wp-maintenance-mode', $actions['wpmm_settings'] ); + $this->assertStringContainsString( '>Settings', $actions['wpmm_settings'] ); } /** @@ -39,9 +49,7 @@ public function test_plugin_row_has_settings_link() { // WP_Plugins_List_Table filters the row actions by the plugin file. $actions = apply_filters( 'plugin_action_links_' . plugin_basename( WPMM_FILE ), array() ); - $this->assertArrayHasKey( 'wpmm_settings', $actions ); - $this->assertStringContainsString( 'admin.php?page=wp-maintenance-mode', $actions['wpmm_settings'] ); - $this->assertStringContainsString( '>Settings', $actions['wpmm_settings'] ); + $this->assert_settings_link( $actions ); } /** @@ -61,8 +69,6 @@ public function test_network_plugin_row_has_settings_link() { $actions = apply_filters( 'network_admin_plugin_action_links_' . $plugin_file, array() ); - $this->assertArrayHasKey( 'wpmm_settings', $actions ); - $this->assertStringContainsString( 'admin.php?page=wp-maintenance-mode', $actions['wpmm_settings'] ); - $this->assertStringContainsString( '>Settings', $actions['wpmm_settings'] ); + $this->assert_settings_link( $actions ); } } From a59b51a6970636682dd4cbbcfab396a6905696d5 Mon Sep 17 00:00:00 2001 From: Alexia-Soare <108459992+Alexia-Soare@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:01:46 +0300 Subject: [PATCH 25/37] fix: return 503 on the selected maintenance page (#570) * fix: return 503 on the selected maintenance page The selected-page (new look) branch returned before the status headers, so visitors got a 200. Send the 503, Retry-After and no-cache headers before the branch so both maintenance flows return them. Co-Authored-By: Claude Opus 5.5 (1M context) * fix: skip Retry-After on selected-page redirects Other URLs in the selected-page flow redirect to the front page, so send the 503 status and Retry-After only on the front page there. Keep the no-cache headers on every maintenance response. Test: assert Cache-Control on the selected page and reset show_on_front only after maintenance mode is disabled. Co-Authored-By: Claude Opus 5.5 (1M context) --------- Co-authored-by: Claude Opus 5.5 (1M context) --- includes/classes/wp-maintenance-mode.php | 29 ++++++---- tests/e2e/specs/maintenance-mode.spec.js | 71 ++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/includes/classes/wp-maintenance-mode.php b/includes/classes/wp-maintenance-mode.php index 4bc43db..7f36e4e 100644 --- a/includes/classes/wp-maintenance-mode.php +++ b/includes/classes/wp-maintenance-mode.php @@ -742,12 +742,6 @@ public function init() { ! $this->check_search_bots() && ! ( defined( 'WP_CLI' ) && WP_CLI ) ) { - if ( isset( $this->plugin_settings['design']['page_id'] ) && get_option( 'wpmm_new_look' ) ) { - define( 'IS_MAINTENANCE', true ); - include_once wpmm_get_template_path( 'maintenance.php', true ); - return; - } - // HEADER STUFF $protocol = ! empty( $_SERVER['SERVER_PROTOCOL'] ) && in_array( $_SERVER['SERVER_PROTOCOL'], array( 'HTTP/1.1', 'HTTP/1.0' ), true ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'; $charset = get_bloginfo( 'charset' ) ? get_bloginfo( 'charset' ) : 'UTF-8'; @@ -756,6 +750,24 @@ public function init() { $backtime_seconds = $this->calculate_backtime(); $backtime = (int) apply_filters( 'wpmm_backtime', $backtime_seconds ); + $is_selected_page = isset( $this->plugin_settings['design']['page_id'] ) && get_option( 'wpmm_new_look' ); + + // send the status headers before the selected page branch, so that both maintenance flows return them + wpmm_set_nocache_constants(); + nocache_headers(); + + // the selected page template redirects other URLs to the front page, and a redirect must not tell clients to wait + if ( ! $is_selected_page || is_front_page() ) { + header( "$protocol $status_code Service Unavailable", true, $status_code ); + header( "Retry-After: $backtime" ); + } + + if ( $is_selected_page ) { + define( 'IS_MAINTENANCE', true ); + include_once wpmm_get_template_path( 'maintenance.php', true ); + return; + } + // META STUFF $title = ! empty( $this->plugin_settings['design']['title'] ) ? $this->plugin_settings['design']['title'] : get_bloginfo( 'name' ) . ' - ' . __( 'Maintenance Mode', 'wp-maintenance-mode' ); $title = apply_filters( 'wm_title', $title ); // this hook will be removed in the next versions @@ -794,13 +806,8 @@ public function init() { $countdown_start = ! empty( $this->plugin_settings['modules']['countdown_start'] ) ? $this->plugin_settings['modules']['countdown_start'] : $this->plugin_settings['general']['status_date']; $countdown_end = strtotime( $countdown_start . ' +' . $backtime_seconds . ' seconds' ); - wpmm_set_nocache_constants(); - nocache_headers(); - ob_start(); header( "Content-type: text/html; charset=$charset" ); - header( "$protocol $status_code Service Unavailable", true, $status_code ); - header( "Retry-After: $backtime" ); // load maintenance mode template include_once wpmm_get_template_path( 'maintenance.php', true ); diff --git a/tests/e2e/specs/maintenance-mode.spec.js b/tests/e2e/specs/maintenance-mode.spec.js index 885faaf..d70cb83 100644 --- a/tests/e2e/specs/maintenance-mode.spec.js +++ b/tests/e2e/specs/maintenance-mode.spec.js @@ -3,11 +3,25 @@ */ import { test, expect } from '@wordpress/e2e-test-utils-playwright'; +/** + * External dependencies + */ +import { execSync } from 'child_process'; + /** * Internal dependencies */ import { setMaintenanceMode, openAsVisitor } from '../utils.js'; +/** + * Run a WP-CLI command inside the wp-env cli container. + * + * @param {string} args WP-CLI arguments, without the leading `wp`. + */ +function wpCli( args ) { + execSync( `npx wp-env run cli wp ${ args }`, { stdio: 'pipe' } ); +} + test.describe( 'maintenance mode lifecycle', () => { test( 'the site is public while maintenance mode is off', async ( { admin, @@ -74,6 +88,63 @@ test.describe( 'maintenance mode lifecycle', () => { ).toHaveCount( 0 ); } ); + test( 'visitors get a 503 on the selected maintenance page (new look)', async ( { + admin, + page, + browser, + requestUtils, + } ) => { + const maintenancePage = await requestUtils.rest( { + path: '/wp/v2/pages', + method: 'POST', + data: { + title: 'Selected maintenance page', + status: 'publish', + content: 'Back in a moment.', + }, + } ); + + await setMaintenanceMode( admin, page, true ); + // The block-based flow has no settings UI without the wizard, so switch it on directly. + wpCli( 'option update wpmm_new_look 1' ); + wpCli( + `option patch insert wpmm_settings design page_id ${ maintenancePage.id }` + ); + + try { + const visitor = await openAsVisitor( browser ); + expect( visitor.response.status() ).toBe( 503 ); + expect( visitor.response.headers()[ 'retry-after' ] ).toBeTruthy(); + expect( visitor.response.headers()[ 'cache-control' ] ).toContain( + 'no-cache' + ); + await expect( visitor.page.locator( 'body' ) ).toContainText( + 'Back in a moment.' + ); + + // Other URLs redirect to the maintenance page without delay. + const redirect = await visitor.context.request.get( + '/sample-page/', + { maxRedirects: 0 } + ); + expect( redirect.status() ).toBe( 302 ); + expect( redirect.headers()[ 'retry-after' ] ).toBeUndefined(); + await visitor.context.close(); + } finally { + // Hand the classic flow back for the specs that follow. + wpCli( 'option update wpmm_new_look 0' ); + wpCli( 'option patch delete wpmm_settings design page_id' ); + await setMaintenanceMode( admin, page, false ); + // Active maintenance mode sets it on every request, so reset it only after disabling. + wpCli( 'option update show_on_front posts' ); + await requestUtils.rest( { + path: `/wp/v2/pages/${ maintenancePage.id }`, + method: 'DELETE', + params: { force: true }, + } ); + } + } ); + test( 'disabling maintenance mode makes the site public again', async ( { admin, page, From 2eba1007aa82e71e89d7f5181ab26b3cf4a46328 Mon Sep 17 00:00:00 2001 From: Alexia Soare <108459992+Alexia-Soare@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:27:24 +0300 Subject: [PATCH 26/37] test: type the assert_settings_link() parameter Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/plugin-action-links-test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/plugin-action-links-test.php b/tests/plugin-action-links-test.php index 5e02796..a5fb833 100644 --- a/tests/plugin-action-links-test.php +++ b/tests/plugin-action-links-test.php @@ -31,10 +31,10 @@ private function boot_admin() { /** * Assert that the row actions contain the LightStart Settings link. * - * @param array $actions Row actions returned by the action links filter. + * @param array $actions Row actions returned by the action links filter. * @return void */ - private function assert_settings_link( $actions ) { + private function assert_settings_link( array $actions ) { $this->assertArrayHasKey( 'wpmm_settings', $actions ); $this->assertStringContainsString( 'admin.php?page=wp-maintenance-mode', $actions['wpmm_settings'] ); $this->assertStringContainsString( '>Settings', $actions['wpmm_settings'] ); From cf175f6c3fa7d3f368da8b52e8053ef0906b2ae2 Mon Sep 17 00:00:00 2001 From: Alexia Soare <108459992+Alexia-Soare@users.noreply.github.com> Date: Mon, 28 Sep 2026 15:32:49 +0300 Subject: [PATCH 27/37] test: share the admin boot helper and type the new tests as void Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/bootstrap.php | 20 ++++++++++++++++++ tests/page-state-test.php | 33 +++--------------------------- tests/plugin-action-links-test.php | 28 ++++--------------------- 3 files changed, 27 insertions(+), 54 deletions(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index eedf395..7d223b5 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -34,3 +34,23 @@ function _register_module() { // Start up the WP testing environment. require $_tests_dir . '/includes/bootstrap.php'; + +/** + * Boot a fresh admin instance, the same way plugins_loaded and init do on admin requests. + * + * @return WP_Maintenance_Mode_Admin + */ +function wpmm_test_boot_admin() { + if ( ! class_exists( 'WP_Maintenance_Mode_Admin' ) ) { + require_once WPMM_CLASSES_PATH . 'wp-maintenance-mode-admin.php'; + } + + $instance = new ReflectionProperty( 'WP_Maintenance_Mode_Admin', 'instance' ); + $instance->setAccessible( true ); + $instance->setValue( null, null ); + + $admin = WP_Maintenance_Mode_Admin::get_instance(); + $admin->load_default_settings(); + + return $admin; +} diff --git a/tests/page-state-test.php b/tests/page-state-test.php index a7adf55..146ed61 100644 --- a/tests/page-state-test.php +++ b/tests/page-state-test.php @@ -106,16 +106,7 @@ public function test_user_page_template_is_restored_on_disable_and_reapplied_on_ private function apply_template( $settings, $template_slug = 'coming-soon-1', $category = 'coming-soon' ) { $this->boot_plugin( $settings ); - if ( ! class_exists( 'WP_Maintenance_Mode_Admin' ) ) { - require_once WPMM_CLASSES_PATH . 'wp-maintenance-mode-admin.php'; - } - - $instance = new ReflectionProperty( 'WP_Maintenance_Mode_Admin', 'instance' ); - $instance->setAccessible( true ); - $instance->setValue( null, null ); - - $admin = WP_Maintenance_Mode_Admin::get_instance(); - $admin->load_default_settings(); + $admin = wpmm_test_boot_admin(); $_POST = array( '_wpnonce' => wp_create_nonce( 'tab-design' ), @@ -157,16 +148,7 @@ private function apply_template( $settings, $template_slug = 'coming-soon-1', $c private function select_page( $settings, $page_id ) { $this->boot_plugin( $settings ); - if ( ! class_exists( 'WP_Maintenance_Mode_Admin' ) ) { - require_once WPMM_CLASSES_PATH . 'wp-maintenance-mode-admin.php'; - } - - $instance = new ReflectionProperty( 'WP_Maintenance_Mode_Admin', 'instance' ); - $instance->setAccessible( true ); - $instance->setValue( null, null ); - - $admin = WP_Maintenance_Mode_Admin::get_instance(); - $admin->load_default_settings(); + $admin = wpmm_test_boot_admin(); $_POST = array( '_wpnonce' => wp_create_nonce( 'tab-design' ), @@ -204,16 +186,7 @@ private function select_page( $settings, $page_id ) { private function save_design_settings( $settings, $design ) { $this->boot_plugin( $settings ); - if ( ! class_exists( 'WP_Maintenance_Mode_Admin' ) ) { - require_once WPMM_CLASSES_PATH . 'wp-maintenance-mode-admin.php'; - } - - $instance = new ReflectionProperty( 'WP_Maintenance_Mode_Admin', 'instance' ); - $instance->setAccessible( true ); - $instance->setValue( null, null ); - - $admin = WP_Maintenance_Mode_Admin::get_instance(); - $admin->load_default_settings(); + $admin = wpmm_test_boot_admin(); wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) ); diff --git a/tests/plugin-action-links-test.php b/tests/plugin-action-links-test.php index a5fb833..d137418 100644 --- a/tests/plugin-action-links-test.php +++ b/tests/plugin-action-links-test.php @@ -8,26 +8,6 @@ */ class Test_Plugin_Action_Links extends WP_UnitTestCase { - /** - * Boot a fresh admin instance, the same way plugins_loaded does on admin requests. - * - * @return void - */ - private function boot_admin() { - if ( ! class_exists( 'WP_Maintenance_Mode_Admin' ) ) { - require_once WPMM_CLASSES_PATH . 'wp-maintenance-mode-admin.php'; - } - - $instance = new ReflectionProperty( 'WP_Maintenance_Mode_Admin', 'instance' ); - $instance->setAccessible( true ); - $instance->setValue( null, null ); - - $admin = WP_Maintenance_Mode_Admin::get_instance(); - - // init fires after plugins_loaded, before the Plugins screen renders. - $admin->load_default_settings(); - } - /** * Assert that the row actions contain the LightStart Settings link. * @@ -43,8 +23,8 @@ private function assert_settings_link( array $actions ) { /** * The Plugins screen shows a Settings link in the plugin row. */ - public function test_plugin_row_has_settings_link() { - $this->boot_admin(); + public function test_plugin_row_has_settings_link(): void { + wpmm_test_boot_admin(); // WP_Plugins_List_Table filters the row actions by the plugin file. $actions = apply_filters( 'plugin_action_links_' . plugin_basename( WPMM_FILE ), array() ); @@ -59,13 +39,13 @@ public function test_plugin_row_has_settings_link() { * * @group ms-required */ - public function test_network_plugin_row_has_settings_link() { + public function test_network_plugin_row_has_settings_link(): void { $this->skipWithoutMultisite(); $plugin_file = plugin_basename( WPMM_FILE ); update_site_option( 'active_sitewide_plugins', array( $plugin_file => time() ) ); - $this->boot_admin(); + wpmm_test_boot_admin(); $actions = apply_filters( 'network_admin_plugin_action_links_' . $plugin_file, array() ); From d4852aad33029f447c6cd22e1b70473fff44c886 Mon Sep 17 00:00:00 2001 From: Alexia Soare <108459992+Alexia-Soare@users.noreply.github.com> Date: Mon, 28 Sep 2026 15:58:40 +0300 Subject: [PATCH 28/37] ci: run PHPUnit in multisite mode too Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/test-php.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-php.yml b/.github/workflows/test-php.yml index eb20a58..3fdeeff 100644 --- a/.github/workflows/test-php.yml +++ b/.github/workflows/test-php.yml @@ -103,3 +103,5 @@ jobs: run: composer install --prefer-dist --no-progress --no-suggest - name: Run phpunit run: composer run-script phpunit + - name: Run phpunit (multisite) + run: WP_MULTISITE=1 composer run-script phpunit From 2270d8c42dbd9ef33a9d0d44ac0d7c9ee64c1bef Mon Sep 17 00:00:00 2001 From: selul Date: Tue, 29 Sep 2026 11:59:29 +0300 Subject: [PATCH 29/37] chore: require Themeisle SDK 3.3.64 The first SDK release that carries the AI Connect module this PR opts into. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_018GJfggt4S4RuAfF24E93EK --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 10184df..1249e19 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ } ], "require": { - "codeinwp/themeisle-sdk": "^3.3", + "codeinwp/themeisle-sdk": "^3.3.64", "ext-json": "*", "ext-dom": "*" }, diff --git a/composer.lock b/composer.lock index 752aca4..584acff 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a6b2d163a24983cd5db977355b2987cd", + "content-hash": "f640ab023fdc65e401262d86f85b7835", "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.62", + "version": "3.3.64", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "8363c9cab1a233095a76cd48e96fb64ce1b29ef8" + "reference": "53c6f4faca201298748d6319f25a1de2767b5254" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/8363c9cab1a233095a76cd48e96fb64ce1b29ef8", - "reference": "8363c9cab1a233095a76cd48e96fb64ce1b29ef8", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/53c6f4faca201298748d6319f25a1de2767b5254", + "reference": "53c6f4faca201298748d6319f25a1de2767b5254", "shasum": "" }, "require-dev": { @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.62" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.64" }, - "time": "2026-09-17T17:08:21+00:00" + "time": "2026-09-29T08:42:21+00:00" } ], "packages-dev": [ From e6e16df616bb4899f905e29787426d6bd216ef52 Mon Sep 17 00:00:00 2001 From: selul Date: Tue, 29 Sep 2026 12:40:53 +0300 Subject: [PATCH 30/37] chore: require Themeisle SDK 3.3.65 Picks up the AI Connect fixes since 3.3.64: the Enable button hides once the connector is active, the notice matches core's height, and the "enabled" event for products with their own notification UI. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_018GJfggt4S4RuAfF24E93EK --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 1249e19..4d72650 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ } ], "require": { - "codeinwp/themeisle-sdk": "^3.3.64", + "codeinwp/themeisle-sdk": "^3.3.65", "ext-json": "*", "ext-dom": "*" }, diff --git a/composer.lock b/composer.lock index 584acff..aef5b41 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f640ab023fdc65e401262d86f85b7835", + "content-hash": "4964549f2377b93b8ecbf02c423eb00b", "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.64", + "version": "3.3.65", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "53c6f4faca201298748d6319f25a1de2767b5254" + "reference": "f650fe856d52ce4e5754557d89ba2f3127ad54d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/53c6f4faca201298748d6319f25a1de2767b5254", - "reference": "53c6f4faca201298748d6319f25a1de2767b5254", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/f650fe856d52ce4e5754557d89ba2f3127ad54d8", + "reference": "f650fe856d52ce4e5754557d89ba2f3127ad54d8", "shasum": "" }, "require-dev": { @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.64" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.65" }, - "time": "2026-09-29T08:42:21+00:00" + "time": "2026-09-29T09:35:47+00:00" } ], "packages-dev": [ From 9fb705516192064befba4bb29ad81a4ff1aaf988 Mon Sep 17 00:00:00 2001 From: selul Date: Tue, 29 Sep 2026 13:57:08 +0300 Subject: [PATCH 31/37] AI agent support - Added AI agent support: let AI assistants read and change your LightStart settings. From 6c84a3a980ca4c5b70dd6300fb239c745cecf86e Mon Sep 17 00:00:00 2001 From: Girish Panchal <79647963+girishpanchal30@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:31:50 +0530 Subject: [PATCH 32/37] Fixed block theme styles without dom (#561) * fix: block theme styles without dom * fix: update PHP version in E2E test workflow to 8.2 * fix: ignore style markup in scripts * fix: revert a test case * release: AI agent support - Added AI agent support: let AI assistants read and change your LightStart settings. --------- Co-authored-by: selul Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: pirate-bot <58979018+pirate-bot@users.noreply.github.com> --- .github/workflows/test-e2e.yml | 2 +- .wp-env.json | 2 +- composer.json | 3 +- composer.lock | 5 +- includes/classes/wp-maintenance-mode.php | 44 +++++------ tests/block-theme-styles-test.php | 95 ++++++++++++++++++++++++ 6 files changed, 123 insertions(+), 28 deletions(-) create mode 100644 tests/block-theme-styles-test.php diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 59ca6e5..687ead2 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -28,7 +28,7 @@ jobs: - name: Setup PHP version uses: shivammathur/setup-php@v2 with: - php-version: "7.4" + php-version: "8.2" - name: Install composer run: composer install --prefer-dist --no-progress - name: Install JS deps diff --git a/.wp-env.json b/.wp-env.json index 8e2962e..7de6aa0 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,6 +1,6 @@ { "core": null, - "phpVersion": "7.4", + "phpVersion": "8.2", "testsEnvironment": false, "plugins": [ "." ], "config": { diff --git a/composer.json b/composer.json index 4d72650..ad2e827 100644 --- a/composer.json +++ b/composer.json @@ -18,8 +18,7 @@ ], "require": { "codeinwp/themeisle-sdk": "^3.3.65", - "ext-json": "*", - "ext-dom": "*" + "ext-json": "*" }, "require-dev": { "squizlabs/php_codesniffer": "^3.13", diff --git a/composer.lock b/composer.lock index aef5b41..7e82dd7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4964549f2377b93b8ecbf02c423eb00b", + "content-hash": "a9912abacd1400467048e76dde8dfcd2", "packages": [ { "name": "codeinwp/themeisle-sdk", @@ -2644,8 +2644,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "ext-json": "*", - "ext-dom": "*" + "ext-json": "*" }, "platform-dev": {}, "platform-overrides": { diff --git a/includes/classes/wp-maintenance-mode.php b/includes/classes/wp-maintenance-mode.php index 7f36e4e..a37b415 100644 --- a/includes/classes/wp-maintenance-mode.php +++ b/includes/classes/wp-maintenance-mode.php @@ -25,7 +25,7 @@ class WP_Maintenance_Mode { protected $plugin_basename; protected static $instance = null; - private $style_buffer; + private $style_buffer = array(); private $current_page_category; /** @@ -1147,9 +1147,7 @@ public function remember_style_fse() { echo $output; - $doc = new DOMDocument(); - $doc->loadHTML( '' . $output . '' ); - $this->style_buffer = $doc->getElementsByTagName( 'style' ); + $this->style_buffer = $this->extract_style_tags( $output ); } /** @@ -1165,30 +1163,34 @@ public function add_style_fse() { $output = ob_get_contents(); ob_end_clean(); - $doc = new DOMDocument(); - $doc->loadHTML( '' . $output . '' ); - $elems = $doc->getElementsByTagName( 'style' ); - $css = ''; + $styles = $this->extract_style_tags( $output ); + $missing = array_diff( $styles, (array) $this->style_buffer ); - $common_positions = array(); + echo implode( '', $missing ); + } - foreach ( $elems as $i => $elem ) { - foreach ( $this->style_buffer as $style ) { - if ( $elems->item( $i )->C14N() == $style->C14N() ) { - $common_positions[] = $i; - } - } + /** + * Collect the inline `` tags, as they appear in the markup. + */ + private function extract_style_tags( $html ) { + if ( empty( $html ) ) { + return array(); } - foreach ( $elems as $i => $elem ) { - if ( in_array( $i, $common_positions ) ) { - continue; - } + // Comments and script bodies can hold `#is', $markup, $matches ); + + return $matches[0]; } /** diff --git a/tests/block-theme-styles-test.php b/tests/block-theme-styles-test.php new file mode 100644 index 0000000..0e7bcf0 --- /dev/null +++ b/tests/block-theme-styles-test.php @@ -0,0 +1,95 @@ +setAccessible( true ); + $prop->setValue( self::$frontend, array() ); + + parent::tear_down(); + } + + public function print_head_style() { + echo ''; + } + + public function print_style_lookalikes() { + echo ''; + echo ''; + } + + public function print_late_style() { + echo ''; + } + + public function test_the_head_callback_prints_the_head_markup() { + add_action( 'wp_head', array( $this, 'print_head_style' ) ); + + ob_start(); + self::$frontend->remember_style_fse(); + $head = ob_get_clean(); + + $this->assertStringContainsString( '.wpmm-head{color:red}', $head ); + } + + public function test_the_footer_callback_prints_only_the_styles_missing_from_the_head() { + add_action( 'wp_head', array( $this, 'print_head_style' ) ); + + ob_start(); + self::$frontend->remember_style_fse(); + ob_end_clean(); + + add_action( 'wp_head', array( $this, 'print_late_style' ) ); + + ob_start(); + self::$frontend->add_style_fse(); + $footer = ob_get_clean(); + + $this->assertStringContainsString( '.wpmm-late{color:blue}', $footer ); + $this->assertStringNotContainsString( '.wpmm-head{color:red}', $footer ); + } + + public function test_the_footer_callback_prints_the_styles_when_the_head_callback_never_ran() { + add_action( 'wp_head', array( $this, 'print_late_style' ) ); + + ob_start(); + self::$frontend->add_style_fse(); + $footer = ob_get_clean(); + + $this->assertStringContainsString( '.wpmm-late{color:blue}', $footer ); + } + + public function test_the_footer_callback_ignores_style_markup_inside_scripts_and_comments() { + add_action( 'wp_head', array( $this, 'print_style_lookalikes' ) ); + + ob_start(); + self::$frontend->add_style_fse(); + $footer = ob_get_clean(); + + $this->assertStringNotContainsString( '.wpmm-in-script', $footer ); + $this->assertStringNotContainsString( '.wpmm-in-comment', $footer ); + } +} From c6cc5bbde4df1d34c9a6dc17842f28ef8b7cdd46 Mon Sep 17 00:00:00 2001 From: selul Date: Tue, 29 Sep 2026 15:37:41 +0300 Subject: [PATCH 33/37] feat: offer Easy MCP AI in the setup wizard Replace the Optimole and Super Page Cache recommendations in the setup wizard with a single "Manage your site with AI" option that installs and activates Easy MCP AI. The option is hidden when Easy MCP AI is already active. Co-Authored-By: Claude Opus 5.5 --- assets/js/scripts-admin.js | 35 ++++----------- .../classes/wp-maintenance-mode-admin.php | 23 +++------- views/wizard.php | 44 ++++--------------- 3 files changed, 22 insertions(+), 80 deletions(-) diff --git a/assets/js/scripts-admin.js b/assets/js/scripts-admin.js index b31978a..f158198 100644 --- a/assets/js/scripts-admin.js +++ b/assets/js/scripts-admin.js @@ -523,7 +523,7 @@ jQuery( function( $ ) { } /** - * Installs or activates Otter and Optimole and adds the template after + * Installs or activates the selected recommended plugins and adds the template after * * @param {Object} data * @param {Function} callback @@ -626,33 +626,19 @@ jQuery( function( $ ) { * Install and activate recommended plugins if the checkboxes is checked. */ function handlePlugins() { - const optimoleCheckbox = $( '#wizard-optimole-checkbox' ); - const wpscCheckbox = $( '#wizard-wpsc-checkbox' ); + const easyMcpCheckbox = $( '#wizard-easy-mcp-checkbox' ); const otterBlockCheckbox = $( '#wizard-otter-block-checkbox' ); let promiseChain = Promise.resolve(); - if ( optimoleCheckbox.length && optimoleCheckbox.is( ':checked' ) ) { - promiseChain = promiseChain - .then(() => { - if ( ! wpmmVars.isOptimoleInstalled ) { - return installPlugin( 'optimole-wp' ).then( () => activatePlugin( 'optimole-wp' ) ); - } - - if ( ! wpmmVars.isOptimoleActive ) { - return activatePlugin( 'optimole-wp' ); - } - }); - } - - if ( wpscCheckbox.length && wpscCheckbox.is( ':checked' ) ) { + if ( easyMcpCheckbox.length && easyMcpCheckbox.is( ':checked' ) ) { promiseChain = promiseChain .then( () => { - if ( ! wpmmVars.isWPSCInstalled ) { - return installPlugin( 'wp-cloudflare-page-cache' ).then( () => activatePlugin( 'wp-cloudflare-page-cache' ) ); + if ( ! wpmmVars.isEasyMcpInstalled ) { + return installPlugin( 'easy-mcp-ai' ).then( () => activatePlugin( 'easy-mcp-ai' ) ); } - if ( ! wpmmVars.isWPSCActive ) { - return activatePlugin( wpmmVars.isWPSCProInstalled ? 'wp-super-page-cache-pro' : 'wp-cloudflare-page-cache' ); + if ( ! wpmmVars.isEasyMcpActive ) { + return activatePlugin( 'easy-mcp-ai' ); } } ); } @@ -727,11 +713,8 @@ jQuery( function( $ ) { switch ( slug ) { case 'otter-blocks': return $.get( wpmmVars.otterActivationLink ); - case 'optimole-wp': - return $.get( wpmmVars.optimoleActivationLink ); - case 'wp-super-page-cache-pro': - case 'wp-cloudflare-page-cache': - return $.get( wpmmVars.wpscActivationLink ); + case 'easy-mcp-ai': + return $.get( wpmmVars.easyMcpActivationLink ); default: break; } diff --git a/includes/classes/wp-maintenance-mode-admin.php b/includes/classes/wp-maintenance-mode-admin.php index da6399d..a8347a8 100644 --- a/includes/classes/wp-maintenance-mode-admin.php +++ b/includes/classes/wp-maintenance-mode-admin.php @@ -166,11 +166,8 @@ public function enqueue_admin_scripts() { 'pluginInstallNonce' => wp_create_nonce( 'updates' ), 'isOtterInstalled' => file_exists( ABSPATH . 'wp-content/plugins/otter-blocks/otter-blocks.php' ), 'isOtterActive' => is_plugin_active( 'otter-blocks/otter-blocks.php' ), - 'isOptimoleInstalled' => file_exists( ABSPATH . 'wp-content/plugins/optimole-wp/optimole-wp.php' ), - 'isOptimoleActive' => is_plugin_active( 'optimole-wp/optimole-wp.php' ), - 'isWPSCInstalled' => file_exists( ABSPATH . 'wp-content/plugins/wp-cloudflare-page-cache/wp-cloudflare-super-page-cache.php' ) || file_exists( ABSPATH . 'wp-content/plugins/wp-super-page-cache-pro/wp-cloudflare-super-page-cache-pro.php' ), - 'isWPSCActive' => is_plugin_active( 'wp-cloudflare-page-cache/wp-cloudflare-super-page-cache.php' ) || is_plugin_active( 'wp-super-page-cache-pro/wp-cloudflare-super-page-cache-pro.php' ), - 'isWPSCProInstalled' => file_exists( ABSPATH . 'wp-content/plugins/wp-super-page-cache-pro/wp-cloudflare-super-page-cache-pro.php' ), + 'isEasyMcpInstalled' => file_exists( ABSPATH . 'wp-content/plugins/easy-mcp-ai/easy-mcp-ai.php' ), + 'isEasyMcpActive' => is_plugin_active( 'easy-mcp-ai/easy-mcp-ai.php' ), 'errorString' => __( 'Something went wrong, please try again.', 'wp-maintenance-mode' ), 'loadingString' => __( 'Doing some magic...', 'wp-maintenance-mode' ), 'importingText' => __( 'Importing', 'wp-maintenance-mode' ), @@ -191,23 +188,13 @@ public function enqueue_admin_scripts() { ), esc_url( network_admin_url( 'plugins.php' ) ) ), - 'optimoleActivationLink' => add_query_arg( + 'easyMcpActivationLink' => add_query_arg( array( 'action' => 'activate', - 'plugin' => rawurlencode( 'optimole-wp/optimole-wp.php' ), + 'plugin' => rawurlencode( 'easy-mcp-ai/easy-mcp-ai.php' ), 'plugin_status' => 'all', 'paged' => '1', - '_wpnonce' => wp_create_nonce( 'activate-plugin_optimole-wp/optimole-wp.php' ), - ), - esc_url( network_admin_url( 'plugins.php' ) ) - ), - 'wpscActivationLink' => add_query_arg( - array( - 'action' => 'activate', - 'plugin' => rawurlencode( file_exists( ABSPATH . 'wp-content/plugins/wp-super-page-cache-pro/wp-cloudflare-super-page-cache-pro.php' ) ? 'wp-super-page-cache-pro/wp-cloudflare-super-page-cache-pro.php' : 'wp-cloudflare-page-cache/wp-cloudflare-super-page-cache.php' ), - 'plugin_status' => 'all', - 'paged' => '1', - '_wpnonce' => wp_create_nonce( file_exists( ABSPATH . 'wp-content/plugins/wp-super-page-cache-pro/wp-cloudflare-super-page-cache-pro.php' ) ? 'activate-plugin_wp-super-page-cache-pro/wp-cloudflare-super-page-cache-pro.php' : 'activate-plugin_wp-cloudflare-page-cache/wp-cloudflare-super-page-cache.php' ), + '_wpnonce' => wp_create_nonce( 'activate-plugin_easy-mcp-ai/easy-mcp-ai.php' ), ), esc_url( network_admin_url( 'plugins.php' ) ) ), diff --git a/views/wizard.php b/views/wizard.php index 0c0bb67..ec227b9 100644 --- a/views/wizard.php +++ b/views/wizard.php @@ -73,54 +73,26 @@ - +
- + -

%3$s %4$s', - __( 'Templates would have pre-optimized images and all of your website’s images would be delivered via Amazon Cloudfront CDN, resulting in an ≈ 80% increase in speed.', 'wp-maintenance-mode' ), - esc_url( 'https://wordpress.org/plugins/optimole-wp/' ), - 'Optimole', - __( 'plugin will be installed and activated.', 'wp-maintenance-mode' ) - ), - wpmm_translated_string_allowed_html() - ); - ?> -

-
- - -
-
- - - - - -
-

- %3$s %4$s', - __( 'Speed up your pages by 60-80% with intelligent caching. Achieve faster load times and better search rankings automatically, with', 'wp-maintenance-mode' ), - esc_url( 'https://wordpress.org/plugins/wp-cloudflare-page-cache/' ), - __( 'Super Page Cache', 'wp-maintenance-mode' ), + __( 'Connect AI assistants like Claude and ChatGPT to your site so they can create pages, edit content and manage settings for you, with', 'wp-maintenance-mode' ), + esc_url( 'https://wordpress.org/plugins/easy-mcp-ai/' ), + 'Easy MCP AI', __( 'plugin installed and activated automatically.', 'wp-maintenance-mode' ) ), wpmm_translated_string_allowed_html() From b267ec1275f41195267d72be06ceac726162f433 Mon Sep 17 00:00:00 2001 From: selul Date: Tue, 29 Sep 2026 15:39:35 +0300 Subject: [PATCH 34/37] chore: realign wizard localized vars after removing plugin links Co-Authored-By: Claude Opus 5.5 --- .../classes/wp-maintenance-mode-admin.php | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/includes/classes/wp-maintenance-mode-admin.php b/includes/classes/wp-maintenance-mode-admin.php index a8347a8..a56085f 100644 --- a/includes/classes/wp-maintenance-mode-admin.php +++ b/includes/classes/wp-maintenance-mode-admin.php @@ -159,26 +159,26 @@ public function enqueue_admin_scripts() { $this->plugin_slug . '-admin-script', 'wpmmVars', array( - 'ajaxURL' => admin_url( 'admin-ajax.php' ), - 'pluginURL' => add_query_arg( array( 'page' => $this->plugin_slug ), admin_url( 'admin.php' ) ), - 'ajaxNonce' => wp_create_nonce( 'ajax' ), - 'wizardNonce' => wp_create_nonce( 'wizard' ), - 'pluginInstallNonce' => wp_create_nonce( 'updates' ), - 'isOtterInstalled' => file_exists( ABSPATH . 'wp-content/plugins/otter-blocks/otter-blocks.php' ), - 'isOtterActive' => is_plugin_active( 'otter-blocks/otter-blocks.php' ), - 'isEasyMcpInstalled' => file_exists( ABSPATH . 'wp-content/plugins/easy-mcp-ai/easy-mcp-ai.php' ), - 'isEasyMcpActive' => is_plugin_active( 'easy-mcp-ai/easy-mcp-ai.php' ), - 'errorString' => __( 'Something went wrong, please try again.', 'wp-maintenance-mode' ), - 'loadingString' => __( 'Doing some magic...', 'wp-maintenance-mode' ), - 'importingText' => __( 'Importing', 'wp-maintenance-mode' ), - 'importDone' => __( 'Done', 'wp-maintenance-mode' ), - 'invalidEmailString' => __( 'Invalid email, please try again.', 'wp-maintenance-mode' ), - 'finishWizardStrings' => array( + 'ajaxURL' => admin_url( 'admin-ajax.php' ), + 'pluginURL' => add_query_arg( array( 'page' => $this->plugin_slug ), admin_url( 'admin.php' ) ), + 'ajaxNonce' => wp_create_nonce( 'ajax' ), + 'wizardNonce' => wp_create_nonce( 'wizard' ), + 'pluginInstallNonce' => wp_create_nonce( 'updates' ), + 'isOtterInstalled' => file_exists( ABSPATH . 'wp-content/plugins/otter-blocks/otter-blocks.php' ), + 'isOtterActive' => is_plugin_active( 'otter-blocks/otter-blocks.php' ), + 'isEasyMcpInstalled' => file_exists( ABSPATH . 'wp-content/plugins/easy-mcp-ai/easy-mcp-ai.php' ), + 'isEasyMcpActive' => is_plugin_active( 'easy-mcp-ai/easy-mcp-ai.php' ), + 'errorString' => __( 'Something went wrong, please try again.', 'wp-maintenance-mode' ), + 'loadingString' => __( 'Doing some magic...', 'wp-maintenance-mode' ), + 'importingText' => __( 'Importing', 'wp-maintenance-mode' ), + 'importDone' => __( 'Done', 'wp-maintenance-mode' ), + 'invalidEmailString' => __( 'Invalid email, please try again.', 'wp-maintenance-mode' ), + 'finishWizardStrings' => array( 'maintenance' => __( 'Your maintenance page is ready!', 'wp-maintenance-mode' ), 'coming-soon' => __( 'Your coming soon page is ready!', 'wp-maintenance-mode' ), ), - 'adminURL' => get_admin_url(), - 'otterActivationLink' => add_query_arg( + 'adminURL' => get_admin_url(), + 'otterActivationLink' => add_query_arg( array( 'action' => 'activate', 'plugin' => rawurlencode( 'otter-blocks/otter-blocks.php' ), @@ -188,7 +188,7 @@ public function enqueue_admin_scripts() { ), esc_url( network_admin_url( 'plugins.php' ) ) ), - 'easyMcpActivationLink' => add_query_arg( + 'easyMcpActivationLink' => add_query_arg( array( 'action' => 'activate', 'plugin' => rawurlencode( 'easy-mcp-ai/easy-mcp-ai.php' ), @@ -198,28 +198,28 @@ public function enqueue_admin_scripts() { ), esc_url( network_admin_url( 'plugins.php' ) ) ), - 'modalTexts' => array( + 'modalTexts' => array( 'title' => __( 'The template has been imported!', 'wp-maintenance-mode' ), 'description' => __( 'The template has been imported to a new draft page. You can take a look and enable it from plugin settings.', 'wp-maintenance-mode' ), 'buttonPage' => __( 'Go to page', 'wp-maintenance-mode' ), 'buttonSettings' => __( 'Go to Settings', 'wp-maintenance-mode' ), ), - 'confirmModalTexts' => array( + 'confirmModalTexts' => array( 'title' => __( 'Import this template?', 'wp-maintenance-mode' ), 'description' => __( 'By importing this template, the existing content on your Maintenance Page will be replaced. Do you wish to continue?', 'wp-maintenance-mode' ), 'buttonContinue' => __( 'Continue', 'wp-maintenance-mode' ), 'buttonGoBack' => __( 'Go back', 'wp-maintenance-mode' ), ), - 'imageUploaderDefaults' => array( + 'imageUploaderDefaults' => array( 'title' => _x( 'Upload Image', 'image_uploader default title', 'wp-maintenance-mode' ), 'buttonText' => _x( 'Choose Image', 'image_uploader default button_text', 'wp-maintenance-mode' ), ), - 'skipImportStrings' => array( + 'skipImportStrings' => array( 'maintenance' => __( 'I don’t want to use a Maintenance Template', 'wp-maintenance-mode' ), 'coming-soon' => __( 'I don’t want to use a Coming Soon Template', 'wp-maintenance-mode' ), 'landing-page' => __( 'I don’t want to use a Landing Page Template', 'wp-maintenance-mode' ), ), - 'skipImportDefault' => __( 'I don’t want to use a template', 'wp-maintenance-mode' ), + 'skipImportDefault' => __( 'I don’t want to use a template', 'wp-maintenance-mode' ), ) ); From 69ce4a5c3d7d273f2607ccb7624e47adc92ce5f8 Mon Sep 17 00:00:00 2001 From: selul Date: Tue, 29 Sep 2026 15:39:35 +0300 Subject: [PATCH 35/37] feat: tie the Easy MCP AI wizard copy to maintenance mode Co-Authored-By: Claude Opus 5.5 --- views/wizard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/wizard.php b/views/wizard.php index ec227b9..3d34f76 100644 --- a/views/wizard.php +++ b/views/wizard.php @@ -90,7 +90,7 @@ sprintf( // translators: %1$s is a description, %2$s is the Easy MCP AI URL, %3$s is the plugin name, %4$s is description text. '%1$s %3$s %4$s', - __( 'Connect AI assistants like Claude and ChatGPT to your site so they can create pages, edit content and manage settings for you, with', 'wp-maintenance-mode' ), + __( 'Let AI assistants like Claude and ChatGPT put your site in or out of maintenance mode, check who signed up on your coming soon page and edit your pages for you, with', 'wp-maintenance-mode' ), esc_url( 'https://wordpress.org/plugins/easy-mcp-ai/' ), 'Easy MCP AI', __( 'plugin installed and activated automatically.', 'wp-maintenance-mode' ) From f9fde5f46014822c7637d035c9f9337619bad824 Mon Sep 17 00:00:00 2001 From: selul Date: Tue, 29 Sep 2026 15:41:59 +0300 Subject: [PATCH 36/37] feat: shorten the Easy MCP AI wizard copy Co-Authored-By: Claude Opus 5.5 --- views/wizard.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/wizard.php b/views/wizard.php index 3d34f76..864404a 100644 --- a/views/wizard.php +++ b/views/wizard.php @@ -90,10 +90,10 @@ sprintf( // translators: %1$s is a description, %2$s is the Easy MCP AI URL, %3$s is the plugin name, %4$s is description text. '%1$s %3$s %4$s', - __( 'Let AI assistants like Claude and ChatGPT put your site in or out of maintenance mode, check who signed up on your coming soon page and edit your pages for you, with', 'wp-maintenance-mode' ), + __( 'Ask Claude or ChatGPT to take your site live, update pages or check new sign-ups, in plain English. No dashboard needed.', 'wp-maintenance-mode' ), esc_url( 'https://wordpress.org/plugins/easy-mcp-ai/' ), 'Easy MCP AI', - __( 'plugin installed and activated automatically.', 'wp-maintenance-mode' ) + __( 'will be installed for you.', 'wp-maintenance-mode' ) ), wpmm_translated_string_allowed_html() ); From 3f5c5c3fb15a3a7b0db181a1ce29101c027b56ae Mon Sep 17 00:00:00 2001 From: selul Date: Tue, 29 Sep 2026 15:45:32 +0300 Subject: [PATCH 37/37] feat: tie the wizard AI option to maintenance mode and Easy MCP AI Co-Authored-By: Claude Opus 5.5 --- views/wizard.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/views/wizard.php b/views/wizard.php index 864404a..5a09474 100644 --- a/views/wizard.php +++ b/views/wizard.php @@ -81,19 +81,16 @@

%3$s %4$s', - __( 'Ask Claude or ChatGPT to take your site live, update pages or check new sign-ups, in plain English. No dashboard needed.', 'wp-maintenance-mode' ), - esc_url( 'https://wordpress.org/plugins/easy-mcp-ai/' ), - 'Easy MCP AI', - __( 'will be installed for you.', 'wp-maintenance-mode' ) + // translators: %s is the Easy MCP AI plugin link. + __( 'Ask Claude or ChatGPT to put your site into maintenance mode, bring it back live, update your pages or check new sign-ups. The free %s plugin will be installed and activated.', 'wp-maintenance-mode' ), + 'Easy MCP AI' ), wpmm_translated_string_allowed_html() );