From 83df36463281ba6206c95ad1963f6d18c1bc9ff6 Mon Sep 17 00:00:00 2001 From: Chris Reynolds Date: Fri, 17 May 2024 09:20:41 -0600 Subject: [PATCH] [CMSP-995] Notices for low max age values in Page Cache Options (#268) * add initial admin interface class hook to mu-plugin > 1.4.0 * some more scaffolding * require at least 6.4 * don't show notices if wp_admin_notice doesn't exist * add notice if mu-plugin was not found * add notice if the mu-plugin is old * fix link and change decimal to string * use spaces instead of line breaks * add helper function to get current max age this is either set in the mu-plugin or empty. if empty, it should default to 1 week * add notice if less than 1 week but not == 600 seconds * add docblock * add translator comments * update tested up to and requires at least values in readmes * version bump * bump composer deps * don't display the warnings if we aren't on the pantheon cache admin page * convert static class to a namespace there's no reason to make this a class since we're not using any class features * add a bootstrapper for namespace-only files * lint * escape the wp_die message * use a helper for humanized time calculations * we still need to test the current max age * pass the current max age to the message, too and be specific that we're talking about seconds * use the filterable value for default max age because it could be something different * add a helper function for retrieving the default max age this is helpful so we can always return the filtered value * also display the seconds in the successful test * rank the max age value don't say it's "very low" unless that's actually accurate. * add caching to the ranked compare * make the color more severe if it's a bigger issue * change the color of the admin notice based on the severity of the diff * clear the transient when the option is updated * break notice type into a variable * fix the translator note * keep the rank but hide it from the dom * add unit tests * add initial behat test let's just see if we can assume this will work * fix failing test * specify the element where the notices are * add site health check test * actually save the changes * remove max age check only exists in an html comment anyway * simplify the site health check * add the message when you expand the accordion * add other site health things * change press to click? * add "open the accordion" as a custom step * move behat stuff around * move stuff around again * update the behat.yml * remove custom context and try "when I follow" * move to checking the text inside the elements * target the hidden element * use hidden='hidden' * call it a div * simplify the tests * remove press since we can't actually press * it did actually find the right heading maybe it will find the right text? * add @\since tags * add changelog and upgrade notice * let's try checking for some of the hidden text * period at the end of the sentence * add the ranks back this shows us that our ranking function is working * add the messages * remove age rank tests because goutte doesn't natively interpret html comments * add github testing badge since phpunit is no longer run on circle * add script to maybe skip behat tests if we don't need to run them * add step to workflow * do not run if the circle config changes * also ignore if scripts (that aren't behat-related) have changed * fix the logic (again) * one more flippity flip * remove the "ignoring" message it's actually implied by the fact that is_ignored is checked * use function to determine if a file should be ignored Co-authored-by: Phil Tyler * update changelog to note mu-plugin updates * Update tests/phpunit/test-admin-interface.php * use data providers to simplify the tests --------- Co-authored-by: Phil Tyler --- .circleci/config.yml | 3 + README.md | 15 +- bin/maybe-skip-tests.sh | 52 +++ composer.lock | 501 +++++++++++++++---------- inc/admin-interface.php | 319 ++++++++++++++++ pantheon-advanced-page-cache.php | 47 ++- readme.txt | 11 +- tests/behat/admin-interface.feature | 23 ++ tests/behat/site-health.feature | 28 ++ tests/phpunit/test-admin-interface.php | 160 ++++++++ 10 files changed, 945 insertions(+), 214 deletions(-) create mode 100755 bin/maybe-skip-tests.sh create mode 100644 inc/admin-interface.php create mode 100644 tests/behat/admin-interface.feature create mode 100644 tests/behat/site-health.feature create mode 100644 tests/phpunit/test-admin-interface.php diff --git a/.circleci/config.yml b/.circleci/config.yml index 7f08bc53..fb4d15c4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,6 +23,9 @@ jobs: - image: quay.io/pantheon-public/build-tools-ci:8.x-php8.2 steps: - checkout + - run: + name: Check if we need to run Behat + command: ./bin/maybe-skip-tests.sh - restore_cache: keys: - test-behat-dependencies-{{ checksum "composer.json" }} diff --git a/README.md b/README.md index 245bba5e..c4e19f7f 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ **Contributors:** [getpantheon](https://profiles.wordpress.org/getpantheon), [danielbachhuber](https://profiles.wordpress.org/danielbachhuber), [kporras07](https://profiles.wordpress.org/kporras07), [jspellman](https://profiles.wordpress.org/jspellman/), [jazzs3quence](https://profiles.wordpress.org/jazzs3quence/), [ryanshoover](https://profiles.wordpress.org/ryanshoover/), [rwagner00](https://profiles.wordpress.org/rwagner00/), [pwtyler](https://profiles.wordpress.org/pwtyler) **Tags:** pantheon, cdn, cache -**Requires at least:** 4.7 -**Tested up to:** 6.4.3 -**Stable tag:** 1.5.0-dev +**Requires at least:** 6.4 +**Tested up to:** 6.5.3 +**Stable tag:** 2.0.0-dev **License:** GPLv2 or later **License URI:** http://www.gnu.org/licenses/gpl-2.0.html @@ -12,7 +12,9 @@ Automatically clear related pages from Pantheon's Edge when you update content. ## Description ## -[![Actively Maintained](https://img.shields.io/badge/Pantheon-Actively_Maintained-yellow?logo=pantheon&color=FFDC28)](https://pantheon.io/docs/oss-support-levels#actively-maintained-support) [![CircleCI](https://circleci.com/gh/pantheon-systems/pantheon-advanced-page-cache.svg?style=svg)](https://circleci.com/gh/pantheon-systems/pantheon-advanced-page-cache) +[![Actively Maintained](https://img.shields.io/badge/Pantheon-Actively_Maintained-yellow?logo=pantheon&color=FFDC28)](https://pantheon.io/docs/oss-support-levels#actively-maintained-support) +[![Lint and Test](https://github.com/pantheon-systems/pantheon-advanced-page-cache/actions/workflows/lint-test.yml/badge.svg)](https://github.com/pantheon-systems/pantheon-advanced-page-cache/actions/workflows/lint-test.yml) +[![CircleCI](https://circleci.com/gh/pantheon-systems/pantheon-advanced-page-cache.svg?style=svg)](https://circleci.com/gh/pantheon-systems/pantheon-advanced-page-cache) For sites wanting fine-grained control over how their responses are represented in their edge cache, Pantheon Advanced Page Cache is the golden ticket. Here's a high-level overview of how the plugin works: @@ -353,7 +355,10 @@ Pantheon Advanced Page Cache integrates with WordPress plugins, including: See [CONTRIBUTING.md](https://github.com/pantheon-systems/pantheon-advanced-page-cache/blob/master/CONTRIBUTING.md) for information on contributing. ## Changelog ## -### 1.5.0-dev (March 11, 2024) ### +### 2.0.0-dev ### +* Adds new admin alerts and Site Health tests about default cache max-age settings and recommendations [[#268](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/268)]. The default Pantheon GCDN cache max-age value has been updated to 1 week in the [Pantheon MU plugin](https://github.com/pantheon-systems/pantheon-mu-plugin). For more information, see the [release note](https://docs.pantheon.io/release-notes/2024/04/pantheon-mu-plugin-1-4-0-update). + +### 1.5.0 (March 11, 2024) ### * Adds filter `pantheon_purge_post_type_ignored` to allow an array of post types to ignore before purging cache [[#258](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/258)] * Adds [wpunit-helpers](https://github.com/pantheon-systems/wpunit-helpers) for running/setting up WP Unit tests diff --git a/bin/maybe-skip-tests.sh b/bin/maybe-skip-tests.sh new file mode 100755 index 00000000..c49d309e --- /dev/null +++ b/bin/maybe-skip-tests.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Paths to ignore. +ignored_paths=( + .editorconfig + .gitattributes + .gitignore + CODEOWNERS + CONTRIBUTING.md + LICENSE + phpcs.xml.dist + phpunit.xml.dist + README.md + readme.txt + .circleci/config.yml + bin/maybe-skip-tests.sh + bin/helpers.sh + bin/install-local-tests.sh + bin/install-wp-tests.sh + bin/phpunit-test.sh + .wordpress-org/* + .github/* + tests/phpunit/* +) + +# Fetch list of changed files from the last commit +changed_files=$(git diff-tree --no-commit-id --name-only -r HEAD) +should_run_tests=true + + +is_ignored_file(){ + for ignore in "${ignored_paths[@]}"; do + if [[ "${1:-}" == "$ignore" ]]; then + return 0 + fi + done + return 1 +} + +for file in $changed_files; do + if ! is_ignored_file "$file"; then + echo "Running tests because $file was changed." + break + fi + should_run_tests=false + echo "Skipping $file..." +done + +if [ "$should_run_tests" = false ]; then + echo "Only ignored files modified. Skipping Behat tests." + circleci-agent step halt +fi diff --git a/composer.lock b/composer.lock index bca61323..8563bfe2 100644 --- a/composer.lock +++ b/composer.lock @@ -1136,16 +1136,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.0.1", + "version": "v5.0.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69" + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/2218c2252c874a4624ab2f613d86ac32d227bc69", - "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", "shasum": "" }, "require": { @@ -1188,22 +1188,22 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" }, - "time": "2024-02-21T19:24:10+00:00" + "time": "2024-03-05T20:51:40+00:00" }, { "name": "pantheon-systems/pantheon-wordpress-upstream-tests", "version": "dev-master", "source": { "type": "git", - "url": "git@github.com:pantheon-systems/pantheon-wordpress-upstream-tests.git", - "reference": "004fc97a604950aef4f62773417691d23b3d75b0" + "url": "https://github.com/pantheon-systems/pantheon-wordpress-upstream-tests.git", + "reference": "612d7d3e5ce064d7d30cc9752bc0e06d4a22f933" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pantheon-systems/pantheon-wordpress-upstream-tests/zipball/004fc97a604950aef4f62773417691d23b3d75b0", - "reference": "004fc97a604950aef4f62773417691d23b3d75b0", + "url": "https://api.github.com/repos/pantheon-systems/pantheon-wordpress-upstream-tests/zipball/612d7d3e5ce064d7d30cc9752bc0e06d4a22f933", + "reference": "612d7d3e5ce064d7d30cc9752bc0e06d4a22f933", "shasum": "" }, "require": { @@ -1225,7 +1225,11 @@ "email": "noreply@pantheon.io" } ], - "time": "2023-08-11T17:05:44+00:00" + "support": { + "issues": "https://github.com/pantheon-systems/pantheon-wordpress-upstream-tests/issues", + "source": "https://github.com/pantheon-systems/pantheon-wordpress-upstream-tests/tree/master" + }, + "time": "2024-05-07T20:24:56+00:00" }, { "name": "pantheon-systems/pantheon-wp-coding-standards", @@ -1317,20 +1321,21 @@ }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -1371,9 +1376,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -1490,28 +1501,28 @@ }, { "name": "phpcompatibility/phpcompatibility-paragonie", - "version": "1.3.2", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", - "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26" + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", - "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/293975b465e0e709b571cbf0c957c6c0a7b9a2ac", + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac", "shasum": "" }, "require": { "phpcompatibility/php-compatibility": "^9.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "paragonie/random_compat": "dev-master", "paragonie/sodium_compat": "dev-master" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", @@ -1541,22 +1552,37 @@ ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" }, - "time": "2022-10-25T01:46:02+00:00" + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-04-24T21:30:46+00:00" }, { "name": "phpcompatibility/phpcompatibility-wp", - "version": "2.1.4", + "version": "2.1.5", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", - "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5" + "reference": "01c1ff2704a58e46f0cb1ca9d06aee07b3589082" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", - "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/01c1ff2704a58e46f0cb1ca9d06aee07b3589082", + "reference": "01c1ff2704a58e46f0cb1ca9d06aee07b3589082", "shasum": "" }, "require": { @@ -1564,10 +1590,10 @@ "phpcompatibility/phpcompatibility-paragonie": "^1.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7" + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", @@ -1596,9 +1622,24 @@ ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" }, - "time": "2022-10-24T09:00:36+00:00" + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-04-24T21:37:59+00:00" }, { "name": "phpcsstandards/phpcsextra", @@ -1680,22 +1721,22 @@ }, { "name": "phpcsstandards/phpcsutils", - "version": "1.0.9", + "version": "1.0.11", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", - "reference": "908247bc65010c7b7541a9551e002db12e9dae70" + "reference": "c457da9dabb60eb7106dd5e3c05132b1a6539c6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/908247bc65010c7b7541a9551e002db12e9dae70", - "reference": "908247bc65010c7b7541a9551e002db12e9dae70", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/c457da9dabb60eb7106dd5e3c05132b1a6539c6a", + "reference": "c457da9dabb60eb7106dd5e3c05132b1a6539c6a", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.8.0 || 4.0.x-dev@dev" + "squizlabs/php_codesniffer": "^3.9.0 || 4.0.x-dev@dev" }, "require-dev": { "ext-filter": "*", @@ -1764,20 +1805,20 @@ "type": "open_collective" } ], - "time": "2023-12-08T14:50:00+00:00" + "time": "2024-04-24T11:47:18+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.26.0", + "version": "1.29.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "231e3186624c03d7e7c890ec662b81e6b0405227" + "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/231e3186624c03d7e7c890ec662b81e6b0405227", - "reference": "231e3186624c03d7e7c890ec662b81e6b0405227", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/536889f2b340489d328f5ffb7b02bb6b183ddedc", + "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc", "shasum": "" }, "require": { @@ -1809,22 +1850,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.26.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.0" }, - "time": "2024-02-23T16:05:55+00:00" + "time": "2024-05-06T12:04:23+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.30", + "version": "9.2.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", "shasum": "" }, "require": { @@ -1881,7 +1922,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" }, "funding": [ { @@ -1889,7 +1930,7 @@ "type": "github" } ], - "time": "2023-12-22T06:47:57+00:00" + "time": "2024-03-02T06:37:42+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2134,16 +2175,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.17", + "version": "9.6.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", "shasum": "" }, "require": { @@ -2217,7 +2258,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" }, "funding": [ { @@ -2233,7 +2274,7 @@ "type": "tidelift" } ], - "time": "2024-02-23T13:14:51+00:00" + "time": "2024-04-05T04:35:58+00:00" }, { "name": "psr/container", @@ -2432,16 +2473,16 @@ }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { @@ -2476,7 +2517,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { @@ -2484,7 +2525,7 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { "name": "sebastian/code-unit", @@ -2730,16 +2771,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { @@ -2784,7 +2825,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -2792,7 +2833,7 @@ "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", @@ -2859,16 +2900,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { @@ -2924,7 +2965,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -2932,20 +2973,20 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.6", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { @@ -2988,7 +3029,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -2996,7 +3037,7 @@ "type": "github" } ], - "time": "2023-08-02T09:26:13+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { "name": "sebastian/lines-of-code", @@ -3232,16 +3273,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -3253,7 +3294,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -3274,8 +3315,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -3283,7 +3323,7 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", @@ -3396,16 +3436,16 @@ }, { "name": "sirbrillig/phpcs-variable-analysis", - "version": "v2.11.17", + "version": "v2.11.18", "source": { "type": "git", "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", - "reference": "3b71162a6bf0cde2bff1752e40a1788d8273d049" + "reference": "ca242a0b7309e0f9d1f73b236e04ecf4ca3248d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/3b71162a6bf0cde2bff1752e40a1788d8273d049", - "reference": "3b71162a6bf0cde2bff1752e40a1788d8273d049", + "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/ca242a0b7309e0f9d1f73b236e04ecf4ca3248d0", + "reference": "ca242a0b7309e0f9d1f73b236e04ecf4ca3248d0", "shasum": "" }, "require": { @@ -3450,36 +3490,36 @@ "source": "https://github.com/sirbrillig/phpcs-variable-analysis", "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" }, - "time": "2023-08-05T23:46:11+00:00" + "time": "2024-04-13T16:42:46+00:00" }, { "name": "slevomat/coding-standard", - "version": "8.14.1", + "version": "8.15.0", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "fea1fd6f137cc84f9cba0ae30d549615dbc6a926" + "reference": "7d1d957421618a3803b593ec31ace470177d7817" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/fea1fd6f137cc84f9cba0ae30d549615dbc6a926", - "reference": "fea1fd6f137cc84f9cba0ae30d549615dbc6a926", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/7d1d957421618a3803b593ec31ace470177d7817", + "reference": "7d1d957421618a3803b593ec31ace470177d7817", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", "php": "^7.2 || ^8.0", "phpstan/phpdoc-parser": "^1.23.1", - "squizlabs/php_codesniffer": "^3.7.1" + "squizlabs/php_codesniffer": "^3.9.0" }, "require-dev": { "phing/phing": "2.17.4", "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.10.37", + "phpstan/phpstan": "1.10.60", "phpstan/phpstan-deprecation-rules": "1.1.4", - "phpstan/phpstan-phpunit": "1.3.14", - "phpstan/phpstan-strict-rules": "1.5.1", - "phpunit/phpunit": "8.5.21|9.6.8|10.3.5" + "phpstan/phpstan-phpunit": "1.3.16", + "phpstan/phpstan-strict-rules": "1.5.2", + "phpunit/phpunit": "8.5.21|9.6.8|10.5.11" }, "type": "phpcodesniffer-standard", "extra": { @@ -3503,7 +3543,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.14.1" + "source": "https://github.com/slevomat/coding-standard/tree/8.15.0" }, "funding": [ { @@ -3515,20 +3555,20 @@ "type": "tidelift" } ], - "time": "2023-10-08T07:28:08+00:00" + "time": "2024-03-09T15:20:58+00:00" }, { "name": "spryker/code-sniffer", - "version": "0.17.21", + "version": "0.17.23", "source": { "type": "git", "url": "https://github.com/spryker/code-sniffer.git", - "reference": "a1ab7fa30981a57b43d1eab5339fd52b600e2f60" + "reference": "1cb5550e11f25c200016d0d3cd7021b2dfeadaf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spryker/code-sniffer/zipball/a1ab7fa30981a57b43d1eab5339fd52b600e2f60", - "reference": "a1ab7fa30981a57b43d1eab5339fd52b600e2f60", + "url": "https://api.github.com/repos/spryker/code-sniffer/zipball/1cb5550e11f25c200016d0d3cd7021b2dfeadaf5", + "reference": "1cb5550e11f25c200016d0d3cd7021b2dfeadaf5", "shasum": "" }, "require": { @@ -3573,20 +3613,20 @@ "issues": "https://github.com/spryker/code-sniffer/issues", "source": "https://github.com/spryker/code-sniffer" }, - "time": "2024-01-30T11:39:44+00:00" + "time": "2024-03-22T09:53:22+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.9.0", + "version": "3.9.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" + "reference": "aac1f6f347a5c5ac6bc98ad395007df00990f480" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/aac1f6f347a5c5ac6bc98ad395007df00990f480", + "reference": "aac1f6f347a5c5ac6bc98ad395007df00990f480", "shasum": "" }, "require": { @@ -3653,7 +3693,7 @@ "type": "open_collective" } ], - "time": "2024-02-16T15:06:51+00:00" + "time": "2024-04-23T20:25:34+00:00" }, { "name": "symfony/browser-kit", @@ -3807,16 +3847,16 @@ }, { "name": "symfony/console", - "version": "v5.4.35", + "version": "v5.4.39", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "dbdf6adcb88d5f83790e1efb57ef4074309d3931" + "reference": "f3e591c48688a0cfa1a3296205926c05e84b22b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/dbdf6adcb88d5f83790e1efb57ef4074309d3931", - "reference": "dbdf6adcb88d5f83790e1efb57ef4074309d3931", + "url": "https://api.github.com/repos/symfony/console/zipball/f3e591c48688a0cfa1a3296205926c05e84b22b1", + "reference": "f3e591c48688a0cfa1a3296205926c05e84b22b1", "shasum": "" }, "require": { @@ -3886,7 +3926,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.35" + "source": "https://github.com/symfony/console/tree/v5.4.39" }, "funding": [ { @@ -3902,20 +3942,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:28:09+00:00" + "time": "2024-04-18T08:26:06+00:00" }, { "name": "symfony/css-selector", - "version": "v5.4.35", + "version": "v5.4.39", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "9e615d367e2bed41f633abb383948c96a2dbbfae" + "reference": "0934c9f1d433776f25c629bdc93f3e157d139e08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/9e615d367e2bed41f633abb383948c96a2dbbfae", - "reference": "9e615d367e2bed41f633abb383948c96a2dbbfae", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/0934c9f1d433776f25c629bdc93f3e157d139e08", + "reference": "0934c9f1d433776f25c629bdc93f3e157d139e08", "shasum": "" }, "require": { @@ -3952,7 +3992,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.4.35" + "source": "https://github.com/symfony/css-selector/tree/v5.4.39" }, "funding": [ { @@ -3968,7 +4008,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T13:51:25+00:00" + "time": "2024-04-18T08:26:06+00:00" }, { "name": "symfony/dependency-injection", @@ -4058,16 +4098,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { @@ -4076,7 +4116,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -4105,7 +4145,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -4121,7 +4161,7 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/dom-crawler", @@ -4199,16 +4239,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v5.4.35", + "version": "v5.4.39", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "7a69a85c7ea5bdd1e875806a99c51a87d3a74b38" + "reference": "d40fae9fd85c762b6ba378152fdd1157a85d7e4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7a69a85c7ea5bdd1e875806a99c51a87d3a74b38", - "reference": "7a69a85c7ea5bdd1e875806a99c51a87d3a74b38", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d40fae9fd85c762b6ba378152fdd1157a85d7e4f", + "reference": "d40fae9fd85c762b6ba378152fdd1157a85d7e4f", "shasum": "" }, "require": { @@ -4264,7 +4304,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.35" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.39" }, "funding": [ { @@ -4280,20 +4320,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T13:51:25+00:00" + "time": "2024-04-18T08:26:06+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", - "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", "shasum": "" }, "require": { @@ -4303,7 +4343,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -4340,7 +4380,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" }, "funding": [ { @@ -4356,27 +4396,28 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/filesystem", - "version": "v5.4.35", + "version": "v5.4.39", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "5a553607d4ffbfa9c0ab62facadea296c9db7086" + "reference": "e6edd875d5d39b03de51f3c3951148cfa79a4d12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/5a553607d4ffbfa9c0ab62facadea296c9db7086", - "reference": "5a553607d4ffbfa9c0ab62facadea296c9db7086", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/e6edd875d5d39b03de51f3c3951148cfa79a4d12", + "reference": "e6edd875d5d39b03de51f3c3951148cfa79a4d12", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-php80": "^1.16", + "symfony/process": "^5.4|^6.4" }, "type": "library", "autoload": { @@ -4404,7 +4445,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.35" + "source": "https://github.com/symfony/filesystem/tree/v5.4.39" }, "funding": [ { @@ -4420,7 +4461,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T13:51:25+00:00" + "time": "2024-04-18T08:26:06+00:00" }, { "name": "symfony/polyfill-ctype", @@ -5129,18 +5170,79 @@ ], "time": "2024-01-29T20:11:03+00:00" }, + { + "name": "symfony/process", + "version": "v6.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "cdb1c81c145fd5aa9b0038bab694035020943381" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/cdb1c81c145fd5aa9b0038bab694035020943381", + "reference": "cdb1c81c145fd5aa9b0038bab694035020943381", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.4.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:22:46+00:00" + }, { "name": "symfony/service-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3", "shasum": "" }, "require": { @@ -5194,7 +5296,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.3" }, "funding": [ { @@ -5210,20 +5312,20 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:17:29+00:00" + "time": "2023-04-21T15:04:16+00:00" }, { "name": "symfony/string", - "version": "v6.4.3", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "7a14736fb179876575464e4658fce0c304e8c15b" + "reference": "ffeb9591c61f65a68d47f77d12b83fa530227a69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/7a14736fb179876575464e4658fce0c304e8c15b", - "reference": "7a14736fb179876575464e4658fce0c304e8c15b", + "url": "https://api.github.com/repos/symfony/string/zipball/ffeb9591c61f65a68d47f77d12b83fa530227a69", + "reference": "ffeb9591c61f65a68d47f77d12b83fa530227a69", "shasum": "" }, "require": { @@ -5280,7 +5382,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.3" + "source": "https://github.com/symfony/string/tree/v6.4.7" }, "funding": [ { @@ -5296,7 +5398,7 @@ "type": "tidelift" } ], - "time": "2024-01-25T09:26:29+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/translation", @@ -5389,16 +5491,16 @@ }, { "name": "symfony/translation-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" + "reference": "b0073a77ac0b7ea55131020e87b1e3af540f4664" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", - "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b0073a77ac0b7ea55131020e87b1e3af540f4664", + "reference": "b0073a77ac0b7ea55131020e87b1e3af540f4664", "shasum": "" }, "require": { @@ -5447,7 +5549,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/translation-contracts/tree/v2.5.3" }, "funding": [ { @@ -5463,20 +5565,20 @@ "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2024-01-23T13:51:25+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.3", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "d75715985f0f94f978e3a8fa42533e10db921b90" + "reference": "53e8b1ef30a65f78eac60fddc5ee7ebbbdb1dee0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/d75715985f0f94f978e3a8fa42533e10db921b90", - "reference": "d75715985f0f94f978e3a8fa42533e10db921b90", + "url": "https://api.github.com/repos/symfony/yaml/zipball/53e8b1ef30a65f78eac60fddc5ee7ebbbdb1dee0", + "reference": "53e8b1ef30a65f78eac60fddc5ee7ebbbdb1dee0", "shasum": "" }, "require": { @@ -5519,7 +5621,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.3" + "source": "https://github.com/symfony/yaml/tree/v6.4.7" }, "funding": [ { @@ -5535,20 +5637,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-04-28T10:28:08+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -5577,7 +5679,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -5585,20 +5687,20 @@ "type": "github" } ], - "time": "2023-11-20T00:12:19+00:00" + "time": "2024-03-03T12:36:25+00:00" }, { "name": "wp-coding-standards/wpcs", - "version": "3.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", - "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1" + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b4caf9689f1a0e4a4c632679a44e638c1c67aff1", - "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7", "shasum": "" }, "require": { @@ -5607,16 +5709,16 @@ "ext-tokenizer": "*", "ext-xmlreader": "*", "php": ">=5.4", - "phpcsstandards/phpcsextra": "^1.1.0", - "phpcsstandards/phpcsutils": "^1.0.8", - "squizlabs/php_codesniffer": "^3.7.2" + "phpcsstandards/phpcsextra": "^1.2.1", + "phpcsstandards/phpcsutils": "^1.0.10", + "squizlabs/php_codesniffer": "^3.9.0" }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.0", "phpcsstandards/phpcsdevtools": "^1.2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "suggest": { "ext-iconv": "For improved results", @@ -5647,24 +5749,24 @@ }, "funding": [ { - "url": "https://opencollective.com/thewpcc/contribute/wp-php-63406", + "url": "https://opencollective.com/php_codesniffer", "type": "custom" } ], - "time": "2023-09-14T07:06:09+00:00" + "time": "2024-03-25T16:39:00+00:00" }, { "name": "yoast/phpunit-polyfills", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "c758753e8f9dac251fed396a73c8305af3f17922" + "reference": "4a088f125c970d6d6ea52c927f96fe39b330d0f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/c758753e8f9dac251fed396a73c8305af3f17922", - "reference": "c758753e8f9dac251fed396a73c8305af3f17922", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/4a088f125c970d6d6ea52c927f96fe39b330d0f1", + "reference": "4a088f125c970d6d6ea52c927f96fe39b330d0f1", "shasum": "" }, "require": { @@ -5672,7 +5774,9 @@ "phpunit/phpunit": "^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0" }, "require-dev": { - "yoast/yoastcs": "^2.3.0" + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "yoast/yoastcs": "^3.1.0" }, "type": "library", "extra": { @@ -5709,9 +5813,10 @@ ], "support": { "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2023-06-06T20:28:24+00:00" + "time": "2024-04-05T16:36:44+00:00" } ], "aliases": [], diff --git a/inc/admin-interface.php b/inc/admin-interface.php new file mode 100644 index 00000000..be6feae2 --- /dev/null +++ b/inc/admin-interface.php @@ -0,0 +1,319 @@ + 1.4.0. + if ( version_compare( PANTHEON_MU_PLUGIN_VERSION, '1.4.0', '>' ) ) { + // Do stuff, e.g. add_action(). + add_action( 'admin_notices', __NAMESPACE__ . '\\admin_notice_maybe_recommend_higher_max_age' ); + } else { + add_action( 'admin_notices', __NAMESPACE__ . '\\admin_notice_old_mu_plugin' ); + } + } else { + add_action( 'admin_notices', __NAMESPACE__ . '\\admin_notice_no_mu_plugin' ); + } + + add_filter( 'site_status_tests', __NAMESPACE__ . '\\default_cache_max_age_test' ); + add_action( 'update_option_pantheon-cache', __NAMESPACE__ . '\\clear_max_age_compare_cache' ); +} + +/** + * Display an admin notice if the Pantheon MU plugin was not found. + * + * @since 2.0.0 + * @return void + */ +function admin_notice_no_mu_plugin() { + /** + * Allow disabling the admin notice. + * + * @param bool $disable_admin_notices Whether to disable the admin notice. + */ + if ( apply_filters( 'pantheon_apc_disable_admin_notices', false ) ) { + return; + } + + wp_admin_notice( + // translators: %s is a link to the Pantheon MU plugin. + sprintf( __( 'Pantheon Advanced Page Cache works best on the Pantheon platform. If you are working inside a Pantheon environment, ensure your site is using the Pantheon MU plugin.', 'pantheon-advanced-page-cache' ), 'https://github.com/pantheon-systems/pantheon-mu-plugin' ), + [ + 'type' => 'error', + 'dismissible' => true, + ] + ); +} + +/** + * Display an admin notice if the Pantheon MU plugin is out of date. + * + * @since 2.0.0 + * @return void + */ +function admin_notice_old_mu_plugin() { + $current_screen = get_current_screen(); + + if ( apply_filters( 'pantheon_apc_disable_admin_notices', false ) || 'settings_page_pantheon-cache' !== $current_screen->id ) { + return; + } + + $mu_plugin_version = PANTHEON_MU_PLUGIN_VERSION; + $message = sprintf( + // translators: %1$s is a link to the Pantheon MU plugin, %2$s is the version of the MU plugin. + __( 'You appear to have an old version of the Pantheon MU plugin. 1.4.0 or above expected but %2$s found.', 'pantheon-advanced-page-cache' ), + 'https://github.com/pantheon-systems/pantheon-mu-plugin', + $mu_plugin_version + ); + + // Check if there's a composer.json file in the root of the site. + if ( file_exists( ABSPATH . 'composer.json' ) ) { + $message .= ' ' . __( 'If you are using Composer, you can update the MU plugin by running composer update.', 'pantheon-advanced-page-cache' ); + } else { + $message .= ' ' . __( 'You should Apply Updates from the Pantheon Dashboard to get the latest version of WordPress and the Pantheon MU plugin.', 'pantheon-advanced-page-cache' ); + } + + wp_admin_notice( + // translators: %s is a link to the Pantheon MU plugin. + $message, + [ + 'type' => 'warning', + 'dismissible' => true, + ] + ); +} + +/** + * Display an admin notice if the max-age is less than a week but not equal to 600 seconds. + * + * @since 2.0.0 + * @return void + */ +function admin_notice_maybe_recommend_higher_max_age() { + $current_screen = get_current_screen(); + + if ( apply_filters( 'pantheon_apc_disable_admin_notices', false ) || 'settings_page_pantheon-cache' !== $current_screen->id ) { + return; + } + + $max_age_rank = max_age_compare(); + $current_max_age = get_current_max_age(); + if ( + $max_age_rank > 0 && + $current_max_age < WEEK_IN_SECONDS && + $current_max_age !== 600 + ) { + // If the current max-age value has a rank of 3 or more (10 is the highest), we'll note that it's very low. + $very_low = $max_age_rank > 3 ? __( 'This is a very low value and may not be optimal for your site.', 'pantheon-advanced-page-cache' ) : ''; + $message = sprintf( + // translators: %1$s is the current max-age, %2$d is the current max-age in seconds, %3$s is a message that displays if the value is very low, %44d is the recommended max age in seconds, %5$s is the humanized recommended max age, %6$s is debug information that is written to the HTML DOM but not displayed. + __( 'The cache max-age is currently set to %1$s (%2$s seconds). %3$s Consider increasing the cache max-age to at least %4$d seconds (%5$s).%6$s', 'pantheon-advanced-page-cache' ), + humanized_max_age(), + $current_max_age, + $very_low, + WEEK_IN_SECONDS, + humanized_max_age( true ), + sprintf( '', $max_age_rank ) + ); + + // Escalating notice types based on the max-age rank. + $notice_type = ( $max_age_rank === 1 ? 'info' : $max_age_rank > 3 ) ? 'error' : 'warning'; + + wp_admin_notice( + $message, + [ + 'type' => $notice_type, + 'dismissible' => true, + ] + ); + } +} + +/** + * Get the current max-age value. + * + * This comes from the Pantheon mu-plugin and only exists if settings were actually saved. + * + * If the site existed prior to 1.4.0 of the mu-plugin, the default value is 600 seconds. Otherwise, the default value is 1 week. + * + * @since 2.0.0 + * @return int + */ +function get_current_max_age() { + $options = get_option( 'pantheon-cache', [] ); + + // If the default_ttl option is not set, we're using the default, which is 1 week. + if ( ! isset( $options['default_ttl'] ) ) { + return get_default_max_age(); + } + + return apply_filters( 'pantheon_cache_default_max_age', $options['default_ttl'] ); +} + +/** + * Add a test to the Site Health page to check the cache max-age. + * + * @param array $tests The Site Health tests. + * + * @since 2.0.0 + * @return array + */ +function default_cache_max_age_test( $tests ) { + $tests['direct']['pantheon_edge_cache'] = [ + 'label' => __( 'Pantheon Edge Cache', 'pantheon-advanced-page-cache' ), + 'test' => __NAMESPACE__ . '\\test_cache_max_age', + ]; + + return $tests; +} + +/** + * Get the humanized max-age. + * + * @param bool $recommended Whether to get the recommended max-age. + * + * @since 2.0.0 + * @return string + */ +function humanized_max_age( $recommended = false ) { + $time = time(); + $current_max_age = $recommended ? get_default_max_age() : get_current_max_age(); + $humanized_time = human_time_diff( $time, $time + $current_max_age ); + + return $humanized_time; +} + +/** + * Get the default max-age. + * + * @since 2.0.0 + * @return int + */ +function get_default_max_age() { + return apply_filters( 'pantheon_cache_default_max_age', WEEK_IN_SECONDS ); +} + +/** + * Compare the current max-age to the default max-age. + * + * @since 2.0.0 + * @return int A ranked value from 0 to 10 where 0 is optimal (equal to or greater than the recommended max age) and 10 is very bad. + */ +function max_age_compare() { + $cached_rank = get_transient( 'papc_max_age_compare' ); + + if ( false !== $cached_rank ) { + return $cached_rank; + } + + $current_max_age = get_current_max_age(); + $default_max_age = get_default_max_age(); + $diff = $current_max_age - $default_max_age; + + if ( $diff >= 0 ) { + return 0; + } + + // Rank the difference on a scale of 0 ($current_max_age >= $default_max_age) to 10 and return the rank int. + $rank = round( abs( $diff ) / $default_max_age * 10 ); + + $cached_rank = min( max( $rank, 1 ), 10 ); + set_transient( 'papc_max_age_compare', $cached_rank, WEEK_IN_SECONDS ); + return $cached_rank; +} + +/** + * The GCDN cache max-age Site Health test. + * + * @since 2.0.0 + * @return array + */ +function test_cache_max_age() { + $default_max_age = get_default_max_age(); + $current_max_age = get_current_max_age(); + $humanized_time = humanized_max_age(); + $humanized_reccomended_time = humanized_max_age( true ); + $recommend_color = max_age_compare() > 3 ? 'red' : 'orange'; + + if ( $current_max_age < $default_max_age ) { + $result = [ + 'label' => __( 'Pantheon GCDN Cache Max-Age', 'pantheon-advanced-page-cache' ), + 'status' => 'recommended', + 'badge' => [ + 'label' => __( 'Performance', 'pantheon-advanced-page-cache' ), + 'color' => $recommend_color, + ], + 'description' => sprintf( + // translators: %1$s is the current max-age, %2$s is the recommended max-age, %3$d is the recommended max-age in seconds. + __( 'The Pantheon GCDN cache max-age is currently set to %1$s (%2$d seconds). We recommend increasing to %3$s (%4$d seconds).', 'pantheon-advanced-page-cache' ), + $humanized_time, + $current_max_age, + $humanized_reccomended_time, + $default_max_age + ), + 'test' => 'pantheon_edge_cache', + ]; + + return $result; + } + + $result = [ + 'label' => sprintf( + // translators: %s is the humanized time. + __( 'Pantheon GCDN Cache Max-Age set to %1$s', 'pantheon-advanced-page-cache' ), + $humanized_time, + $humanized_reccomended_time + ), + 'status' => 'good', + 'badge' => [ + 'label' => __( 'Performance', 'pantheon-advanced-page-cache' ), + 'color' => 'blue', + ], + 'description' => sprintf( + '%1$s
%2$s', + sprintf( + // translators: %1$s is the current max-age, %2$s is the recommended max-age, %3$d is the recommended max-age in seconds. + __( 'The Pantheon cache max-age is currently set to %1$s (%2$s seconds). Our recommendation is %3$s (%4$d seconds) or more.', 'pantheon-advanced-page-cache' ), + $humanized_time, + $current_max_age, + $humanized_reccomended_time, + $default_max_age + ), + sprintf( + // translators: %s is a link to the cache configuration guide. + __( 'View our cache configuration guide for more information.', 'pantheon-advanced-page-cache' ), + 'https://docs.pantheon.io/guides/wordpress-configurations/wordpress-cache-plugin#pantheon-page-cache-plugin-configuration' + ) + ), + 'test' => 'pantheon_edge_cache', + ]; + + return $result; +} + +/** + * Clear the max-age compare cache when the max-age is updated. + * + * @since 2.0.0 + * @return void + */ +function clear_max_age_compare_cache() { + delete_transient( 'papc_max_age_compare' ); +} diff --git a/pantheon-advanced-page-cache.php b/pantheon-advanced-page-cache.php index b574ff34..06b130ff 100644 --- a/pantheon-advanced-page-cache.php +++ b/pantheon-advanced-page-cache.php @@ -1,13 +1,15 @@ __DIR__ . '/inc/admin-interface.php', + ]; + + foreach ( $namespaced_files as $namespace => $file ) { + if ( file_exists( $file ) ) { + require $file; + call_user_func( $namespace . '\\bootstrap' ); + } else { + wp_die( esc_html( "Could not find $file" ), 'Pantheon Advanced Page Cache error' ); + + } + } +} + /** * Registers the class autoloader. */ @@ -122,6 +148,11 @@ function ( $class_autoloader ) { } ); +/** + * Init namespaced files. + */ +add_action( 'plugins_loaded', 'pantheon_bootstrap_namespaces' ); + /** * Registers relevant UI */ diff --git a/readme.txt b/readme.txt index cf1c1f43..2dc976d3 100644 --- a/readme.txt +++ b/readme.txt @@ -1,9 +1,9 @@ === Pantheon Advanced Page Cache === Contributors: getpantheon, danielbachhuber, kporras07, jspellman, jazzs3quence, ryanshoover, rwagner00, pwtyler Tags: pantheon, cdn, cache -Requires at least: 4.7 -Tested up to: 6.4.3 -Stable tag: 1.5.0 +Requires at least: 6.4 +Tested up to: 6.5.3 +Stable tag: 2.0.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -332,6 +332,9 @@ Pantheon Advanced Page Cache integrates with WordPress plugins, including: See [CONTRIBUTING.md](https://github.com/pantheon-systems/wp-saml-auth/blob/master/CONTRIBUTING.md) for information on contributing. == Changelog == += 2.0.0-dev = +* Adds new admin alerts and Site Health tests about default cache max-age settings and recommendations [[#268](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/268)]. The default Pantheon GCDN cache max-age value has been updated to 1 week in the [Pantheon MU plugin](https://github.com/pantheon-systems/pantheon-mu-plugin). For more information, see the [release note](https://docs.pantheon.io/release-notes/2024/04/pantheon-mu-plugin-1-4-0-update). + = 1.5.0 (11 March 2024) = * Adds filter `pantheon_purge_post_type_ignored` to allow an array of post types to ignore before purging cache [[#258](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/258)] * Adds [wpunit-helpers](https://github.com/pantheon-systems/wpunit-helpers) for running/setting up WP Unit tests @@ -409,6 +412,8 @@ See [CONTRIBUTING.md](https://github.com/pantheon-systems/wp-saml-auth/blob/mast * Initial release. == Upgrade Notice == += 2.0.0 = +This release requires a minimum WordPress version of 6.4.0. It uses Site Health checks and the `wp_admin_notices` function to alert users to the new cache max-age default settings and recommendations. The plugin will still function with earlier versions, but you will not get the benefit of the alerts and Site Health checks. = Latest = Note that the Pantheon Advanced Page Cache 1.3.0 release now prefixes keys on a WordPress Multisite (WPMS) with the blog ID. For users who already have this plugin installed on a WPMS, they will need to click the Clear Cache button on the settings page to generate the prefixed keys. diff --git a/tests/behat/admin-interface.feature b/tests/behat/admin-interface.feature new file mode 100644 index 00000000..044f877f --- /dev/null +++ b/tests/behat/admin-interface.feature @@ -0,0 +1,23 @@ +Feature: Adjust the Default Max Age setting + +Background: + Given I log in as an admin + +Scenario: Change the cache max age + When I go to "/wp-admin/options-general.php?page=pantheon-cache" + And I fill in "pantheon-cache[default_ttl]" with "300" + And I press "Save Changes" + Then I should see "This is a very low value and may not be optimal for your site" in the ".notice" element + And I should see "Consider increasing the cache max-age to at least 604800 seconds (1 week)" in the ".notice" element + +Scenario: Change the cache max age to 5 days + When I go to "/wp-admin/options-general.php?page=pantheon-cache" + And I fill in "pantheon-cache[default_ttl]" with "432000" + And I press "Save Changes" + Then I should see "Consider increasing the cache max-age to at least 604800 seconds (1 week)" in the ".notice" element + +Scenario: Change the cache max age to 1 week + When I go to "/wp-admin/options-general.php?page=pantheon-cache" + And I fill in "pantheon-cache[default_ttl]" with "604800" + And I press "Save Changes" + Then I should see "Settings saved." diff --git a/tests/behat/site-health.feature b/tests/behat/site-health.feature new file mode 100644 index 00000000..a07875be --- /dev/null +++ b/tests/behat/site-health.feature @@ -0,0 +1,28 @@ +Feature: Site Health tests based on Cache Max Age + +Background: + Given I log in as an admin + +Scenario: Site Health should report when Max Age is a low value + When I go to "/wp-admin/options-general.php?page=pantheon-cache" + And I fill in "pantheon-cache[default_ttl]" with "300" + And I press "Save Changes" + And I go to "/wp-admin/site-health.php" + Then I should see "Pantheon GCDN Cache Max-Age" + And I should see "The Pantheon GCDN cache max-age is currently set to 5 mins (300 seconds). We recommend increasing to 1 week (604800 seconds)" + +Scenario: Site Health should report when Max age is less than the recommendation + When I go to "/wp-admin/options-general.php?page=pantheon-cache" + And I fill in "pantheon-cache[default_ttl]" with "432000" + And I press "Save Changes" + And I go to "/wp-admin/site-health.php" + Then I should see "Pantheon GCDN Cache Max-Age" + And I should see "The Pantheon GCDN cache max-age is currently set to 5 days (432000 seconds). We recommend increasing to 1 week (604800 seconds)" + +Scenario: Site Health check should pass when Max Age is the recommneded value + When I go to "/wp-admin/options-general.php?page=pantheon-cache" + And I fill in "pantheon-cache[default_ttl]" with "604800" + And I press "Save Changes" + And I go to "/wp-admin/site-health.php" + Then I should see "Pantheon GCDN Cache Max-Age set to 1 week" + And I should see "The Pantheon cache max-age is currently set to 1 week (604800 seconds). Our recommendation is 1 week (604800 seconds) or more." diff --git a/tests/phpunit/test-admin-interface.php b/tests/phpunit/test-admin-interface.php new file mode 100644 index 00000000..8da3f335 --- /dev/null +++ b/tests/phpunit/test-admin-interface.php @@ -0,0 +1,160 @@ + 300 ] ); + } + + /** + * Tear down tests. + */ + public function tearDown(): void { + parent::tearDown(); + delete_option( 'pantheon-cache' ); + delete_transient( 'papc_max_age_compare' ); + } + + /** + * Test the get_current_max_age and get_default_max_age functions. + */ + public function test_get_max_age() { + $this->assertEquals( 300, get_current_max_age() ); + $this->assertNotEquals( get_default_max_age(), get_current_max_age() ); + $this->assertEquals( WEEK_IN_SECONDS, get_default_max_age() ); + } + + /** + * Test the Site Health tests with very low cache max age. + */ + public function test_site_health_tests_300_seconds() { + $tests = apply_filters( 'site_status_tests', [] ); + + $this->assertContains( 'pantheon_edge_cache', array_keys( $tests['direct'] ) ); + + // Base test with 300 second max-age. + $test_results = test_cache_max_age(); + $this->assertEquals( 'recommended', $test_results['status'] ); + $this->assertEquals( 'red',$test_results['badge']['color'] ); + $this->assertStringContainsString( '300 seconds', $test_results['description'] ); + $this->assertStringContainsString( 'We recommend increasing to 1 week', $test_results['description'] ); + } + + /** + * Test the Site Health tests with 5 day cache max age. + */ + public function test_site_health_tests_5_days() { + // Update the option and rerun. + update_option( 'pantheon-cache', [ 'default_ttl' => 5 * DAY_IN_SECONDS ] ); + $test_results = test_cache_max_age(); + $this->assertEquals( 'recommended', $test_results['status'] ); + $this->assertEquals( 'orange',$test_results['badge']['color'] ); + $this->assertStringContainsString( '5 days', $test_results['description'] ); + $this->assertStringContainsString( 'We recommend increasing to 1 week', $test_results['description'] ); + } + + /** + * Test the Site Health tests with 1 week cache max age. + */ + public function test_site_health_tests_1_week() { + // Update the option to the default and rerun. + update_option( 'pantheon-cache', [ 'default_ttl' => WEEK_IN_SECONDS ] ); + $test_results = test_cache_max_age(); + $this->assertEquals( 'good', $test_results['status'] ); + $this->assertEquals( 'blue',$test_results['badge']['color'] ); + $this->assertStringContainsString( '1 week', $test_results['label'] ); + $this->assertStringContainsString( 'Pantheon GCDN Cache Max-Age set to 1 week', $test_results['label'] ); + } + + /** + * Test the humanized_max_age function. + * + * @dataProvider humanized_max_age_provider + */ + public function test_humanized_max_age( $max_age, $expected ) { + update_option( 'pantheon-cache', [ 'default_ttl' => $max_age ] ); + $this->assertEquals( $expected, humanized_max_age() ); + } + + /** + * Data provider for test_humanized_max_age. + * + * @return array + */ + public function humanized_max_age_provider() { + return [ + [ 300, '5 mins' ], // 300 seconds is humanized to 5 mins. + [ 5 * DAY_IN_SECONDS, '5 days' ], + [ WEEK_IN_SECONDS, '1 week' ], + ]; + } + + /** + * Test the max_age_compare function. + * + * @dataProvider max_age_compare_provider + */ + public function test_max_age_compare( $max_age, $expected ){ + update_option( 'pantheon-cache', [ 'default_ttl' => $max_age ] ); + $this->assertEquals( $expected, max_age_compare() ); + } + + /** + * Data provider for test_max_age_compare. + * + * @return array + */ + public function max_age_compare_provider() { + return [ + [ 300, 10 ], // 300 seconds is bad. It should rank the highest. + [ 5 * DAY_IN_SECONDS, 3 ], // 5 days is better. + [ WEEK_IN_SECONDS, 0 ], // Default recommendation should always return 0. + [ 2 * WEEK_IN_SECONDS, 0 ], // More than the recommendation is also good and should always return 0. + ]; + } + + /** + * Test the delete transient on option update hook. + * + * @dataProvider delete_transient_on_option_update_provider + */ + public function test_delete_transient_on_option_update( $expected, $max_age ) { + update_option( 'pantheon-cache', [ 'default_ttl' => $max_age] ); + max_age_compare(); + $cached_max_age_compare = get_transient( 'papc_max_age_compare' ); + // When the max_age_compare rank is zero, which is the case when it is at least the recommended 1 week, the transient will be deleted. + if ( $max_age === WEEK_IN_SECONDS ) { + $this->assertFalse( $cached_max_age_compare ); + } else { + $this->assertNotFalse( $cached_max_age_compare ); + } + $this->assertEquals( $expected, $cached_max_age_compare ); + } + + /** + * Data provider for delete_transient_on_option_update. + * + * @return array + */ + public function delete_transient_on_option_update_provider() { + return [ + [ 10, 300 ], + [ 3, 5 * DAY_IN_SECONDS ], + [ 0, WEEK_IN_SECONDS ], + ]; + } + +}