diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000..7a7cd872
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,6 @@
+# Our standard eslint config reads .gitignore and .eslintignore to determine what to ignore.
+
+# @todo: Files below ignored as part of Automattic/jetpack/pull/25449
+# They can be removed here as lint errors are addressed.
+/inc/delete-cache-button.js
+/inc/preload-notification.js
diff --git a/.gitattributes b/.gitattributes
index a336949f..33422e5d 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,24 +1,13 @@
-# Automatically generated rules.
-/.git* export-ignore
-
-# Package attributes file.
-# Files not needed to be distributed in the package.
-.gitattributes export-ignore
-.github/ export-ignore
-package.json export-ignore
-
-# Files to include in the mirror repo, but excluded via gitignore
-# Remember to end all directories with `/**` to properly tag every file.
-# /src/js/example.min.js production-include
-
-/jetpack_vendor/** production-include
-/vendor/autoload.php production-include
-/vendor/composer/** production-include
-/vendor/automattic/jetpack-device-detection/** production-include
-
-# Files to exclude from the mirror repo, but included in the monorepo.
-# Remember to end all directories with `/**` to properly tag every file.
-.gitignore production-exclude
-.phpcs.dir.xml production-exclude
-changelog/** production-exclude
-tests/** production-exclude
+# Files not needed in the release zip.
+/.git* export-ignore
+.gitattributes export-ignore
+.github/ export-ignore
+package.json export-ignore
+tests/ export-ignore
+.phan/ export-ignore
+phpunit.*.xml.dist export-ignore
+.phpcs.* export-ignore
+.eslintignore export-ignore
+eslint.config.mjs export-ignore
+changelog/ export-ignore
+.w.org-assets/ export-ignore
diff --git a/.github/workflows/changelogger.yml b/.github/workflows/changelogger.yml
new file mode 100644
index 00000000..f295ce99
--- /dev/null
+++ b/.github/workflows/changelogger.yml
@@ -0,0 +1,27 @@
+name: Changelogger
+
+on:
+ pull_request:
+
+jobs:
+ validate:
+ name: Validate changelog
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.4'
+ tools: composer
+ coverage: none
+ - run: composer install --no-progress
+ - name: Check for changelog entry
+ run: |
+ BASE_SHA=${{ github.event.pull_request.base.sha }}
+ HEAD_SHA=${{ github.event.pull_request.head.sha }}
+ CHANGELOG_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- 'changelog/*')
+ if [ -z "$CHANGELOG_FILES" ]; then
+ echo "::warning::No changelog entry found. If this change is user-facing, please add one with: vendor/bin/changelogger add"
+ fi
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
deleted file mode 100644
index aac439b4..00000000
--- a/.github/workflows/e2e-tests.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-name: End to end tests
-
-on:
- push:
-
-# We use secrets.REPO_DISPATCH_TOKEN for everything here, so no need for permissions.
-permissions: {}
-
-jobs:
- run-tests:
- name: "Trigger e2e tests in Jetpack monorepo"
- runs-on: ubuntu-latest
-
- steps:
- - name: Create a repository dispatch
- uses: peter-evans/repository-dispatch@v4
- with:
- token: ${{ secrets.REPO_DISPATCH_TOKEN }}
- repository: automattic/jetpack
- event-type: e2e tests at ${{github.event_name}} on ${{github.repository}} ${{github.ref_name}} ${{github.ref_type}}
- client-payload: '{"repository": "${{github.repository}}", "ref_name": "${{github.ref_name}}", "ref_type": "${{github.ref_type}}"}'
diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml
new file mode 100644
index 00000000..597b563a
--- /dev/null
+++ b/.github/workflows/linting.yml
@@ -0,0 +1,20 @@
+name: Linting
+
+on:
+ pull_request:
+ push:
+ branches: [trunk]
+
+jobs:
+ phpcs:
+ name: PHPCS
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.4'
+ tools: composer
+ coverage: none
+ - run: composer install --no-progress
+ - run: vendor/bin/phpcs
diff --git a/.github/workflows/php-tests.yml b/.github/workflows/php-tests.yml
new file mode 100644
index 00000000..e867f532
--- /dev/null
+++ b/.github/workflows/php-tests.yml
@@ -0,0 +1,24 @@
+name: PHP Tests
+
+on:
+ pull_request:
+ push:
+ branches: [trunk]
+
+jobs:
+ phpunit:
+ name: PHPUnit (PHP ${{ matrix.php }})
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ['8.2', '8.3', '8.4', '8.5']
+ steps:
+ - uses: actions/checkout@v4
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ tools: composer
+ coverage: none
+ - run: composer install --no-progress
+ - run: composer test-php
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..140fd587
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+vendor/
+node_modules/
diff --git a/.phan/baseline.php b/.phan/baseline.php
new file mode 100644
index 00000000..249d59df
--- /dev/null
+++ b/.phan/baseline.php
@@ -0,0 +1,81 @@
+ [
+ 'advanced-cache.php' => ['PhanPluginSimplifyExpressionBool'],
+ 'inc/delete-cache-button.php' => ['PhanPluginNeverReturnFunction', 'PhanTypeMismatchArgument'],
+ 'ossdl-cdn.php' => ['PhanUndeclaredClassMethod'],
+ 'partials/advanced.php' => ['PhanPluginSimplifyExpressionBool', 'PhanPossiblyUndeclaredGlobalVariable', 'PhanTypeMismatchArgument', 'PhanTypeNonVarPassByRef', 'PhanUndeclaredGlobalVariable'],
+ 'partials/debug.php' => ['PhanTypeNonVarPassByRef', 'PhanUndeclaredGlobalVariable'],
+ 'partials/easy.php' => ['PhanPluginSimplifyExpressionBool', 'PhanTypeArraySuspiciousNull', 'PhanTypeInvalidDimOffset', 'PhanTypeMismatchArgumentInternalReal', 'PhanTypeMismatchArgumentProbablyReal', 'PhanUndeclaredConstant', 'PhanUndeclaredGlobalVariable'],
+ 'partials/lockdown.php' => ['PhanUndeclaredGlobalVariable'],
+ 'partials/preload.php' => ['PhanPluginDuplicateAdjacentStatement', 'PhanPluginSimplifyExpressionBool', 'PhanPossiblyUndeclaredGlobalVariable', 'PhanTypeMismatchDimAssignment', 'PhanUndeclaredGlobalVariable'],
+ 'partials/tracking_parameters.php' => ['PhanUndeclaredGlobalVariable'],
+ 'plugins/domain-mapping.php' => ['PhanUndeclaredFunction'],
+ 'plugins/jetpack.php' => ['PhanPluginSimplifyExpressionBool'],
+ 'plugins/wptouch.php' => ['PhanPluginSimplifyExpressionBool', 'PhanUndeclaredFunction'],
+ 'rest/class.wp-super-cache-rest-get-cache.php' => ['PhanPluginSimplifyExpressionBool'],
+ 'rest/class.wp-super-cache-rest-get-settings.php' => ['PhanPluginSimplifyExpressionBool', 'PhanSuspiciousValueComparison', 'PhanTypeMismatchReturn', 'PhanUndeclaredFunctionInCallable', 'PhanUndeclaredVariable'],
+ 'rest/class.wp-super-cache-rest-get-status.php' => ['PhanPluginSimplifyExpressionBool', 'PhanSuspiciousValueComparison', 'PhanTypeNonVarPassByRef', 'PhanUndeclaredVariable'],
+ 'rest/class.wp-super-cache-rest-test-cache.php' => ['PhanPluginSimplifyExpressionBool', 'PhanTypeConversionFromArray', 'PhanTypePossiblyInvalidDimOffset', 'PhanUndeclaredVariableDim'],
+ 'rest/class.wp-super-cache-rest-update-settings.php' => ['PhanCommentParamWithoutRealParam', 'PhanPluginSimplifyExpressionBool', 'PhanTypeMissingReturn'],
+ 'src/device-detection/class-user-agent-info.php' => ['PhanPluginSimplifyExpressionBool', 'PhanTypeMismatchProperty', 'PhanTypeMismatchReturn'],
+ 'tests/e2e/tools/mu-test-helpers.php' => ['PhanTypeMismatchArgument'],
+ 'wp-cache-base.php' => ['PhanTypeMismatchArgumentNullableInternal'],
+ 'wp-cache-phase1.php' => ['PhanTypeNonVarPassByRef'],
+ 'wp-cache-phase2.php' => ['PhanPluginDuplicateConditionalNullCoalescing', 'PhanPluginSimplifyExpressionBool', 'PhanPluginUnreachableCode', 'PhanPossiblyUndeclaredVariable', 'PhanSuspiciousValueComparison', 'PhanTypeArraySuspiciousNullable', 'PhanTypeMismatchArgument', 'PhanTypeMismatchArgumentInternalProbablyReal', 'PhanTypeMismatchArgumentNullable', 'PhanTypeMismatchArgumentNullableInternal', 'PhanTypeNonVarPassByRef', 'PhanTypePossiblyInvalidDimOffset', 'PhanTypeSuspiciousNonTraversableForeach', 'PhanTypeSuspiciousStringExpression', 'PhanUndeclaredVariableDim'],
+ 'wp-cache.php' => ['PhanPluginDuplicateAdjacentStatement', 'PhanPluginDuplicateExpressionAssignmentOperation', 'PhanPluginNeverReturnFunction', 'PhanPluginSimplifyExpressionBool', 'PhanPossiblyUndeclaredVariable', 'PhanSuspiciousValueComparison', 'PhanTypeArraySuspiciousNullable', 'PhanTypeInvalidDimOffset', 'PhanTypeInvalidLeftOperandOfBitwiseOp', 'PhanTypeInvalidLeftOperandOfNumericOp', 'PhanTypeInvalidRightOperandOfAdd', 'PhanTypeInvalidRightOperandOfBitwiseOp', 'PhanTypeMismatchArgument', 'PhanTypeMismatchArgumentInternal', 'PhanTypeMismatchArgumentInternalProbablyReal', 'PhanTypeMismatchArgumentInternalReal', 'PhanTypeMismatchArgumentNullable', 'PhanTypeMismatchArgumentNullableInternal', 'PhanTypeMismatchArgumentProbablyReal', 'PhanTypeNonVarPassByRef', 'PhanTypePossiblyInvalidDimOffset', 'PhanTypeSuspiciousNonTraversableForeach', 'PhanUndeclaredFunction', 'PhanUndeclaredVariable', 'PhanUndeclaredVariableDim'],
+ ],
+ // 'directory_suppressions' => ['src/directory_name' => ['PhanIssueName1', 'PhanIssueName2']] can be manually added if needed.
+ // (directory_suppressions will currently be ignored by subsequent calls to --save-baseline, but may be preserved in future Phan releases)
+];
diff --git a/.phan/config.php b/.phan/config.php
new file mode 100644
index 00000000..ed85a106
--- /dev/null
+++ b/.phan/config.php
@@ -0,0 +1,49 @@
+ '7.2',
+ 'target_php_version' => '8.5',
+
+ 'backward_compatibility_checks' => false,
+ 'enable_class_alias_support' => false,
+ 'redundant_condition_detection' => true,
+
+ 'directory_list' => [
+ '.',
+ ],
+
+ 'file_list' => [
+ __DIR__ . '/stubs/amp-stubs.php',
+ ],
+
+ 'exclude_analysis_directory_list' => [
+ 'vendor/',
+ 'node_modules/',
+ 'tests/e2e/node_modules/',
+ 'jetpack_vendor/',
+ ],
+
+ 'autoload_internal_extension_signatures' => [],
+
+ 'stubs' => [
+ 'vendor/php-stubs/wordpress-stubs/wordpress-stubs.php',
+ 'vendor/php-stubs/wp-cli-stubs/wp-cli-stubs.php',
+ ],
+
+ 'plugins' => [
+ 'AddNeverReturnTypePlugin',
+ 'DuplicateArrayKeyPlugin',
+ 'DuplicateExpressionPlugin',
+ 'LoopVariableReusePlugin',
+ 'PHPUnitNotDeadCodePlugin',
+ 'PregRegexCheckerPlugin',
+ 'RedundantAssignmentPlugin',
+ 'SimplifyExpressionPlugin',
+ 'UnreachableCodePlugin',
+ 'UseReturnValuePlugin',
+ 'UnusedSuppressionPlugin',
+ ],
+];
diff --git a/.phan/stubs/amp-stubs.php b/.phan/stubs/amp-stubs.php
new file mode 100644
index 00000000..0705f4ab
--- /dev/null
+++ b/.phan/stubs/amp-stubs.php
@@ -0,0 +1,152 @@
+ true,
+ * ) );
+ * ```
+ *
+ * Transitional mode is also implied if you define a `template_dir`:
+ *
+ * ```php
+ * add_theme_support( AMP_Theme_Support::SLUG, array(
+ * 'template_dir' => 'amp',
+ * ) );
+ * ```
+ *
+ * If you want to have AMP-specific templates in addition to serving AMP-first, do:
+ *
+ * ```php
+ * add_theme_support( AMP_Theme_Support::SLUG, array(
+ * 'paired' => false,
+ * 'template_dir' => 'amp',
+ * ) );
+ * ```
+ *
+ * @see AMP_Theme_Support::read_theme_support()
+ * @return boolean Whether this is in AMP 'canonical' mode, that is whether it is AMP-first and there is not a separate (paired) AMP URL.
+ */
+function amp_is_canonical()
+{
+}
+/**
+ * Determines whether the legacy AMP post templates are being used.
+ *
+ * @since 2.0
+ * @return bool
+ */
+function amp_is_legacy()
+{
+}
+/**
+ * Determine whether AMP is available for the current URL.
+ *
+ * @since 2.0
+ *
+ * @return bool Whether there is an AMP version for the provided URL.
+ * @global string $pagenow
+ * @global WP_Query $wp_query
+ */
+function amp_is_available()
+{
+}
+/**
+ * Retrieves the full AMP-specific permalink for the given post ID.
+ *
+ * On a site in Standard mode, this is the same as `get_permalink()`.
+ *
+ * @since 0.1
+ *
+ * @param int $post_id Post ID.
+ * @return string AMP permalink.
+ */
+function amp_get_permalink($post_id)
+{
+}
+/**
+ * Determine whether the current request is for an AMP page.
+ *
+ * This function cannot be called before the parse_query action because it needs to be able
+ * to determine the queried object is able to be served as AMP. If 'amp' theme support is not
+ * present, this function returns true just if the query var is present. If theme support is
+ * present, then it returns true in transitional mode if an AMP template is available and the query
+ * var is present, or else in standard mode if just the template is available.
+ *
+ * @since 2.0 Formerly known as is_amp_endpoint().
+ *
+ * @return bool Whether it is the AMP endpoint.
+ * @global WP_Query $wp_query
+ */
+function amp_is_request()
+{
+}
+/**
+ * Determine whether the current response being served as AMP.
+ *
+ * This function cannot be called before the parse_query action because it needs to be able
+ * to determine the queried object is able to be served as AMP. If 'amp' theme support is not
+ * present, this function returns true just if the query var is present. If theme support is
+ * present, then it returns true in transitional mode if an AMP template is available and the query
+ * var is present, or else in standard mode if just the template is available.
+ *
+ * @since 0.1
+ * @since 2.0 Renamed to AMP-prefixed version, amp_is_request().
+ * @deprecated Use amp_is_request() instead.
+ *
+ * @return bool Whether it is the AMP endpoint.
+ */
+function is_amp_endpoint()
+{
+}
+/**
+ * Class AMP_Options_Manager
+ *
+ * @internal
+ */
+class AMP_Options_Manager
+{
+ /**
+ * Get plugin option.
+ *
+ * @param string $option Plugin option name.
+ * @param bool $default Default value.
+ *
+ * @return mixed Option value.
+ */
+ public static function get_option($option, $default = \false)
+ {
+ }
+}
+/**
+ * Registers a submenu page to access the AMP template editor panel in the Customizer.
+ *
+ * @internal
+ */
+function amp_add_customizer_link()
+{
+}
+// AMP endpoint Verifier
+/**
+ * @phan-return mixed Dummy doc for stub.
+ */
+function ampforwp_is_amp_endpoint()
+{
+}
diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist
new file mode 100644
index 00000000..cf4d6e8c
--- /dev/null
+++ b/.phpcs.xml.dist
@@ -0,0 +1,59 @@
+
+
+ PHPCS ruleset for WP Super Cache
+
+ .
+
+ /vendor/*
+ /node_modules/*
+ /tests/e2e/*
+ /jetpack_vendor/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.w.org-assets/README.md b/.w.org-assets/README.md
new file mode 100644
index 00000000..edae01c6
--- /dev/null
+++ b/.w.org-assets/README.md
@@ -0,0 +1,5 @@
+# WordPress.org Assets
+
+This directory is intended to hold assets meant for w.org plugins SVN.
+
+There is no auto-deployment of these assets, so once you make changes, please ping the plugin's team or ask the Monorepo team to deploy them.
diff --git a/.w.org-assets/banner-1544x500.png b/.w.org-assets/banner-1544x500.png
new file mode 100644
index 00000000..f70052f3
Binary files /dev/null and b/.w.org-assets/banner-1544x500.png differ
diff --git a/.w.org-assets/banner-772x250.png b/.w.org-assets/banner-772x250.png
new file mode 100644
index 00000000..87927d0f
Binary files /dev/null and b/.w.org-assets/banner-772x250.png differ
diff --git a/.w.org-assets/blueprints/blueprint.json b/.w.org-assets/blueprints/blueprint.json
new file mode 100644
index 00000000..b99ab62c
--- /dev/null
+++ b/.w.org-assets/blueprints/blueprint.json
@@ -0,0 +1,19 @@
+{
+ "landingPage": "/wp-admin/options-general.php?page=wpsupercache",
+ "steps": [
+ {
+ "step": "installPlugin",
+ "pluginData": {
+ "resource": "wordpress.org/plugins",
+ "slug": "wp-super-cache"
+ },
+ "options": {
+ "activate": true
+ }
+ },
+ {
+ "step": "runPHP",
+ "code": "=7.4",
+ "phpcompatibility/phpcompatibility-wp": "^2.1",
+ "sirbrillig/phpcs-variable-analysis": "^2.10",
+ "wp-coding-standards/wpcs": "^3.0"
+ },
+ "require-dev": {
+ "automattic/jetpack-changelogger": "^4.2.6",
+ "yoast/phpunit-polyfills": "^1.1.1"
+ },
+ "type": "phpcodesniffer-standard",
+ "extra": {
+ "autotagger": true,
+ "mirror-repo": "Automattic/jetpack-codesniffer",
+ "branch-alias": {
+ "dev-trunk": "4.0.x-dev"
+ },
+ "changelogger": {
+ "link-template": "https://github.com/Automattic/jetpack-codesniffer/compare/v${old}...v${new}"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Automattic\\Jetpack\\Sniffs\\": "Jetpack/Sniffs"
+ },
+ "classmap": [
+ "hacks/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "description": "Jetpack Coding Standards. Based on the WordPress Coding Standards, with some additions.",
+ "keywords": [
+ "codesniffer",
+ "dev",
+ "jetpack",
+ "phpcs",
+ "standards",
+ "testing"
+ ],
+ "support": {
+ "source": "https://github.com/Automattic/jetpack-codesniffer/tree/v4.0.0"
+ },
+ "time": "2024-08-29T19:01:59+00:00"
},
{
"name": "automattic/phpunit-select-config",
- "version": "1.0.4",
+ "version": "v1.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Automattic/phpunit-select-config.git",
+ "reference": "698bb97becda13dda0c5516bce42c3c9852349b2"
+ },
"dist": {
- "type": "path",
- "url": "/tmp/jetpack-build/Automattic/phpunit-select-config",
- "reference": "228436b8cf848e2714976d7ab9b8027cadf77fde"
+ "type": "zip",
+ "url": "https://api.github.com/repos/Automattic/phpunit-select-config/zipball/698bb97becda13dda0c5516bce42c3c9852349b2",
+ "reference": "698bb97becda13dda0c5516bce42c3c9852349b2",
+ "shasum": ""
},
"require": {
"composer-runtime-api": "^2.2.2",
@@ -114,31 +169,15 @@
"type": "library",
"extra": {
"autotagger": true,
+ "mirror-repo": "Automattic/phpunit-select-config",
"branch-alias": {
"dev-trunk": "1.0.x-dev"
},
"changelogger": {
"link-template": "https://github.com/Automattic/phpunit-select-config/compare/v${old}...v${new}"
- },
- "mirror-repo": "Automattic/phpunit-select-config"
- },
- "scripts": {
- "phpunit": [
- "./vendor/bin/phpunit-select-config phpunit.#.xml.dist --colors=always"
- ],
- "test-coverage": [
- "php -dpcov.directory=. ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --coverage-php \"$COVERAGE_DIR/php.cov\""
- ],
- "test-php": [
- "@composer phpunit"
- ],
- "post-install-cmd": [
- "rm -f vendor/bin/phpunit-select-config && ln -s ../../bin/local-phpunit-select-config vendor/bin/phpunit-select-config"
- ],
- "post-update-cmd": [
- "rm -f vendor/bin/phpunit-select-config && ln -s ../../bin/local-phpunit-select-config vendor/bin/phpunit-select-config"
- ]
+ }
},
+ "notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0-or-later"
],
@@ -148,245 +187,1706 @@
"dev",
"phpunit"
],
- "transport-options": {
- "relative": false
- }
+ "support": {
+ "source": "https://github.com/Automattic/phpunit-select-config/tree/v1.0.4"
+ },
+ "time": "2026-03-30T16:01:55+00:00"
},
{
- "name": "myclabs/deep-copy",
- "version": "1.13.4",
+ "name": "automattic/vipwpcs",
+ "version": "3.0.0",
"source": {
"type": "git",
- "url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
+ "url": "https://github.com/Automattic/VIP-Coding-Standards.git",
+ "reference": "1b8960ebff9ea3eb482258a906ece4d1ee1e25fd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
- "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+ "url": "https://api.github.com/repos/Automattic/VIP-Coding-Standards/zipball/1b8960ebff9ea3eb482258a906ece4d1ee1e25fd",
+ "reference": "1b8960ebff9ea3eb482258a906ece4d1ee1e25fd",
"shasum": ""
},
"require": {
- "php": "^7.1 || ^8.0"
+ "php": ">=5.4",
+ "phpcsstandards/phpcsextra": "^1.1.0",
+ "phpcsstandards/phpcsutils": "^1.0.8",
+ "sirbrillig/phpcs-variable-analysis": "^2.11.17",
+ "squizlabs/php_codesniffer": "^3.7.2",
+ "wp-coding-standards/wpcs": "^3.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",
+ "phpcsstandards/phpcsdevtools": "^1.0",
+ "phpunit/phpunit": "^4 || ^5 || ^6 || ^7"
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/Automattic/VIP-Coding-Standards/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress VIP minimum coding conventions",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "static analysis",
+ "wordpress"
+ ],
+ "support": {
+ "issues": "https://github.com/Automattic/VIP-Coding-Standards/issues",
+ "source": "https://github.com/Automattic/VIP-Coding-Standards",
+ "wiki": "https://github.com/Automattic/VIP-Coding-Standards/wiki"
+ },
+ "time": "2023-09-05T11:01:05+00:00"
+ },
+ {
+ "name": "composer/pcre",
+ "version": "3.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/pcre.git",
+ "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
+ "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0"
},
"conflict": {
- "doctrine/collections": "<1.6.8",
- "doctrine/common": "<2.13.3 || >=3 <3.2.2"
+ "phpstan/phpstan": "<1.11.10"
},
"require-dev": {
- "doctrine/collections": "^1.6.8",
- "doctrine/common": "^2.13.3 || ^3.2.2",
- "phpspec/prophecy": "^1.10",
- "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+ "phpstan/phpstan": "^1.12 || ^2",
+ "phpstan/phpstan-strict-rules": "^1 || ^2",
+ "phpunit/phpunit": "^8 || ^9"
},
"type": "library",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ },
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
"autoload": {
- "files": [
- "src/DeepCopy/deep_copy.php"
- ],
"psr-4": {
- "DeepCopy\\": "src/DeepCopy/"
+ "Composer\\Pcre\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
- "description": "Create deep copies (clones) of your objects",
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "PCRE wrapping library that offers type-safe preg_* replacements.",
"keywords": [
- "clone",
- "copy",
- "duplicate",
- "object",
- "object graph"
+ "PCRE",
+ "preg",
+ "regex",
+ "regular expression"
],
"support": {
- "issues": "https://github.com/myclabs/DeepCopy/issues",
- "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
+ "issues": "https://github.com/composer/pcre/issues",
+ "source": "https://github.com/composer/pcre/tree/3.3.2"
},
"funding": [
{
- "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
- "time": "2025-08-01T08:46:24+00:00"
+ "time": "2024-11-12T16:29:46+00:00"
},
{
- "name": "nikic/php-parser",
- "version": "v5.7.0",
+ "name": "composer/semver",
+ "version": "3.4.0",
"source": {
"type": "git",
- "url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
+ "url": "https://github.com/composer/semver.git",
+ "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
- "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+ "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32",
+ "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32",
"shasum": ""
},
"require": {
- "ext-ctype": "*",
- "ext-json": "*",
- "ext-tokenizer": "*",
- "php": ">=7.4"
+ "php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
- "ircmaxell/php-yacc": "^0.0.7",
- "phpunit/phpunit": "^9.0"
+ "phpstan/phpstan": "^1.4",
+ "symfony/phpunit-bridge": "^4.2 || ^5"
},
- "bin": [
- "bin/php-parse"
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Semver\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nils Adermann",
+ "email": "naderman@naderman.de",
+ "homepage": "http://www.naderman.de"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ },
+ {
+ "name": "Rob Bast",
+ "email": "rob.bast@gmail.com",
+ "homepage": "http://robbast.nl"
+ }
+ ],
+ "description": "Semver library that offers utilities, version constraint parsing and validation.",
+ "keywords": [
+ "semantic",
+ "semver",
+ "validation",
+ "versioning"
+ ],
+ "support": {
+ "irc": "ircs://irc.libera.chat:6697/composer",
+ "issues": "https://github.com/composer/semver/issues",
+ "source": "https://github.com/composer/semver/tree/3.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
],
+ "time": "2023-08-31T09:50:34+00:00"
+ },
+ {
+ "name": "composer/spdx-licenses",
+ "version": "1.5.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/spdx-licenses.git",
+ "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f",
+ "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.2 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.11",
+ "symfony/phpunit-bridge": "^3 || ^7"
+ },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.x-dev"
+ "dev-main": "1.x-dev"
}
},
"autoload": {
"psr-4": {
- "PhpParser\\": "lib/PhpParser"
+ "Composer\\Spdx\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "BSD-3-Clause"
+ "MIT"
],
"authors": [
{
- "name": "Nikita Popov"
+ "name": "Nils Adermann",
+ "email": "naderman@naderman.de",
+ "homepage": "http://www.naderman.de"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ },
+ {
+ "name": "Rob Bast",
+ "email": "rob.bast@gmail.com",
+ "homepage": "http://robbast.nl"
}
],
- "description": "A PHP parser written in PHP",
+ "description": "SPDX licenses list and validation library.",
"keywords": [
- "parser",
- "php"
+ "license",
+ "spdx",
+ "validator"
],
"support": {
- "issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
+ "irc": "ircs://irc.libera.chat:6697/composer",
+ "issues": "https://github.com/composer/spdx-licenses/issues",
+ "source": "https://github.com/composer/spdx-licenses/tree/1.5.9"
},
- "time": "2025-12-06T11:56:16+00:00"
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-05-12T21:07:07+00:00"
},
{
- "name": "phar-io/manifest",
- "version": "2.0.4",
+ "name": "composer/xdebug-handler",
+ "version": "3.0.5",
"source": {
"type": "git",
- "url": "https://github.com/phar-io/manifest.git",
- "reference": "54750ef60c58e43759730615a392c31c80e23176"
+ "url": "https://github.com/composer/xdebug-handler.git",
+ "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef",
+ "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef",
+ "shasum": ""
+ },
+ "require": {
+ "composer/pcre": "^1 || ^2 || ^3",
+ "php": "^7.2.5 || ^8.0",
+ "psr/log": "^1 || ^2 || ^3"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-strict-rules": "^1.1",
+ "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Composer\\XdebugHandler\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "description": "Restarts a process without Xdebug.",
+ "keywords": [
+ "Xdebug",
+ "performance"
+ ],
+ "support": {
+ "irc": "ircs://irc.libera.chat:6697/composer",
+ "issues": "https://github.com/composer/xdebug-handler/issues",
+ "source": "https://github.com/composer/xdebug-handler/tree/3.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-05-06T16:37:16+00:00"
+ },
+ {
+ "name": "dealerdirect/phpcodesniffer-composer-installer",
+ "version": "v1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/composer-installer.git",
+ "reference": "845eb62303d2ca9b289ef216356568ccc075ffd1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/845eb62303d2ca9b289ef216356568ccc075ffd1",
+ "reference": "845eb62303d2ca9b289ef216356568ccc075ffd1",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^2.2",
+ "php": ">=5.4",
+ "squizlabs/php_codesniffer": "^3.1.0 || ^4.0"
+ },
+ "require-dev": {
+ "composer/composer": "^2.2",
+ "ext-json": "*",
+ "ext-zip": "*",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev",
+ "yoast/phpunit-polyfills": "^1.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Franck Nijhof",
+ "email": "opensource@frenck.dev",
+ "homepage": "https://frenck.dev",
+ "role": "Open source developer"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
+ "keywords": [
+ "PHPCodeSniffer",
+ "PHP_CodeSniffer",
+ "code quality",
+ "codesniffer",
+ "composer",
+ "installer",
+ "phpcbf",
+ "phpcs",
+ "plugin",
+ "qa",
+ "quality",
+ "standard",
+ "standards",
+ "style guide",
+ "stylecheck",
+ "tests"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCSStandards/composer-installer/issues",
+ "security": "https://github.com/PHPCSStandards/composer-installer/security/policy",
+ "source": "https://github.com/PHPCSStandards/composer-installer"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCSStandards",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-11-11T04:32:07+00:00"
+ },
+ {
+ "name": "doctrine/deprecations",
+ "version": "1.1.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/deprecations.git",
+ "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca",
+ "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<=7.5 || >=14"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^9 || ^12 || ^14",
+ "phpstan/phpstan": "1.4.10 || 2.1.30",
+ "phpstan/phpstan-phpunit": "^1.0 || ^2",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0",
+ "psr/log": "^1 || ^2 || ^3"
+ },
+ "suggest": {
+ "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Deprecations\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
+ "homepage": "https://www.doctrine-project.org/",
+ "support": {
+ "issues": "https://github.com/doctrine/deprecations/issues",
+ "source": "https://github.com/doctrine/deprecations/tree/1.1.6"
+ },
+ "time": "2026-02-07T07:09:04+00:00"
+ },
+ {
+ "name": "felixfbecker/advanced-json-rpc",
+ "version": "v3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git",
+ "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447",
+ "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447",
+ "shasum": ""
+ },
+ "require": {
+ "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
+ "php": "^7.1 || ^8.0",
+ "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "AdvancedJsonRpc\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "ISC"
+ ],
+ "authors": [
+ {
+ "name": "Felix Becker",
+ "email": "felix.b@outlook.com"
+ }
+ ],
+ "description": "A more advanced JSONRPC implementation",
+ "support": {
+ "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues",
+ "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1"
+ },
+ "time": "2021-06-11T22:34:44+00:00"
+ },
+ {
+ "name": "mediawiki/mediawiki-codesniffer",
+ "version": "v43.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wikimedia/mediawiki-tools-codesniffer.git",
+ "reference": "c559bc02e87b0a969b6ed7380d7fa1d02738158b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wikimedia/mediawiki-tools-codesniffer/zipball/c559bc02e87b0a969b6ed7380d7fa1d02738158b",
+ "reference": "c559bc02e87b0a969b6ed7380d7fa1d02738158b",
+ "shasum": ""
+ },
+ "require": {
+ "composer/semver": "3.3.2 || 3.4.0",
+ "composer/spdx-licenses": "~1.5.2",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "php": ">=7.4.0",
+ "phpcsstandards/phpcsextra": "1.1.2",
+ "squizlabs/php_codesniffer": "3.8.1",
+ "symfony/polyfill-php80": "^1.26.0"
+ },
+ "require-dev": {
+ "mediawiki/mediawiki-phan-config": "0.12.1",
+ "mediawiki/minus-x": "1.1.1",
+ "php-parallel-lint/php-console-highlighter": "1.0.0",
+ "php-parallel-lint/php-parallel-lint": "1.3.2",
+ "phpunit/phpunit": "9.5.28"
+ },
+ "type": "phpcodesniffer-standard",
+ "autoload": {
+ "psr-4": {
+ "MediaWiki\\Sniffs\\": "MediaWiki/Sniffs/",
+ "MediaWiki\\Sniffs\\Tests\\": "MediaWiki/Tests/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "description": "MediaWiki CodeSniffer Standards",
+ "homepage": "https://www.mediawiki.org/wiki/Manual:Coding_conventions/PHP",
+ "keywords": [
+ "codesniffer",
+ "mediawiki"
+ ],
+ "support": {
+ "source": "https://github.com/wikimedia/mediawiki-tools-codesniffer/tree/v43.0.0"
+ },
+ "time": "2024-01-29T16:06:37+00:00"
+ },
+ {
+ "name": "microsoft/tolerant-php-parser",
+ "version": "v0.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/microsoft/tolerant-php-parser.git",
+ "reference": "3eccfd273323aaf69513e2f1c888393f5947804b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/microsoft/tolerant-php-parser/zipball/3eccfd273323aaf69513e2f1c888393f5947804b",
+ "reference": "3eccfd273323aaf69513e2f1c888393f5947804b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.15"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Microsoft\\PhpParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Rob Lourens",
+ "email": "roblou@microsoft.com"
+ }
+ ],
+ "description": "Tolerant PHP-to-AST parser designed for IDE usage scenarios",
+ "support": {
+ "issues": "https://github.com/microsoft/tolerant-php-parser/issues",
+ "source": "https://github.com/microsoft/tolerant-php-parser/tree/v0.1.2"
+ },
+ "time": "2022-10-05T17:30:19+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.13.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/collections": "<1.6.8",
+ "doctrine/common": "<2.13.3 || >=3 <3.2.2"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-01T08:46:24+00:00"
+ },
+ {
+ "name": "netresearch/jsonmapper",
+ "version": "v4.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cweiske/jsonmapper.git",
+ "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5",
+ "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-spl": "*",
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0",
+ "squizlabs/php_codesniffer": "~3.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "JsonMapper": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "OSL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "Christian Weiske",
+ "email": "cweiske@cweiske.de",
+ "homepage": "http://github.com/cweiske/jsonmapper/",
+ "role": "Developer"
+ }
+ ],
+ "description": "Map nested JSON structures onto PHP classes",
+ "support": {
+ "email": "cweiske@cweiske.de",
+ "issues": "https://github.com/cweiske/jsonmapper/issues",
+ "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0"
+ },
+ "time": "2024-09-08T10:13:13+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v5.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
+ },
+ "time": "2025-12-06T11:56:16+00:00"
+ },
+ {
+ "name": "phan/phan",
+ "version": "5.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phan/phan.git",
+ "reference": "25d7e8d185a4c78e7423c188fb28bba5dbde20c1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phan/phan/zipball/25d7e8d185a4c78e7423c188fb28bba5dbde20c1",
+ "reference": "25d7e8d185a4c78e7423c188fb28bba5dbde20c1",
+ "shasum": ""
+ },
+ "require": {
+ "composer/semver": "^1.4|^2.0|^3.0",
+ "composer/xdebug-handler": "^2.0|^3.0",
+ "ext-filter": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "felixfbecker/advanced-json-rpc": "^3.0.4",
+ "microsoft/tolerant-php-parser": "0.1.2",
+ "netresearch/jsonmapper": "^1.6.0|^2.0|^3.0|^4.0|^5.0",
+ "php": "^7.2.0|^8.0.0",
+ "sabre/event": "^5.1.3",
+ "symfony/console": "^3.2|^4.0|^5.0|^6.0|^7.0",
+ "symfony/polyfill-mbstring": "^1.11.0",
+ "symfony/polyfill-php80": "^1.20.0",
+ "tysonandre/var_representation_polyfill": "^0.0.2|^0.1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.0"
+ },
+ "suggest": {
+ "ext-ast": "Needed for parsing ASTs (unless --use-fallback-parser is used). 1.0.1+ is needed, 1.0.16+ is recommended.",
+ "ext-iconv": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8",
+ "ext-igbinary": "Improves performance of polyfill when ext-ast is unavailable",
+ "ext-mbstring": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8",
+ "ext-tokenizer": "Needed for fallback/polyfill parser support and file/line-based suppressions.",
+ "ext-var_representation": "Suggested for converting values to strings in issue messages"
+ },
+ "bin": [
+ "phan",
+ "phan_client",
+ "tocheckstyle"
+ ],
+ "type": "project",
+ "autoload": {
+ "psr-4": {
+ "Phan\\": "src/Phan"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tyson Andre"
+ },
+ {
+ "name": "Rasmus Lerdorf"
+ },
+ {
+ "name": "Andrew S. Morrison"
+ }
+ ],
+ "description": "A static analyzer for PHP",
+ "keywords": [
+ "analyzer",
+ "php",
+ "static",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phan/phan/issues",
+ "source": "https://github.com/phan/phan/tree/5.5.2"
+ },
+ "time": "2025-10-04T18:04:38+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
+ },
+ "dist": {
+ "type": "zip",
+ "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",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "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.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
+ },
+ {
+ "name": "php-stubs/wordpress-stubs",
+ "version": "v6.9.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-stubs/wordpress-stubs.git",
+ "reference": "f12220f303e0d7c0844c0e5e957b0c3cee48d2f7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/f12220f303e0d7c0844c0e5e957b0c3cee48d2f7",
+ "reference": "f12220f303e0d7c0844c0e5e957b0c3cee48d2f7",
+ "shasum": ""
+ },
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "5.6.1"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "nikic/php-parser": "^5.5",
+ "php": "^7.4 || ^8.0",
+ "php-stubs/generator": "^0.8.3",
+ "phpdocumentor/reflection-docblock": "^6.0",
+ "phpstan/phpstan": "^2.1",
+ "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"
+ },
+ "suggest": {
+ "paragonie/sodium_compat": "Pure PHP implementation of libsodium",
+ "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan"
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "WordPress function and class declaration stubs for static analysis.",
+ "homepage": "https://github.com/php-stubs/wordpress-stubs",
+ "keywords": [
+ "PHPStan",
+ "static analysis",
+ "wordpress"
+ ],
+ "support": {
+ "issues": "https://github.com/php-stubs/wordpress-stubs/issues",
+ "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.9.1"
+ },
+ "time": "2026-02-03T19:29:21+00:00"
+ },
+ {
+ "name": "php-stubs/wp-cli-stubs",
+ "version": "v2.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-stubs/wp-cli-stubs.git",
+ "reference": "af16401e299a3fd2229bd0fa9a037638a4174a9d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-stubs/wp-cli-stubs/zipball/af16401e299a3fd2229bd0fa9a037638a4174a9d",
+ "reference": "af16401e299a3fd2229bd0fa9a037638a4174a9d",
+ "shasum": ""
+ },
+ "require": {
+ "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0"
+ },
+ "require-dev": {
+ "php": "~7.3 || ~8.0",
+ "php-stubs/generator": "^0.8.0"
+ },
+ "suggest": {
+ "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+ "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan"
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "WP-CLI function and class declaration stubs for static analysis.",
+ "homepage": "https://github.com/php-stubs/wp-cli-stubs",
+ "keywords": [
+ "PHPStan",
+ "static analysis",
+ "wordpress",
+ "wp-cli"
+ ],
+ "support": {
+ "issues": "https://github.com/php-stubs/wp-cli-stubs/issues",
+ "source": "https://github.com/php-stubs/wp-cli-stubs/tree/v2.12.0"
+ },
+ "time": "2025-06-10T09:58:05+00:00"
+ },
+ {
+ "name": "phpcompatibility/php-compatibility",
+ "version": "9.3.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
+ },
+ "conflict": {
+ "squizlabs/php_codesniffer": "2.6.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "homepage": "https://github.com/wimg",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
+ "role": "lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
+ }
+ ],
+ "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
+ "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
+ "keywords": [
+ "compatibility",
+ "phpcs",
+ "standards"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
+ "source": "https://github.com/PHPCompatibility/PHPCompatibility"
+ },
+ "time": "2019-12-27T09:44:58+00:00"
+ },
+ {
+ "name": "phpcompatibility/phpcompatibility-paragonie",
+ "version": "1.3.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
+ "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf",
+ "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf",
+ "shasum": ""
+ },
+ "require": {
+ "phpcompatibility/php-compatibility": "^9.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "paragonie/random_compat": "dev-master",
+ "paragonie/sodium_compat": "dev-master"
+ },
+ "suggest": {
+ "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",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "lead"
+ }
+ ],
+ "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
+ "homepage": "http://phpcompatibility.com/",
+ "keywords": [
+ "compatibility",
+ "paragonie",
+ "phpcs",
+ "polyfill",
+ "standards",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
+ "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy",
+ "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCompatibility",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcompatibility",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-09-19T17:43:28+00:00"
+ },
+ {
+ "name": "phpcompatibility/phpcompatibility-wp",
+ "version": "2.1.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
+ "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/7c8d18b4d90dac9e86b0869a608fa09158e168fa",
+ "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa",
+ "shasum": ""
+ },
+ "require": {
+ "phpcompatibility/php-compatibility": "^9.0",
+ "phpcompatibility/phpcompatibility-paragonie": "^1.0",
+ "squizlabs/php_codesniffer": "^3.3"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0"
+ },
+ "suggest": {
+ "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",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "lead"
+ }
+ ],
+ "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.",
+ "homepage": "http://phpcompatibility.com/",
+ "keywords": [
+ "compatibility",
+ "phpcs",
+ "standards",
+ "static analysis",
+ "wordpress"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues",
+ "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy",
+ "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCompatibility",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcompatibility",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-10-18T00:05:59+00:00"
+ },
+ {
+ "name": "phpcsstandards/phpcsextra",
+ "version": "1.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/PHPCSExtra.git",
+ "reference": "746c3190ba8eb2f212087c947ba75f4f5b9a58d5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/746c3190ba8eb2f212087c947ba75f4f5b9a58d5",
+ "reference": "746c3190ba8eb2f212087c947ba75f4f5b9a58d5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4",
+ "phpcsstandards/phpcsutils": "^1.0.8",
+ "squizlabs/php_codesniffer": "^3.7.1"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-console-highlighter": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "phpcsstandards/phpcsdevcs": "^1.1.6",
+ "phpcsstandards/phpcsdevtools": "^1.2.1",
+ "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "type": "phpcodesniffer-standard",
+ "extra": {
+ "branch-alias": {
+ "dev-stable": "1.x-dev",
+ "dev-develop": "1.x-dev"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
+ "role": "lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors"
+ }
+ ],
+ "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.",
+ "keywords": [
+ "PHP_CodeSniffer",
+ "phpcbf",
+ "phpcodesniffer-standard",
+ "phpcs",
+ "standards",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues",
+ "source": "https://github.com/PHPCSStandards/PHPCSExtra"
+ },
+ "time": "2023-09-20T22:06:18+00:00"
+ },
+ {
+ "name": "phpcsstandards/phpcsutils",
+ "version": "1.0.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/PHPCSUtils.git",
+ "reference": "908247bc65010c7b7541a9551e002db12e9dae70"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/908247bc65010c7b7541a9551e002db12e9dae70",
+ "reference": "908247bc65010c7b7541a9551e002db12e9dae70",
+ "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"
+ },
+ "require-dev": {
+ "ext-filter": "*",
+ "php-parallel-lint/php-console-highlighter": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "phpcsstandards/phpcsdevcs": "^1.1.6",
+ "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0"
+ },
+ "type": "phpcodesniffer-standard",
+ "extra": {
+ "branch-alias": {
+ "dev-stable": "1.x-dev",
+ "dev-develop": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "PHPCSUtils/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
+ "role": "lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors"
+ }
+ ],
+ "description": "A suite of utility functions for use with PHP_CodeSniffer",
+ "homepage": "https://phpcsutils.com/",
+ "keywords": [
+ "PHP_CodeSniffer",
+ "phpcbf",
+ "phpcodesniffer-standard",
+ "phpcs",
+ "phpcs3",
+ "standards",
+ "static analysis",
+ "tokens",
+ "utility"
+ ],
+ "support": {
+ "docs": "https://phpcsutils.com/",
+ "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues",
+ "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy",
+ "source": "https://github.com/PHPCSStandards/PHPCSUtils"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCSStandards",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2023-12-08T14:50:00+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-2.x": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
+ },
+ "time": "2020-06-27T09:03:43+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "5.6.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "31a105931bc8ffa3a123383829772e832fd8d903"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/31a105931bc8ffa3a123383829772e832fd8d903",
+ "reference": "31a105931bc8ffa3a123383829772e832fd8d903",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/deprecations": "^1.1",
+ "ext-filter": "*",
+ "php": "^7.4 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.2",
+ "phpdocumentor/type-resolver": "^1.7",
+ "phpstan/phpdoc-parser": "^1.7|^2.0",
+ "webmozart/assert": "^1.9.1 || ^2"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.3.5 || ~1.6.0",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1.8",
+ "phpstan/phpstan-mockery": "^1.1",
+ "phpstan/phpstan-webmozart-assert": "^1.2",
+ "phpunit/phpunit": "^9.5",
+ "psalm/phar": "^5.26"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ },
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.7"
+ },
+ "time": "2026-03-18T20:47:46+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "1.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
- "reference": "54750ef60c58e43759730615a392c31c80e23176",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195",
+ "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195",
"shasum": ""
},
"require": {
- "ext-dom": "*",
- "ext-libxml": "*",
- "ext-phar": "*",
- "ext-xmlwriter": "*",
- "phar-io/version": "^3.0.1",
- "php": "^7.2 || ^8.0"
+ "doctrine/deprecations": "^1.0",
+ "php": "^7.3 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.0",
+ "phpstan/phpdoc-parser": "^1.18|^2.0"
+ },
+ "require-dev": {
+ "ext-tokenizer": "*",
+ "phpbench/phpbench": "^1.2",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1.8",
+ "phpstan/phpstan-phpunit": "^1.1",
+ "phpunit/phpunit": "^9.5",
+ "rector/rector": "^0.13.9",
+ "vimeo/psalm": "^4.25"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0.x-dev"
+ "dev-1.x": "1.x-dev"
}
},
"autoload": {
- "classmap": [
- "src/"
- ]
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "BSD-3-Clause"
+ "MIT"
],
"authors": [
{
- "name": "Arne Blankerts",
- "email": "arne@blankerts.de",
- "role": "Developer"
- },
- {
- "name": "Sebastian Heuer",
- "email": "sebastian@phpeople.de",
- "role": "Developer"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "Developer"
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
}
],
- "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
- "issues": "https://github.com/phar-io/manifest/issues",
- "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+ "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
+ "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0"
},
- "funding": [
- {
- "url": "https://github.com/theseer",
- "type": "github"
- }
- ],
- "time": "2024-03-03T12:33:53+00:00"
+ "time": "2025-11-21T15:09:14+00:00"
},
{
- "name": "phar-io/version",
- "version": "3.2.1",
+ "name": "phpstan/phpdoc-parser",
+ "version": "2.3.2",
"source": {
"type": "git",
- "url": "https://github.com/phar-io/version.git",
- "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
- "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a",
+ "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a",
"shasum": ""
},
"require": {
- "php": "^7.2 || ^8.0"
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/annotations": "^2.0",
+ "nikic/php-parser": "^5.3.0",
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^9.6",
+ "symfony/process": "^5.2"
},
"type": "library",
"autoload": {
- "classmap": [
- "src/"
- ]
+ "psr-4": {
+ "PHPStan\\PhpDocParser\\": [
+ "src/"
+ ]
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Arne Blankerts",
- "email": "arne@blankerts.de",
- "role": "Developer"
- },
- {
- "name": "Sebastian Heuer",
- "email": "sebastian@phpeople.de",
- "role": "Developer"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "Developer"
- }
+ "MIT"
],
- "description": "Library for handling version information and constraints",
+ "description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
- "issues": "https://github.com/phar-io/version/issues",
- "source": "https://github.com/phar-io/version/tree/3.2.1"
+ "issues": "https://github.com/phpstan/phpdoc-parser/issues",
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2"
},
- "time": "2022-02-21T01:04:05+00:00"
+ "time": "2026-01-25T14:56:51+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -736,16 +2236,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "12.5.14",
+ "version": "12.5.16",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0"
+ "reference": "b2429f58ae75cae980b5bb9873abe4de6aac8b58"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/47283cfd98d553edcb1353591f4e255dc1bb61f0",
- "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b2429f58ae75cae980b5bb9873abe4de6aac8b58",
+ "reference": "b2429f58ae75cae980b5bb9873abe4de6aac8b58",
"shasum": ""
},
"require": {
@@ -767,7 +2267,7 @@
"sebastian/cli-parser": "^4.2.0",
"sebastian/comparator": "^7.1.4",
"sebastian/diff": "^7.0.0",
- "sebastian/environment": "^8.0.3",
+ "sebastian/environment": "^8.0.4",
"sebastian/exporter": "^7.0.2",
"sebastian/global-state": "^8.0.2",
"sebastian/object-enumerator": "^7.0.0",
@@ -814,31 +2314,15 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.14"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.16"
},
"funding": [
{
- "url": "https://phpunit.de/sponsors.html",
- "type": "custom"
- },
- {
- "url": "https://github.com/sebastianbergmann",
- "type": "github"
- },
- {
- "url": "https://liberapay.com/sebastianbergmann",
- "type": "liberapay"
- },
- {
- "url": "https://thanks.dev/u/gh/sebastianbergmann",
- "type": "thanks_dev"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
- "type": "tidelift"
+ "url": "https://phpunit.de/sponsoring.html",
+ "type": "other"
}
],
- "time": "2026-02-18T12:38:40+00:00"
+ "time": "2026-04-03T05:26:42+00:00"
},
{
"name": "psr/container",
@@ -893,6 +2377,122 @@
},
"time": "2021-11-05T16:47:00+00:00"
},
+ {
+ "name": "psr/log",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.2"
+ },
+ "time": "2024-09-11T13:17:53+00:00"
+ },
+ {
+ "name": "sabre/event",
+ "version": "5.1.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sabre-io/event.git",
+ "reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sabre-io/event/zipball/86d57e305c272898ba3c28e9bd3d65d5464587c2",
+ "reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.17.1||^3.63",
+ "phpstan/phpstan": "^0.12",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "lib/coroutine.php",
+ "lib/Loop/functions.php",
+ "lib/Promise/functions.php"
+ ],
+ "psr-4": {
+ "Sabre\\Event\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Evert Pot",
+ "email": "me@evertpot.com",
+ "homepage": "http://evertpot.com/",
+ "role": "Developer"
+ }
+ ],
+ "description": "sabre/event is a library for lightweight event-based programming",
+ "homepage": "http://sabre.io/event/",
+ "keywords": [
+ "EventEmitter",
+ "async",
+ "coroutine",
+ "eventloop",
+ "events",
+ "hooks",
+ "plugin",
+ "promise",
+ "reactor",
+ "signal"
+ ],
+ "support": {
+ "forum": "https://groups.google.com/group/sabredav-discuss",
+ "issues": "https://github.com/sabre-io/event/issues",
+ "source": "https://github.com/fruux/sabre-event"
+ },
+ "time": "2024-08-27T11:23:05+00:00"
+ },
{
"name": "sebastian/cli-parser",
"version": "4.2.0",
@@ -1181,16 +2781,16 @@
},
{
"name": "sebastian/environment",
- "version": "8.0.3",
+ "version": "8.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68"
+ "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68",
- "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/7b8842c2d8e85d0c3a5831236bf5869af6ab2a11",
+ "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11",
"shasum": ""
},
"require": {
@@ -1233,7 +2833,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"security": "https://github.com/sebastianbergmann/environment/security/policy",
- "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3"
+ "source": "https://github.com/sebastianbergmann/environment/tree/8.0.4"
},
"funding": [
{
@@ -1253,7 +2853,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-12T14:11:56+00:00"
+ "time": "2026-03-15T07:05:40+00:00"
},
{
"name": "sebastian/exporter",
@@ -1716,79 +3316,215 @@
"security": "https://github.com/sebastianbergmann/type/security/policy",
"source": "https://github.com/sebastianbergmann/type/tree/6.0.3"
},
- "funding": [
- {
- "url": "https://github.com/sebastianbergmann",
- "type": "github"
- },
- {
- "url": "https://liberapay.com/sebastianbergmann",
- "type": "liberapay"
- },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/type",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-09T06:57:12+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c",
+ "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "security": "https://github.com/sebastianbergmann/version/security/policy",
+ "source": "https://github.com/sebastianbergmann/version/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T05:00:38+00:00"
+ },
+ {
+ "name": "sirbrillig/phpcs-variable-analysis",
+ "version": "v2.13.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git",
+ "reference": "a15e970b8a0bf64cfa5e86d941f5e6b08855f369"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/a15e970b8a0bf64cfa5e86d941f5e6b08855f369",
+ "reference": "a15e970b8a0bf64cfa5e86d941f5e6b08855f369",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "squizlabs/php_codesniffer": "^3.5.7 || ^4.0.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0",
+ "phpstan/phpstan": "^1.7 || ^2.0",
+ "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0 || ^10.5.32 || ^11.3.3",
+ "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "type": "phpcodesniffer-standard",
+ "autoload": {
+ "psr-4": {
+ "VariableAnalysis\\": "VariableAnalysis/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
{
- "url": "https://thanks.dev/u/gh/sebastianbergmann",
- "type": "thanks_dev"
+ "name": "Sam Graham",
+ "email": "php-codesniffer-variableanalysis@illusori.co.uk"
},
{
- "url": "https://tidelift.com/funding/github/packagist/sebastian/type",
- "type": "tidelift"
+ "name": "Payton Swick",
+ "email": "payton@foolord.com"
}
],
- "time": "2025-08-09T06:57:12+00:00"
+ "description": "A PHPCS sniff to detect problems with variables.",
+ "keywords": [
+ "phpcs",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/sirbrillig/phpcs-variable-analysis/issues",
+ "source": "https://github.com/sirbrillig/phpcs-variable-analysis",
+ "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki"
+ },
+ "time": "2025-09-30T22:22:48+00:00"
},
{
- "name": "sebastian/version",
- "version": "6.0.0",
+ "name": "squizlabs/php_codesniffer",
+ "version": "3.8.1",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/version.git",
- "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c"
+ "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
+ "reference": "14f5fff1e64118595db5408e946f3a22c75807f7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c",
- "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/14f5fff1e64118595db5408e946f3a22c75807f7",
+ "reference": "14f5fff1e64118595db5408e946f3a22c75807f7",
"shasum": ""
},
"require": {
- "php": ">=8.3"
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
},
+ "bin": [
+ "bin/phpcbf",
+ "bin/phpcs"
+ ],
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "6.0-dev"
+ "dev-master": "3.x-dev"
}
},
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
+ "name": "Greg Sherwood",
+ "role": "Former lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "Current lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors"
}
],
- "description": "Library that helps with managing the version number of Git-hosted PHP projects",
- "homepage": "https://github.com/sebastianbergmann/version",
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "static analysis"
+ ],
"support": {
- "issues": "https://github.com/sebastianbergmann/version/issues",
- "security": "https://github.com/sebastianbergmann/version/security/policy",
- "source": "https://github.com/sebastianbergmann/version/tree/6.0.0"
+ "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues",
+ "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy",
+ "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+ "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki"
},
"funding": [
{
- "url": "https://github.com/sebastianbergmann",
+ "url": "https://github.com/PHPCSStandards",
"type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
}
],
- "time": "2025-02-07T05:00:38+00:00"
+ "time": "2024-01-11T20:47:48+00:00"
},
{
"name": "staabm/side-effects-detector",
@@ -1844,16 +3580,16 @@
},
{
"name": "symfony/console",
- "version": "v7.4.7",
+ "version": "v7.4.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d"
+ "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d",
- "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d",
+ "url": "https://api.github.com/repos/symfony/console/zipball/1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707",
+ "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707",
"shasum": ""
},
"require": {
@@ -1918,7 +3654,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.4.7"
+ "source": "https://github.com/symfony/console/tree/v7.4.8"
},
"funding": [
{
@@ -1938,7 +3674,7 @@
"type": "tidelift"
}
],
- "time": "2026-03-06T14:06:20+00:00"
+ "time": "2026-03-30T13:54:39+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -2342,18 +4078,102 @@
],
"time": "2024-12-23T08:48:59+00:00"
},
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-01-02T08:10:11+00:00"
+ },
{
"name": "symfony/process",
- "version": "v7.4.5",
+ "version": "v7.4.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "608476f4604102976d687c483ac63a79ba18cc97"
+ "reference": "60f19cd3badc8de688421e21e4305eba50f8089a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97",
- "reference": "608476f4604102976d687c483ac63a79ba18cc97",
+ "url": "https://api.github.com/repos/symfony/process/zipball/60f19cd3badc8de688421e21e4305eba50f8089a",
+ "reference": "60f19cd3badc8de688421e21e4305eba50f8089a",
"shasum": ""
},
"require": {
@@ -2385,7 +4205,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v7.4.5"
+ "source": "https://github.com/symfony/process/tree/v7.4.8"
},
"funding": [
{
@@ -2405,7 +4225,7 @@
"type": "tidelift"
}
],
- "time": "2026-01-26T15:07:59+00:00"
+ "time": "2026-03-24T13:12:05+00:00"
},
{
"name": "symfony/service-contracts",
@@ -2496,16 +4316,16 @@
},
{
"name": "symfony/string",
- "version": "v8.0.6",
+ "version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4"
+ "reference": "ae9488f874d7603f9d2dfbf120203882b645d963"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
- "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
+ "url": "https://api.github.com/repos/symfony/string/zipball/ae9488f874d7603f9d2dfbf120203882b645d963",
+ "reference": "ae9488f874d7603f9d2dfbf120203882b645d963",
"shasum": ""
},
"require": {
@@ -2562,7 +4382,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v8.0.6"
+ "source": "https://github.com/symfony/string/tree/v8.0.8"
},
"funding": [
{
@@ -2582,7 +4402,7 @@
"type": "tidelift"
}
],
- "time": "2026-02-09T10:14:57+00:00"
+ "time": "2026-03-30T15:14:47+00:00"
},
{
"name": "theseer/tokenizer",
@@ -2634,6 +4454,196 @@
],
"time": "2025-12-08T11:19:18+00:00"
},
+ {
+ "name": "tysonandre/var_representation_polyfill",
+ "version": "0.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/TysonAndre/var_representation_polyfill.git",
+ "reference": "e9116c2c352bb0835ca428b442dde7767c11ad32"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/TysonAndre/var_representation_polyfill/zipball/e9116c2c352bb0835ca428b442dde7767c11ad32",
+ "reference": "e9116c2c352bb0835ca428b442dde7767c11ad32",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.2.0|^8.0.0"
+ },
+ "provide": {
+ "ext-var_representation": "*"
+ },
+ "require-dev": {
+ "phan/phan": "^5.4.1",
+ "phpunit/phpunit": "^8.5.0"
+ },
+ "suggest": {
+ "ext-var_representation": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "0.1.3-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/var_representation.php"
+ ],
+ "psr-4": {
+ "VarRepresentation\\": "src/VarRepresentation"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tyson Andre"
+ }
+ ],
+ "description": "Polyfill for var_representation: convert a variable to a string in a way that fixes the shortcomings of var_export",
+ "keywords": [
+ "var_export",
+ "var_representation"
+ ],
+ "support": {
+ "issues": "https://github.com/TysonAndre/var_representation_polyfill/issues",
+ "source": "https://github.com/TysonAndre/var_representation_polyfill/tree/0.1.3"
+ },
+ "time": "2022-08-31T12:59:22+00:00"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "2.1.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozarts/assert.git",
+ "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/ff31ad6efc62e66e518fbab1cde3453d389bcdc8",
+ "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-date": "*",
+ "ext-filter": "*",
+ "php": "^8.2"
+ },
+ "suggest": {
+ "ext-intl": "",
+ "ext-simplexml": "",
+ "ext-spl": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-feature/2-0": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ },
+ {
+ "name": "Woody Gilk",
+ "email": "woody.gilk@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "support": {
+ "issues": "https://github.com/webmozarts/assert/issues",
+ "source": "https://github.com/webmozarts/assert/tree/2.1.6"
+ },
+ "time": "2026-02-27T10:28:38+00:00"
+ },
+ {
+ "name": "wp-coding-standards/wpcs",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
+ "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b4caf9689f1a0e4a4c632679a44e638c1c67aff1",
+ "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1",
+ "shasum": ""
+ },
+ "require": {
+ "ext-filter": "*",
+ "ext-libxml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlreader": "*",
+ "php": ">=5.4",
+ "phpcsstandards/phpcsextra": "^1.1.0",
+ "phpcsstandards/phpcsutils": "^1.0.8",
+ "squizlabs/php_codesniffer": "^3.7.2"
+ },
+ "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"
+ },
+ "suggest": {
+ "ext-iconv": "For improved results",
+ "ext-mbstring": "For improved results"
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "static analysis",
+ "wordpress"
+ ],
+ "support": {
+ "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues",
+ "source": "https://github.com/WordPress/WordPress-Coding-Standards",
+ "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/thewpcc/contribute/wp-php-63406",
+ "type": "custom"
+ }
+ ],
+ "time": "2023-09-14T07:06:09+00:00"
+ },
{
"name": "yoast/phpunit-polyfills",
"version": "4.0.0",
@@ -2700,10 +4710,7 @@
],
"aliases": [],
"minimum-stability": "dev",
- "stability-flags": {
- "automattic/jetpack-changelogger": 20,
- "automattic/phpunit-select-config": 20
- },
+ "stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {},
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 00000000..08b98a39
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,14 @@
+import js from '@eslint/js';
+import globals from 'globals';
+
+export default [
+ js.configs.recommended,
+ {
+ languageOptions: {
+ globals: {
+ ...globals.browser,
+ ...globals.jquery,
+ },
+ },
+ },
+];
diff --git a/package.json b/package.json
index 0b3fee2a..1db626fc 100644
--- a/package.json
+++ b/package.json
@@ -5,12 +5,11 @@
"description": "A very fast caching engine for WordPress that produces static html files.",
"homepage": "https://jetpack.com",
"bugs": {
- "url": "https://github.com/Automattic/jetpack/labels/[Plugin] Super Cache"
+ "url": "https://github.com/Automattic/wp-super-cache/issues"
},
"repository": {
"type": "git",
- "url": "https://github.com/Automattic/jetpack.git",
- "directory": "projects/plugins/super-cache"
+ "url": "https://github.com/Automattic/wp-super-cache.git"
},
"license": "GPL-2.0-or-later",
"author": "Automattic",
@@ -20,5 +19,10 @@
"build-production": "echo 'Not implemented.'",
"build-production-js": "echo 'Not implemented.'",
"clean": "true"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.0",
+ "eslint": "^9.0",
+ "globals": "^15.0"
}
}
diff --git a/phpunit.9.xml.dist b/phpunit.9.xml.dist
new file mode 100644
index 00000000..3965963c
--- /dev/null
+++ b/phpunit.9.xml.dist
@@ -0,0 +1,17 @@
+
+
+
+
+ tests/php
+
+
+
diff --git a/tests/e2e/.env b/tests/e2e/.env
new file mode 100644
index 00000000..40316769
--- /dev/null
+++ b/tests/e2e/.env
@@ -0,0 +1,4 @@
+COMPOSE_PROJECT_NAME=super-cache-e2e
+SUPER_CACHE_E2E_PORT=2022
+SUPER_CACHE_E2E_ADMIN_USER=super_cache
+SUPER_CACHE_E2E_ADMIN_PASSWORD=super_secure
diff --git a/tests/e2e/Dockerfile b/tests/e2e/Dockerfile
new file mode 100644
index 00000000..8108abbd
--- /dev/null
+++ b/tests/e2e/Dockerfile
@@ -0,0 +1,15 @@
+FROM wordpress:latest
+
+# Install wp-cli
+RUN curl -o /bin/wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
+RUN chmod +x /bin/wp-cli.phar
+RUN ln -s /bin/wp-cli.phar /usr/bin/wp
+
+ENV WORDPRESS_DB_HOST=db
+ENV WORDPRESS_DB_USER=wordpress
+ENV WORDPRESS_DB_PASSWORD=wordpress
+ENV WORDPRESS_DB_NAME=wordpress
+
+# Override standard entry-point with our own wrapper to finish installing WP.
+ENTRYPOINT ["docker-entrypoint.sh"]
+CMD ["apache2-wrapper"]
diff --git a/tests/e2e/docker-compose.yml b/tests/e2e/docker-compose.yml
new file mode 100644
index 00000000..19661f37
--- /dev/null
+++ b/tests/e2e/docker-compose.yml
@@ -0,0 +1,43 @@
+services:
+ db:
+ image: mariadb:latest
+ healthcheck:
+ test: ["CMD", "mariadb-admin", "-P", "3306", "-pwordpress", "ping", "--silent"]
+ interval: 5s
+ timeout: 5s
+ retries: 20
+ volumes:
+ - db_data:/var/lib/mysql
+ environment:
+ - MARIADB_ROOT_PASSWORD=wordpress
+ - MYSQL_DATABASE=wordpress
+ - MYSQL_USER=wordpress
+ - MYSQL_PASSWORD=wordpress
+ expose:
+ - 3306
+ - 33060
+
+ wordpress:
+ build: .
+ volumes:
+ - wp_data:/var/www/html
+ - ../../:/var/www/html/wp-content/plugins/wp-super-cache
+ - ./tools/mu-test-helpers.php:/var/www/html/wp-content/mu-plugins/mu-test-helpers.php
+ - ./tools/apache2-wrapper.sh:/bin/apache2-wrapper
+ hostname: super-cache-e2e
+ ports:
+ - "${SUPER_CACHE_E2E_PORT:-2022}:${SUPER_CACHE_E2E_PORT:-2022}"
+ env_file:
+ - .env
+ environment:
+ - WORDPRESS_DB_HOST=db
+ - WORDPRESS_DB_USER=wordpress
+ - WORDPRESS_DB_PASSWORD=wordpress
+ - WORDPRESS_DB_NAME=wordpress
+ depends_on:
+ db:
+ condition: service_healthy
+
+volumes:
+ db_data:
+ wp_data:
diff --git a/tests/e2e/eslint.config.mjs b/tests/e2e/eslint.config.mjs
new file mode 100644
index 00000000..ae471350
--- /dev/null
+++ b/tests/e2e/eslint.config.mjs
@@ -0,0 +1,11 @@
+import { makeBaseConfig, defineConfig } from 'jetpack-js-tools/eslintrc/base.mjs';
+
+export default defineConfig( makeBaseConfig( import.meta.url, { envs: [ 'node' ] } ), {
+ rules: {
+ 'jsdoc/require-jsdoc': 'off',
+ 'jsdoc/require-hyphen-before-param-description': 'off',
+ 'jsdoc/require-param-description': 'off',
+ 'jsdoc/require-param-type': 'off',
+ 'jsdoc/require-returns': 'off',
+ },
+} );
diff --git a/tests/e2e/jest.config.js b/tests/e2e/jest.config.js
new file mode 100644
index 00000000..c7648e60
--- /dev/null
+++ b/tests/e2e/jest.config.js
@@ -0,0 +1,10 @@
+import { fileURLToPath } from 'url';
+import { config as dotenvConfig } from 'dotenv';
+
+// Read .env file so variables are available in tests.
+dotenvConfig( { path: fileURLToPath( '.env', import.meta.url ) } );
+
+export default {
+ preset: 'ts-jest',
+ testTimeout: 10000,
+};
diff --git a/tests/e2e/lib/CheerioForm.ts b/tests/e2e/lib/CheerioForm.ts
new file mode 100644
index 00000000..ddaea94a
--- /dev/null
+++ b/tests/e2e/lib/CheerioForm.ts
@@ -0,0 +1,58 @@
+import { authenticatedRequest } from './plugin-tools';
+import type { Cheerio } from 'cheerio';
+import type { AnyNode } from 'domhandler';
+
+/**
+ * Helper class for reading, updating and submitting HTML forms from a Cheerio DOMs
+ */
+export default class CheerioForm {
+ private readonly fields: Record< string, string > = {};
+
+ constructor( private readonly form: Cheerio< AnyNode > ) {
+ for ( const { name, value } of form.serializeArray() ) {
+ this.fields[ name ] = value;
+ }
+ }
+
+ /**
+ * Checks or unchecks a checkbox on the form.
+ *
+ * @param {string} name - Name of the checkbox.
+ * @param {boolean} value - True for check, false for uncheck.
+ */
+ public setCheckbox( name: string, value: boolean ): void {
+ if ( value ) {
+ this.fields[ name ] = ( this.element( name ).val() ?? '' ).toString();
+ } else {
+ delete this.fields[ name ];
+ }
+ }
+
+ /**
+ * Sets the value of a radio button or text field.
+ *
+ * @param {string} name - Name of the field.
+ * @param {string} value - Value to set.
+ */
+ public setValue( name: string, value: string ): void {
+ this.fields[ name ] = value;
+ }
+
+ /**
+ * Submit this form as an authenticated Request, using the given cookie.
+ *
+ * @param {string} authCookie - Auth cookie for form submission.
+ */
+ public async submit( authCookie: string ): Promise< void > {
+ await authenticatedRequest( authCookie, 'POST', this.form.attr( 'action' )!, this.fields );
+ }
+
+ private element( name: string ): Cheerio< AnyNode > {
+ const element = this.form.find( `input[name=${ name }]` );
+ if ( ! element ) {
+ throw new Error( `Could not find element with name ${ name }` );
+ }
+
+ return element;
+ }
+}
diff --git a/tests/e2e/lib/docker-tools.ts b/tests/e2e/lib/docker-tools.ts
new file mode 100644
index 00000000..09e1588d
--- /dev/null
+++ b/tests/e2e/lib/docker-tools.ts
@@ -0,0 +1,123 @@
+import { exec } from './system-tools';
+
+// Cache the docker container id
+let containerId: string | undefined;
+
+export async function getContainerId(): Promise< string > {
+ // Docker-compose v2
+ if ( ! containerId ) {
+ const { stdout } = await exec(
+ 'docker',
+ 'ps',
+ '-q',
+ '--filter',
+ 'ancestor=super-cache-e2e-wordpress '
+ );
+ containerId = stdout.trim();
+ }
+
+ // Docker-compose v1
+ if ( ! containerId ) {
+ const { stdout } = await exec(
+ 'docker',
+ 'ps',
+ '-q',
+ '--filter',
+ 'ancestor=super-cache-e2e_wordpress '
+ );
+ containerId = stdout.trim();
+ }
+
+ // Fail.
+ if ( ! containerId ) {
+ throw new Error( 'Failed to determine docker container ID. Is the test environment running?' );
+ }
+
+ return containerId;
+}
+
+/**
+ * Run the given command in the test Docker instance.
+ *
+ * @param {...string} command - The command to run, as a series of strings which will each be escaped.
+ */
+export async function dockerExec( ...command: string[] ) {
+ const result = await exec(
+ 'docker',
+ 'exec',
+ '-u',
+ 'www-data',
+ await getContainerId(),
+ ...command
+ );
+
+ return result.stdout;
+}
+
+/**
+ * Delete any lines that match the regex from the specified file.
+ *
+ * @param {string} filename - The file to be filtered.
+ * @param {string} regex - A regex (without / / markers) for lines to remove.
+ */
+export async function deleteLinesFromContainerFile( filename: string, regex: string ) {
+ await dockerExec( 'sed', '-i', `/${ regex }/d`, filename );
+}
+
+/**
+ * Deletes the specified file from docker.
+ *
+ * @param {string} filename - The file to delete.
+ */
+export async function deleteContainerFile( filename: string ) {
+ await dockerExec( 'rm', '-f', filename );
+}
+
+/**
+ * Deletes the specified directory (and its contents) from docker.
+ *
+ * @param {string} filename - The file to delete.
+ */
+export async function deleteContainerDirectory( filename: string ) {
+ await dockerExec( 'rm', '-rf', filename );
+}
+
+/**
+ * Returns the contents of the specified file from docker.
+ *
+ * @param {string} filename - The file to read.
+ */
+export async function readContainerFile( filename: string ): Promise< Buffer > {
+ const encoded = await dockerExec( 'bash', '-c', `cat ${ filename } | base64 -w 0` );
+
+ return Buffer.from( encoded, 'base64' );
+}
+
+/**
+ * Returns the contents of the specified file from docker, converted to string.
+ *
+ * @param {string} filename - The file to read.
+ * @param encoding
+ */
+export async function decodeContainerFile(
+ filename: string,
+ encoding: BufferEncoding = 'utf8'
+): Promise< string > {
+ return ( await readContainerFile( filename ) ).toString( encoding );
+}
+
+/**
+ * Writes the specified contents to the specified file in docker.
+ *
+ * @param {string} filename - The file to write.
+ * @param {Buffer | string} data - The file data to write.
+ */
+export async function writeContainerFile( filename: string, data: Buffer | string ) {
+ const buffer = data instanceof Buffer ? data : Buffer.from( data );
+
+ await dockerExec(
+ 'bash',
+ '-c',
+ `echo '${ buffer.toString( 'base64' ) }' | base64 --decode > ${ filename }`
+ );
+}
diff --git a/tests/e2e/lib/plugin-settings.ts b/tests/e2e/lib/plugin-settings.ts
new file mode 100644
index 00000000..e3e3355b
--- /dev/null
+++ b/tests/e2e/lib/plugin-settings.ts
@@ -0,0 +1,113 @@
+import { expect } from '@jest/globals';
+import { load as parseDom } from 'cheerio';
+import HtmlForm from './CheerioForm';
+import { authenticatedRequest, getSiteUrl } from './plugin-tools';
+
+export enum ModRewriteOptions {
+ Off = '0',
+ On = '1',
+}
+
+export enum CacheNotLoggedInOptions {
+ EnableForAllVisitors = '0',
+ DisableForAnyCookie = '1',
+ DisableForLoggedIn = '2',
+}
+
+/**
+ * Description of how to write each setting, by name.
+ */
+const settingsHandlers = {
+ wp_cache_enabled: async ( authCookie: string, value: boolean ) => {
+ await submitSettingsForm( authCookie, 'settings', 'scupdates', form => {
+ form.setCheckbox( 'wp_cache_enabled', value );
+ } );
+ },
+
+ wp_cache_mod_rewrite: async ( authCookie: string, value: ModRewriteOptions ) => {
+ await submitSettingsForm( authCookie, 'settings', 'scupdates', form => {
+ form.setValue( 'wp_cache_mod_rewrite', value );
+ } );
+ },
+
+ wp_cache_not_logged_in: async ( authCookie: string, value: CacheNotLoggedInOptions ) => {
+ await submitSettingsForm( authCookie, 'settings', 'scupdates', form => {
+ form.setValue( 'wp_cache_not_logged_in', value );
+ } );
+ },
+
+ wp_cache_no_cache_for_get: async ( authCookie: string, value: boolean ) => {
+ await submitSettingsForm( authCookie, 'settings', 'scupdates', form => {
+ form.setCheckbox( 'wp_cache_no_cache_for_get', value );
+ } );
+ },
+
+ cache_compression: async ( authCookie: string, value: boolean ) => {
+ await submitSettingsForm( authCookie, 'settings', 'scupdates', form => {
+ form.setCheckbox( 'cache_compression', value );
+ } );
+ },
+};
+
+type SettingName = keyof typeof settingsHandlers;
+type SettingMethod< Name extends SettingName > = ( typeof settingsHandlers )[ Name ];
+type SettingParams< Name extends SettingName > = Parameters< SettingMethod< Name > >;
+type SettingValue< Name extends SettingName > = SettingParams< Name >[ 1 ];
+
+type Settings = {
+ [ Name in SettingName ]: SettingValue< Name >;
+};
+
+/**
+ * Update the plugin settings as specified in the settings object.
+ *
+ * @param {string} authCookie - Auth cookie for the admin user.
+ * @param {Settings} settings - Object with settings to update and their values.
+ */
+export async function updateSettings( authCookie: string, settings: Partial< Settings > ) {
+ for ( const [ name, value ] of Object.entries( settings ) ) {
+ await (
+ settingsHandlers as Record<
+ string,
+ ( authCookie: string, value: unknown ) => Promise< void >
+ >
+ )[ name ]( authCookie, value );
+ }
+}
+
+/**
+ * Helper method to load, edit and submit a settings form.
+ *
+ * @param {string} authCookie - Auth cookie for the admin user.
+ * @param {string} tab - The name of the settings tab to submit a form from.
+ * @param {string} action - The action name of the form to submit.
+ * @param {Function} callback - Callback to edit the form after loading it.
+ */
+async function submitSettingsForm(
+ authCookie: string,
+ tab: string,
+ action: string,
+ callback: ( form: HtmlForm ) => void
+) {
+ const html = await authenticatedRequest(
+ authCookie,
+ 'GET',
+ getSiteUrl( `/wp-admin/options-general.php`, {
+ page: 'wpsupercache',
+ tab,
+ } )
+ );
+
+ const dom = parseDom( html );
+
+ const actionInput = dom( 'input[name=action][value=' + action + ']' );
+ expect( actionInput.length ).toBe( 1 );
+
+ const formElement = actionInput.closest( 'form' );
+ expect( formElement.length ).toBe( 1 );
+
+ const form = new HtmlForm( formElement );
+ callback( form );
+
+ await form.submit( authCookie );
+}
diff --git a/tests/e2e/lib/plugin-tools.ts b/tests/e2e/lib/plugin-tools.ts
new file mode 100644
index 00000000..c4e566ca
--- /dev/null
+++ b/tests/e2e/lib/plugin-tools.ts
@@ -0,0 +1,106 @@
+import fsp from 'fs/promises';
+import pathLib from 'path';
+import { expect } from '@jest/globals';
+import axios from 'axios';
+import { deleteContainerDirectory } from './docker-tools';
+import { wpcli } from './wordpress-tools';
+import type { Method } from 'axios';
+
+/**
+ * Returns the absolute path of a file in the plugin directory.
+ *
+ * @param {string} path - The path to the file, relative to the plugin directory.
+ * @return {string} The absolute path to the file.
+ */
+export function pluginFilePath( path: string ) {
+ return pathLib.join( __dirname, '../../../', path );
+}
+
+/**
+ * Returns the contents of the specified file from the plugin directory.
+ *
+ * @param {string} path - The path to the file, relative to the plugin directory.
+ * @return {string} The contents of the file. Assumed to be utf8.
+ */
+export async function readPluginFile( path: string ): Promise< string > {
+ return fsp.readFile( pluginFilePath( path ), 'utf8' );
+}
+
+/**
+ * Clears the cache.
+ */
+export async function clearCache(): Promise< void > {
+ await wpcli( 'eval', 'wp_cache_clear_cache();' );
+}
+
+/**
+ * Logs into the site and creates an auth cookie that can be used for authenticated requests.
+ *
+ * @return {string} The auth cookie.
+ */
+export async function getAuthCookie(): Promise< string > {
+ const user = process.env.SUPER_CACHE_E2E_ADMIN_USER;
+ const pass = process.env.SUPER_CACHE_E2E_ADMIN_PASSWORD;
+ const encodedAuth = Buffer.from( user + ':' + pass ).toString( 'base64' );
+ const headers = { Authorization: 'test ' + encodedAuth };
+
+ const response = await axios.post( getSiteUrl(), {}, { headers } );
+ expect( response.status ).toBe( 200 );
+
+ const cookies = response.headers[ 'set-cookie' ];
+ if ( ! cookies ) {
+ throw new Error( 'No set-cookie header found in response' );
+ }
+
+ return cookies.map( ( c: string ) => c.replace( ' HttpOnly', '' ) ).join( '; ' );
+}
+
+/**
+ * Makes an authenticated request to the specified URL.
+ *
+ * @param {string} authCookie - Authentication cookie to use for the request.
+ * @param {Method} method - HTTP method to use. e.g.: 'GET'.
+ * @param {string} url - URL to request.
+ * @param {object} data - Key / value pairs (strings) to submit as post data.
+ */
+export async function authenticatedRequest(
+ authCookie: string,
+ method: Method,
+ url: string,
+ data: Record< string, string > | undefined = undefined
+): Promise< string > {
+ const response = await axios( url, {
+ method,
+ data: data ? new URLSearchParams( data ).toString() : null,
+ headers: {
+ Accept: 'text/html',
+ Cookie: authCookie,
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ } );
+
+ expect( response.status ).toBe( 200 );
+
+ return response.data;
+}
+
+/**
+ * Returns a URL within the plugin site.
+ *
+ * @param {string} path - The path relative to the site URL.
+ * @param {object} query - Query parameters to add to the URL.
+ * @return {string} The site URL.
+ */
+export function getSiteUrl( path = '/', query: Record< string, string > = {} ): string {
+ const domain = 'http://localhost:' + process.env.SUPER_CACHE_E2E_PORT;
+ const queryString = new URLSearchParams( query ).toString();
+
+ return domain + path + ( queryString ? '?' + queryString : '' );
+}
+
+/**
+ * Delete the cache directory.
+ */
+export async function deleteCacheDirectory(): Promise< void > {
+ return deleteContainerDirectory( '/var/www/html/wp-content/cache' );
+}
diff --git a/tests/e2e/lib/system-tools.ts b/tests/e2e/lib/system-tools.ts
new file mode 100644
index 00000000..dfeb0222
--- /dev/null
+++ b/tests/e2e/lib/system-tools.ts
@@ -0,0 +1,15 @@
+import childProcess from 'node:child_process';
+import util from 'util';
+import shellEscape from 'shell-escape';
+
+const execPromise = util.promisify( childProcess.exec );
+
+/**
+ * Execute a command in the local shell. Returns its stdout.
+ *
+ * @param {...string} command - Command to run. Each string will be escaped.
+ * @return {string} stdout contents.
+ */
+export function exec( ...command: string[] ) {
+ return execPromise( shellEscape( command ) );
+}
diff --git a/tests/e2e/lib/test-tools.ts b/tests/e2e/lib/test-tools.ts
new file mode 100644
index 00000000..c6031a07
--- /dev/null
+++ b/tests/e2e/lib/test-tools.ts
@@ -0,0 +1,18 @@
+import { expect } from '@jest/globals';
+import axios from 'axios';
+import { getSiteUrl } from './plugin-tools';
+
+/**
+ * Loads the specified page and returns the response body. Expects a 200 response so tests fail otherwise.
+ *
+ * @param {string} path The path within the test site to load.
+ * @param {object} params GET parameters to add to the URL.
+ */
+export async function loadPage( path = '/', params = {} ): Promise< string > {
+ const response = await axios.get( getSiteUrl( path, params ), {
+ headers: { Accept: 'text/html' },
+ } );
+ expect( response.status ).toBe( 200 );
+
+ return response.data;
+}
diff --git a/tests/e2e/lib/wordpress-tools.ts b/tests/e2e/lib/wordpress-tools.ts
new file mode 100644
index 00000000..124dabc3
--- /dev/null
+++ b/tests/e2e/lib/wordpress-tools.ts
@@ -0,0 +1,43 @@
+import { expect } from '@jest/globals';
+import {
+ dockerExec,
+ deleteLinesFromContainerFile,
+ deleteContainerFile,
+ decodeContainerFile,
+ writeContainerFile,
+} from './docker-tools';
+import { deleteCacheDirectory, readPluginFile } from './plugin-tools';
+
+/**
+ * Run the given wp-cli command (provided as a string array) in wp-cli in the docker.
+ *
+ * @param {...string} command - The command to run.
+ */
+export async function wpcli( ...command: string[] ) {
+ return dockerExec( 'wp', ...command );
+}
+
+/**
+ * Reset the environment; clear out files created by wp-super-cache, and deactivate the plugin.
+ */
+export async function resetEnvironmnt() {
+ await wpcli( 'plugin', 'deactivate', 'wp-super-cache', '--skip-themes' );
+ await deleteContainerFile( '/var/www/html/wp-content/advanced-cache.php' );
+ await deleteContainerFile( '/var/www/html/wp-content/wp-cache-config.php' );
+ await deleteLinesFromContainerFile( '/var/www/html/wp-config.php', 'WPCACHEHOME' );
+ await deleteLinesFromContainerFile( '/var/www/html/wp-config.php', 'WP_CACHE' );
+ await writeContainerFile(
+ '/var/www/html/.htaccess',
+ await readPluginFile( 'tests/e2e/tools/htaccess.txt' )
+ );
+
+ await deleteCacheDirectory();
+
+ // Make sure tests fail if the env isn't clean.
+ const config = await decodeContainerFile( '/var/www/html/wp-config.php' );
+ expect( /define\(\s*'WP_CACHE'/.test( config ) ).toBe( false );
+ expect( /define\(\s*'WPCACHEHOME'/.test( config ) ).toBe( false );
+
+ const htaccess = await decodeContainerFile( '/var/www/html/.htaccess' );
+ expect( htaccess ).not.toContain( 'WPSuperCache' );
+}
diff --git a/tests/e2e/package.json b/tests/e2e/package.json
new file mode 100644
index 00000000..9e75c5d9
--- /dev/null
+++ b/tests/e2e/package.json
@@ -0,0 +1,30 @@
+{
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "build": "pnpm jetpack build plugins/super-cache -v --no-pnpm-install --production",
+ "clean": "rm -rf output",
+ "distclean": "rm -rf node_modules",
+ "env:down": "docker-compose down",
+ "env:up": "docker-compose up -d",
+ "pretest:run": "pnpm run clean",
+ "test:run": "jest --runInBand",
+ "typecheck": "tsgo --noEmit"
+ },
+ "devDependencies": {
+ "@jest/globals": "^30.0.0",
+ "@types/node": "^24.12.0",
+ "@types/shell-escape": "0.2.3",
+ "@typescript/native-preview": "7.0.0-dev.20260225.1",
+ "_jetpack-e2e-commons": "workspace:*",
+ "axios": "1.13.5",
+ "cheerio": "1.2.0",
+ "domhandler": "5.0.3",
+ "dotenv": "16.6.1",
+ "jest": "^30.0.0",
+ "jest-util": "^30.0.0",
+ "shell-escape": "0.2.0",
+ "ts-jest": "29.4.6",
+ "typescript": "5.9.3"
+ }
+}
diff --git a/tests/e2e/specs/activation.test.ts b/tests/e2e/specs/activation.test.ts
new file mode 100644
index 00000000..04afa45a
--- /dev/null
+++ b/tests/e2e/specs/activation.test.ts
@@ -0,0 +1,34 @@
+import { describe, beforeAll, expect, test } from '@jest/globals';
+import { decodeContainerFile, dockerExec } from '../lib/docker-tools';
+import { readPluginFile } from '../lib/plugin-tools';
+import { resetEnvironmnt, wpcli } from '../lib/wordpress-tools';
+
+describe( 'Plugin Activation', () => {
+ beforeAll( async () => {
+ await resetEnvironmnt();
+ await wpcli( 'plugin', 'activate', 'wp-super-cache' );
+ } );
+
+ test( 'Ensure wp-config.php is updated when activated', async () => {
+ const config = await decodeContainerFile( '/var/www/html/wp-config.php' );
+
+ expect( /define\(\s*'WP_CACHE'/.test( config ) ).toBe( true );
+ expect( /define\(\s*'WPCACHEHOME'/.test( config ) ).toBe( true );
+ } );
+
+ test( 'Ensure advanced-cache is populated correctly.', async () => {
+ const advancedCache = await decodeContainerFile(
+ '/var/www/html/wp-content/advanced-cache.php'
+ );
+ const expectedContents = await readPluginFile( 'advanced-cache.php' );
+
+ expect( advancedCache ).toBe( expectedContents );
+ expect( advancedCache ).not.toBe( '' );
+ } );
+
+ test( 'Ensure a wp-cache-config.php file has been created and appears valid.', async () => {
+ const result = await dockerExec( 'php', '-l', '/var/www/html/wp-content/wp-cache-config.php' );
+
+ expect( result ).toContain( 'No syntax errors' );
+ } );
+} );
diff --git a/tests/e2e/specs/default-settings.test.ts b/tests/e2e/specs/default-settings.test.ts
new file mode 100644
index 00000000..f5f6f081
--- /dev/null
+++ b/tests/e2e/specs/default-settings.test.ts
@@ -0,0 +1,98 @@
+import { describe, expect, beforeAll, test, beforeEach } from '@jest/globals';
+import { dockerExec } from '../lib/docker-tools';
+import { updateSettings } from '../lib/plugin-settings';
+import {
+ authenticatedRequest,
+ clearCache,
+ getAuthCookie,
+ getSiteUrl,
+ deleteCacheDirectory,
+} from '../lib/plugin-tools';
+import { loadPage } from '../lib/test-tools';
+import { resetEnvironmnt, wpcli } from '../lib/wordpress-tools';
+
+describe( 'cache behavior with default settings', () => {
+ beforeAll( async () => {
+ await resetEnvironmnt();
+ await wpcli( 'plugin', 'activate', 'wp-super-cache' );
+ await updateSettings( await getAuthCookie(), {
+ wp_cache_enabled: true,
+ } );
+ } );
+
+ beforeEach( async () => {
+ await deleteCacheDirectory();
+ } );
+
+ test( 'caches URLs with no get parameters', async () => {
+ const first = await loadPage();
+ const second = await loadPage();
+
+ expect( first ).toBe( second );
+ } );
+
+ test( 'logged in users get cached pages', async () => {
+ const cookie = await getAuthCookie();
+ const url = getSiteUrl();
+
+ const first = await authenticatedRequest( cookie, 'GET', url );
+ const second = await authenticatedRequest( cookie, 'GET', url );
+
+ expect( first ).toBe( second );
+ } );
+
+ test( 'pages with identical GET parameters should cache together', async () => {
+ const first = await loadPage( '/', { s: 'potato' } );
+ const second = await loadPage( '/', { s: 'potato' } );
+
+ expect( first ).toBe( second );
+ } );
+
+ test( 'pages with different GET parameters should not cache together', async () => {
+ const first = await loadPage( '/', { s: 'squid' } );
+ const second = await loadPage( '/', { s: 'potato' } );
+
+ expect( first ).not.toBe( second );
+ } );
+
+ test( 'tracking GET parameters should affect caching', async () => {
+ const first = await loadPage( '/', { utm_source: 'test' } );
+ const second = await loadPage( '/', { fbclid: 'test' } );
+
+ expect( first ).not.toBe( second );
+ } );
+
+ test( 'double slash at the start of URLs should not break URL processing', async () => {
+ const first = await loadPage( '//', { s: 'squid' } );
+ const second = await loadPage( '//' );
+
+ expect( first ).not.toBe( second );
+ } );
+
+ test( 'cached files expire', async () => {
+ const first = await loadPage();
+ const second = await loadPage();
+
+ await dockerExec(
+ 'touch',
+ '-d',
+ '1 hour ago',
+ '/var/www/html/wp-content/cache/supercache/localhost/index.html'
+ );
+
+ const third = await loadPage();
+ const fourth = await loadPage();
+
+ expect( first ).toBe( second );
+ expect( third ).toBe( fourth );
+ expect( first ).not.toBe( third );
+ } );
+
+ test( 'clears the cache', async () => {
+ const first = await loadPage();
+ await clearCache();
+ const second = await loadPage();
+
+ expect( first ).not.toBe( second );
+ } );
+} );
diff --git a/tests/e2e/specs/settings/cache-compression.test.ts b/tests/e2e/specs/settings/cache-compression.test.ts
new file mode 100644
index 00000000..c09c2f8e
--- /dev/null
+++ b/tests/e2e/specs/settings/cache-compression.test.ts
@@ -0,0 +1,46 @@
+import util from 'util';
+import zlib from 'zlib';
+import { describe, expect, beforeAll, test } from '@jest/globals';
+import { readContainerFile } from '../../lib/docker-tools';
+import { updateSettings } from '../../lib/plugin-settings';
+import { getAuthCookie } from '../../lib/plugin-tools';
+import { loadPage } from '../../lib/test-tools';
+import { resetEnvironmnt, wpcli } from '../../lib/wordpress-tools';
+
+const gunzip = util.promisify( zlib.gunzip );
+
+let authCookie: string;
+
+describe( 'cache_compression settings', () => {
+ beforeAll( async () => {
+ await resetEnvironmnt();
+ await wpcli( 'plugin', 'activate', 'wp-super-cache' );
+
+ authCookie = await getAuthCookie();
+ await updateSettings( authCookie, {
+ wp_cache_enabled: true,
+ cache_compression: true,
+ } );
+ } );
+
+ test( 'caching works correctly', async () => {
+ const first = await loadPage();
+ const second = await loadPage();
+
+ expect( first ).toBe( second );
+ expect( first ).toMatch( /Compression = gzip/ );
+ } );
+
+ test( 'cached files are stored gzipped', async () => {
+ // Load a page, and strip the WP Super Cache comment from the bottom.
+ const rawContent = await loadPage();
+ const trimmed = rawContent.replace( '', '' ).trim();
+
+ const gzipped = await readContainerFile(
+ '/var/www/html/wp-content/cache/supercache/localhost/index.html.gz'
+ );
+ const decompressed = ( await gunzip( gzipped ) ).toString().trim();
+
+ expect( trimmed ).toBe( decompressed );
+ } );
+} );
diff --git a/tests/e2e/specs/settings/mod-rewrite.test.ts b/tests/e2e/specs/settings/mod-rewrite.test.ts
new file mode 100644
index 00000000..e6f93388
--- /dev/null
+++ b/tests/e2e/specs/settings/mod-rewrite.test.ts
@@ -0,0 +1,95 @@
+import { describe, expect, beforeAll, test } from '@jest/globals';
+import { decodeContainerFile } from '../../lib/docker-tools';
+import { ModRewriteOptions, updateSettings } from '../../lib/plugin-settings';
+import {
+ authenticatedRequest,
+ clearCache,
+ getAuthCookie,
+ getSiteUrl,
+} from '../../lib/plugin-tools';
+import { loadPage } from '../../lib/test-tools';
+import { resetEnvironmnt, wpcli } from '../../lib/wordpress-tools';
+
+let authCookie: string;
+
+describe( 'cache behavior with mod_rewrite enabled', () => {
+ beforeAll( async () => {
+ await resetEnvironmnt();
+ await wpcli( 'plugin', 'activate', 'wp-super-cache' );
+
+ authCookie = await getAuthCookie();
+ await updateSettings( authCookie, {
+ wp_cache_enabled: true,
+ wp_cache_mod_rewrite: ModRewriteOptions.On,
+ } );
+ } );
+
+ test( 'updates mod_rewrite rules', async () => {
+ const rules = await decodeContainerFile( '/var/www/html/.htaccess' );
+
+ expect( rules ).toContain( '# BEGIN WPSuperCache' );
+ expect( rules ).toContain( '# END WPSuperCache' );
+ expect( rules ).toContain(
+ 'RewriteRule ^(.*) "/wp-content/cache/supercache/%{SERVER_NAME}/$1/index.html" [L]'
+ );
+ } );
+
+ test( 'caches URLs with no get parameters', async () => {
+ const first = await loadPage();
+ const second = await loadPage();
+
+ expect( first ).toBe( second );
+ } );
+
+ test( 'logged in users get cached pages by default', async () => {
+ const url = getSiteUrl();
+
+ const first = await authenticatedRequest( authCookie, 'GET', url );
+ const second = await authenticatedRequest( authCookie, 'GET', url );
+
+ expect( first ).toBe( second );
+ } );
+
+ test( 'GET parameters should affect caching', async () => {
+ const first = await loadPage( '/', { s: 'squid' } );
+ const second = await loadPage( '/', { s: 'potato' } );
+
+ expect( first ).not.toBe( second );
+ } );
+
+ test( 'tracking GET parameters should affect caching', async () => {
+ const first = await loadPage( '/', { utm_source: 'test' } );
+ const second = await loadPage( '/', { fbclid: 'test' } );
+
+ expect( first ).not.toBe( second );
+ } );
+
+ test( 'double slash at the start of URLs should not break URL processing', async () => {
+ const first = await loadPage( '//', { s: 'squid' } );
+ const second = await loadPage( '//' );
+
+ expect( first ).not.toBe( second );
+ } );
+
+ test( 'clears the cache', async () => {
+ const first = await loadPage();
+ await clearCache();
+ const second = await loadPage();
+
+ expect( first ).not.toBe( second );
+ } );
+
+ test( 'removes mod_rewrite rules when turned off', async () => {
+ await updateSettings( authCookie, {
+ wp_cache_mod_rewrite: ModRewriteOptions.Off,
+ } );
+
+ const rules = await decodeContainerFile( '/var/www/html/.htaccess' );
+ expect( rules ).not.toContain( 'cache/supercache' );
+
+ // Return things to the state other tests expect.
+ await updateSettings( authCookie, {
+ wp_cache_mod_rewrite: ModRewriteOptions.On,
+ } );
+ } );
+} );
diff --git a/tests/e2e/specs/settings/no-cache-for-get.test.ts b/tests/e2e/specs/settings/no-cache-for-get.test.ts
new file mode 100644
index 00000000..8d4d34e7
--- /dev/null
+++ b/tests/e2e/specs/settings/no-cache-for-get.test.ts
@@ -0,0 +1,34 @@
+import { describe, expect, beforeAll, test } from '@jest/globals';
+import { updateSettings } from '../../lib/plugin-settings';
+import { getAuthCookie } from '../../lib/plugin-tools';
+import { loadPage } from '../../lib/test-tools';
+import { resetEnvironmnt, wpcli } from '../../lib/wordpress-tools';
+
+let authCookie: string;
+
+describe( 'wp_cache_no_cache_for_get settings', () => {
+ beforeAll( async () => {
+ await resetEnvironmnt();
+ await wpcli( 'plugin', 'activate', 'wp-super-cache' );
+
+ authCookie = await getAuthCookie();
+ await updateSettings( authCookie, {
+ wp_cache_enabled: true,
+ wp_cache_no_cache_for_get: true,
+ } );
+ } );
+
+ test( 'caches URLs with no get parameters', async () => {
+ const first = await loadPage();
+ const second = await loadPage();
+
+ expect( first ).toBe( second );
+ } );
+
+ test( 'does not cache URLs with get parameters', async () => {
+ const first = await loadPage( '/', { s: 'potato' } );
+ const second = await loadPage( '/', { s: 'potato' } );
+
+ expect( first ).not.toBe( second );
+ } );
+} );
diff --git a/tests/e2e/specs/settings/not-logged-in.test.ts b/tests/e2e/specs/settings/not-logged-in.test.ts
new file mode 100644
index 00000000..88ad84ca
--- /dev/null
+++ b/tests/e2e/specs/settings/not-logged-in.test.ts
@@ -0,0 +1,83 @@
+import { describe, expect, beforeAll, test } from '@jest/globals';
+import { CacheNotLoggedInOptions, updateSettings } from '../../lib/plugin-settings';
+import { authenticatedRequest, getAuthCookie, getSiteUrl } from '../../lib/plugin-tools';
+import { loadPage } from '../../lib/test-tools';
+import { resetEnvironmnt, wpcli } from '../../lib/wordpress-tools';
+
+let authCookie: string;
+
+describe( 'wp_cache_not_logged_in settings', () => {
+ beforeAll( async () => {
+ await resetEnvironmnt();
+ await wpcli( 'plugin', 'activate', 'wp-super-cache' );
+
+ authCookie = await getAuthCookie();
+ await updateSettings( authCookie, {
+ wp_cache_enabled: true,
+ } );
+ } );
+
+ test( 'logged in users get cached pages when "Enable caching for all visitors"', async () => {
+ await updateSettings( authCookie, {
+ wp_cache_not_logged_in: CacheNotLoggedInOptions.EnableForAllVisitors,
+ } );
+
+ const url = getSiteUrl();
+
+ const first = await authenticatedRequest( authCookie, 'GET', url );
+ const second = await authenticatedRequest( authCookie, 'GET', url );
+
+ expect( first ).toBe( second );
+ } );
+
+ test( 'wp-admin is never cached, even when "Enable caching for all visitors"', async () => {
+ await updateSettings( authCookie, {
+ wp_cache_not_logged_in: CacheNotLoggedInOptions.EnableForAllVisitors,
+ } );
+
+ const url = getSiteUrl( '/wp-admin/' );
+
+ const first = await authenticatedRequest( authCookie, 'GET', url );
+ const second = await authenticatedRequest( authCookie, 'GET', url );
+
+ expect( first ).not.toBe( second );
+ } );
+
+ test( 'users with any cookie do not get cached when "Disable caching for visitors who have a cookie"', async () => {
+ await updateSettings( authCookie, {
+ wp_cache_not_logged_in: CacheNotLoggedInOptions.DisableForAnyCookie,
+ } );
+
+ const cookie = 'cooookie: OMNOMNOM';
+ const url = getSiteUrl();
+
+ const first = await authenticatedRequest( cookie, 'GET', url );
+ const second = await authenticatedRequest( cookie, 'GET', url );
+
+ expect( first ).not.toBe( second );
+ } );
+
+ test( 'users with no cookie get cached when "Disable caching for visitors who have a cookie"', async () => {
+ await updateSettings( authCookie, {
+ wp_cache_not_logged_in: CacheNotLoggedInOptions.DisableForAnyCookie,
+ } );
+
+ const first = await loadPage();
+ const second = await loadPage();
+
+ expect( first ).toBe( second );
+ } );
+
+ test( 'logged in users do not get cached pages when "Disable caching for logged in visitors"', async () => {
+ await updateSettings( authCookie, {
+ wp_cache_not_logged_in: CacheNotLoggedInOptions.DisableForLoggedIn,
+ } );
+
+ const url = getSiteUrl();
+
+ const first = await authenticatedRequest( authCookie, 'GET', url );
+ const second = await authenticatedRequest( authCookie, 'GET', url );
+
+ expect( first ).not.toBe( second );
+ } );
+} );
diff --git a/tests/e2e/tools/apache2-wrapper.sh b/tests/e2e/tools/apache2-wrapper.sh
new file mode 100644
index 00000000..032b9026
--- /dev/null
+++ b/tests/e2e/tools/apache2-wrapper.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+##
+## The WordPress base image installs WordPress, then launches apache directly.
+## See: https://github.com/docker-library/wordpress/blob/b9af6087524edc719249f590940b34ef107c95ca/latest/php7.4/apache/docker-entrypoint.sh
+##
+## This wrapper hijacks that part of the process, and sets up WordPress too.
+##
+
+sed -i "s/^Listen 80$/Listen $SUPER_CACHE_E2E_PORT/g" /etc/apache2/ports.conf
+
+wp core install \
+ --allow-root \
+ --url="http://localhost:$SUPER_CACHE_E2E_PORT" \
+ --path=/var/www/html \
+ --title='Super Cache e2e' \
+ --admin_user="$SUPER_CACHE_E2E_ADMIN_USER" \
+ --admin_password="$SUPER_CACHE_E2E_ADMIN_PASSWORD" \
+ --admin_email=fake@example.com
+
+wp rewrite structure '/%year%/%monthnum%/%postname%/'\
+ --allow-root \
+ --path=/var/www/html
+
+# Start Apache.
+apache2-foreground
diff --git a/tests/e2e/tools/htaccess.txt b/tests/e2e/tools/htaccess.txt
new file mode 100644
index 00000000..423c2f6e
--- /dev/null
+++ b/tests/e2e/tools/htaccess.txt
@@ -0,0 +1,11 @@
+# BEGIN WordPress
+
+RewriteEngine On
+RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+RewriteBase /
+RewriteRule ^index\.php$ - [L]
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule . /index.php [L]
+
+# END WordPress
diff --git a/tests/e2e/tools/mu-test-helpers.php b/tests/e2e/tools/mu-test-helpers.php
new file mode 100644
index 00000000..91ae2708
--- /dev/null
+++ b/tests/e2e/tools/mu-test-helpers.php
@@ -0,0 +1,42 @@
+';
+}
+add_action( 'wp_footer', 'wpsc_test_inject_footer' );
+add_action( 'admin_footer', 'wpsc_test_inject_footer' );
+
+/**
+ * Allow per-request login via HTTP header for a single request. Makes testing easier.
+ */
+function wpsc_test_header_login() {
+ if ( ! empty( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
+ $auth = sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) );
+ if ( 0 === stripos( $auth, 'test ' ) ) {
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
+ list( $user, $pass ) = explode( ':', base64_decode( substr( $auth, 5 ) ) );
+
+ $user = wp_signon(
+ array(
+ 'user_login' => $user,
+ 'user_password' => $pass,
+ )
+ );
+
+ wp_set_current_user( $user->ID, $user->user_login );
+ do_action( 'wp_login', $user->user_login, $user );
+ }
+ }
+}
+add_action( 'init', 'wpsc_test_header_login' );
diff --git a/tests/e2e/tsconfig.json b/tests/e2e/tsconfig.json
new file mode 100644
index 00000000..ca184b2e
--- /dev/null
+++ b/tests/e2e/tsconfig.json
@@ -0,0 +1,6 @@
+{
+ "extends": "_jetpack-e2e-commons/tsconfig.json",
+ "compilerOptions": {
+ "esModuleInterop": true
+ }
+}
diff --git a/tests/php/bootstrap.php b/tests/php/bootstrap.php
new file mode 100644
index 00000000..46763b04
--- /dev/null
+++ b/tests/php/bootstrap.php
@@ -0,0 +1,11 @@
+
- * Jordi Boggiano
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Composer\Autoload;
-
-/**
- * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
- *
- * $loader = new \Composer\Autoload\ClassLoader();
- *
- * // register classes with namespaces
- * $loader->add('Symfony\Component', __DIR__.'/component');
- * $loader->add('Symfony', __DIR__.'/framework');
- *
- * // activate the autoloader
- * $loader->register();
- *
- * // to enable searching the include path (eg. for PEAR packages)
- * $loader->setUseIncludePath(true);
- *
- * In this example, if you try to use a class in the Symfony\Component
- * namespace or one of its children (Symfony\Component\Console for instance),
- * the autoloader will first look for the class under the component/
- * directory, and it will then fallback to the framework/ directory if not
- * found before giving up.
- *
- * This class is loosely based on the Symfony UniversalClassLoader.
- *
- * @author Fabien Potencier
- * @author Jordi Boggiano
- * @see https://www.php-fig.org/psr/psr-0/
- * @see https://www.php-fig.org/psr/psr-4/
- */
-class ClassLoader
-{
- /** @var \Closure(string):void */
- private static $includeFile;
-
- /** @var string|null */
- private $vendorDir;
-
- // PSR-4
- /**
- * @var array>
- */
- private $prefixLengthsPsr4 = array();
- /**
- * @var array>
- */
- private $prefixDirsPsr4 = array();
- /**
- * @var list
- */
- private $fallbackDirsPsr4 = array();
-
- // PSR-0
- /**
- * List of PSR-0 prefixes
- *
- * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
- *
- * @var array>>
- */
- private $prefixesPsr0 = array();
- /**
- * @var list
- */
- private $fallbackDirsPsr0 = array();
-
- /** @var bool */
- private $useIncludePath = false;
-
- /**
- * @var array
- */
- private $classMap = array();
-
- /** @var bool */
- private $classMapAuthoritative = false;
-
- /**
- * @var array
- */
- private $missingClasses = array();
-
- /** @var string|null */
- private $apcuPrefix;
-
- /**
- * @var array
- */
- private static $registeredLoaders = array();
-
- /**
- * @param string|null $vendorDir
- */
- public function __construct($vendorDir = null)
- {
- $this->vendorDir = $vendorDir;
- self::initializeIncludeClosure();
- }
-
- /**
- * @return array>
- */
- public function getPrefixes()
- {
- if (!empty($this->prefixesPsr0)) {
- return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
- }
-
- return array();
- }
-
- /**
- * @return array>
- */
- public function getPrefixesPsr4()
- {
- return $this->prefixDirsPsr4;
- }
-
- /**
- * @return list
- */
- public function getFallbackDirs()
- {
- return $this->fallbackDirsPsr0;
- }
-
- /**
- * @return list
- */
- public function getFallbackDirsPsr4()
- {
- return $this->fallbackDirsPsr4;
- }
-
- /**
- * @return array Array of classname => path
- */
- public function getClassMap()
- {
- return $this->classMap;
- }
-
- /**
- * @param array $classMap Class to filename map
- *
- * @return void
- */
- public function addClassMap(array $classMap)
- {
- if ($this->classMap) {
- $this->classMap = array_merge($this->classMap, $classMap);
- } else {
- $this->classMap = $classMap;
- }
- }
-
- /**
- * Registers a set of PSR-0 directories for a given prefix, either
- * appending or prepending to the ones previously set for this prefix.
- *
- * @param string $prefix The prefix
- * @param list|string $paths The PSR-0 root directories
- * @param bool $prepend Whether to prepend the directories
- *
- * @return void
- */
- public function add($prefix, $paths, $prepend = false)
- {
- $paths = (array) $paths;
- if (!$prefix) {
- if ($prepend) {
- $this->fallbackDirsPsr0 = array_merge(
- $paths,
- $this->fallbackDirsPsr0
- );
- } else {
- $this->fallbackDirsPsr0 = array_merge(
- $this->fallbackDirsPsr0,
- $paths
- );
- }
-
- return;
- }
-
- $first = $prefix[0];
- if (!isset($this->prefixesPsr0[$first][$prefix])) {
- $this->prefixesPsr0[$first][$prefix] = $paths;
-
- return;
- }
- if ($prepend) {
- $this->prefixesPsr0[$first][$prefix] = array_merge(
- $paths,
- $this->prefixesPsr0[$first][$prefix]
- );
- } else {
- $this->prefixesPsr0[$first][$prefix] = array_merge(
- $this->prefixesPsr0[$first][$prefix],
- $paths
- );
- }
- }
-
- /**
- * Registers a set of PSR-4 directories for a given namespace, either
- * appending or prepending to the ones previously set for this namespace.
- *
- * @param string $prefix The prefix/namespace, with trailing '\\'
- * @param list|string $paths The PSR-4 base directories
- * @param bool $prepend Whether to prepend the directories
- *
- * @throws \InvalidArgumentException
- *
- * @return void
- */
- public function addPsr4($prefix, $paths, $prepend = false)
- {
- $paths = (array) $paths;
- if (!$prefix) {
- // Register directories for the root namespace.
- if ($prepend) {
- $this->fallbackDirsPsr4 = array_merge(
- $paths,
- $this->fallbackDirsPsr4
- );
- } else {
- $this->fallbackDirsPsr4 = array_merge(
- $this->fallbackDirsPsr4,
- $paths
- );
- }
- } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
- // Register directories for a new namespace.
- $length = strlen($prefix);
- if ('\\' !== $prefix[$length - 1]) {
- throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
- }
- $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
- $this->prefixDirsPsr4[$prefix] = $paths;
- } elseif ($prepend) {
- // Prepend directories for an already registered namespace.
- $this->prefixDirsPsr4[$prefix] = array_merge(
- $paths,
- $this->prefixDirsPsr4[$prefix]
- );
- } else {
- // Append directories for an already registered namespace.
- $this->prefixDirsPsr4[$prefix] = array_merge(
- $this->prefixDirsPsr4[$prefix],
- $paths
- );
- }
- }
-
- /**
- * Registers a set of PSR-0 directories for a given prefix,
- * replacing any others previously set for this prefix.
- *
- * @param string $prefix The prefix
- * @param list|string $paths The PSR-0 base directories
- *
- * @return void
- */
- public function set($prefix, $paths)
- {
- if (!$prefix) {
- $this->fallbackDirsPsr0 = (array) $paths;
- } else {
- $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
- }
- }
-
- /**
- * Registers a set of PSR-4 directories for a given namespace,
- * replacing any others previously set for this namespace.
- *
- * @param string $prefix The prefix/namespace, with trailing '\\'
- * @param list|string $paths The PSR-4 base directories
- *
- * @throws \InvalidArgumentException
- *
- * @return void
- */
- public function setPsr4($prefix, $paths)
- {
- if (!$prefix) {
- $this->fallbackDirsPsr4 = (array) $paths;
- } else {
- $length = strlen($prefix);
- if ('\\' !== $prefix[$length - 1]) {
- throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
- }
- $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
- $this->prefixDirsPsr4[$prefix] = (array) $paths;
- }
- }
-
- /**
- * Turns on searching the include path for class files.
- *
- * @param bool $useIncludePath
- *
- * @return void
- */
- public function setUseIncludePath($useIncludePath)
- {
- $this->useIncludePath = $useIncludePath;
- }
-
- /**
- * Can be used to check if the autoloader uses the include path to check
- * for classes.
- *
- * @return bool
- */
- public function getUseIncludePath()
- {
- return $this->useIncludePath;
- }
-
- /**
- * Turns off searching the prefix and fallback directories for classes
- * that have not been registered with the class map.
- *
- * @param bool $classMapAuthoritative
- *
- * @return void
- */
- public function setClassMapAuthoritative($classMapAuthoritative)
- {
- $this->classMapAuthoritative = $classMapAuthoritative;
- }
-
- /**
- * Should class lookup fail if not found in the current class map?
- *
- * @return bool
- */
- public function isClassMapAuthoritative()
- {
- return $this->classMapAuthoritative;
- }
-
- /**
- * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
- *
- * @param string|null $apcuPrefix
- *
- * @return void
- */
- public function setApcuPrefix($apcuPrefix)
- {
- $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
- }
-
- /**
- * The APCu prefix in use, or null if APCu caching is not enabled.
- *
- * @return string|null
- */
- public function getApcuPrefix()
- {
- return $this->apcuPrefix;
- }
-
- /**
- * Registers this instance as an autoloader.
- *
- * @param bool $prepend Whether to prepend the autoloader or not
- *
- * @return void
- */
- public function register($prepend = false)
- {
- spl_autoload_register(array($this, 'loadClass'), true, $prepend);
-
- if (null === $this->vendorDir) {
- return;
- }
-
- if ($prepend) {
- self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
- } else {
- unset(self::$registeredLoaders[$this->vendorDir]);
- self::$registeredLoaders[$this->vendorDir] = $this;
- }
- }
-
- /**
- * Unregisters this instance as an autoloader.
- *
- * @return void
- */
- public function unregister()
- {
- spl_autoload_unregister(array($this, 'loadClass'));
-
- if (null !== $this->vendorDir) {
- unset(self::$registeredLoaders[$this->vendorDir]);
- }
- }
-
- /**
- * Loads the given class or interface.
- *
- * @param string $class The name of the class
- * @return true|null True if loaded, null otherwise
- */
- public function loadClass($class)
- {
- if ($file = $this->findFile($class)) {
- $includeFile = self::$includeFile;
- $includeFile($file);
-
- return true;
- }
-
- return null;
- }
-
- /**
- * Finds the path to the file where the class is defined.
- *
- * @param string $class The name of the class
- *
- * @return string|false The path if found, false otherwise
- */
- public function findFile($class)
- {
- // class map lookup
- if (isset($this->classMap[$class])) {
- return $this->classMap[$class];
- }
- if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
- return false;
- }
- if (null !== $this->apcuPrefix) {
- $file = apcu_fetch($this->apcuPrefix.$class, $hit);
- if ($hit) {
- return $file;
- }
- }
-
- $file = $this->findFileWithExtension($class, '.php');
-
- // Search for Hack files if we are running on HHVM
- if (false === $file && defined('HHVM_VERSION')) {
- $file = $this->findFileWithExtension($class, '.hh');
- }
-
- if (null !== $this->apcuPrefix) {
- apcu_add($this->apcuPrefix.$class, $file);
- }
-
- if (false === $file) {
- // Remember that this class does not exist.
- $this->missingClasses[$class] = true;
- }
-
- return $file;
- }
-
- /**
- * Returns the currently registered loaders keyed by their corresponding vendor directories.
- *
- * @return array
- */
- public static function getRegisteredLoaders()
- {
- return self::$registeredLoaders;
- }
-
- /**
- * @param string $class
- * @param string $ext
- * @return string|false
- */
- private function findFileWithExtension($class, $ext)
- {
- // PSR-4 lookup
- $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
-
- $first = $class[0];
- if (isset($this->prefixLengthsPsr4[$first])) {
- $subPath = $class;
- while (false !== $lastPos = strrpos($subPath, '\\')) {
- $subPath = substr($subPath, 0, $lastPos);
- $search = $subPath . '\\';
- if (isset($this->prefixDirsPsr4[$search])) {
- $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
- foreach ($this->prefixDirsPsr4[$search] as $dir) {
- if (file_exists($file = $dir . $pathEnd)) {
- return $file;
- }
- }
- }
- }
- }
-
- // PSR-4 fallback dirs
- foreach ($this->fallbackDirsPsr4 as $dir) {
- if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
- return $file;
- }
- }
-
- // PSR-0 lookup
- if (false !== $pos = strrpos($class, '\\')) {
- // namespaced class name
- $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
- . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
- } else {
- // PEAR-like class name
- $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
- }
-
- if (isset($this->prefixesPsr0[$first])) {
- foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
- if (0 === strpos($class, $prefix)) {
- foreach ($dirs as $dir) {
- if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
- return $file;
- }
- }
- }
- }
- }
-
- // PSR-0 fallback dirs
- foreach ($this->fallbackDirsPsr0 as $dir) {
- if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
- return $file;
- }
- }
-
- // PSR-0 include paths.
- if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
- return $file;
- }
-
- return false;
- }
-
- /**
- * @return void
- */
- private static function initializeIncludeClosure()
- {
- if (self::$includeFile !== null) {
- return;
- }
-
- /**
- * Scope isolated include.
- *
- * Prevents access to $this/self from included files.
- *
- * @param string $file
- * @return void
- */
- self::$includeFile = \Closure::bind(static function($file) {
- include $file;
- }, null, null);
- }
-}
diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php
deleted file mode 100644
index 2052022f..00000000
--- a/vendor/composer/InstalledVersions.php
+++ /dev/null
@@ -1,396 +0,0 @@
-
- * Jordi Boggiano
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Composer;
-
-use Composer\Autoload\ClassLoader;
-use Composer\Semver\VersionParser;
-
-/**
- * This class is copied in every Composer installed project and available to all
- *
- * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
- *
- * To require its presence, you can require `composer-runtime-api ^2.0`
- *
- * @final
- */
-class InstalledVersions
-{
- /**
- * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
- * @internal
- */
- private static $selfDir = null;
-
- /**
- * @var mixed[]|null
- * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null
- */
- private static $installed;
-
- /**
- * @var bool
- */
- private static $installedIsLocalDir;
-
- /**
- * @var bool|null
- */
- private static $canGetVendors;
-
- /**
- * @var array[]
- * @psalm-var array}>
- */
- private static $installedByVendor = array();
-
- /**
- * Returns a list of all package names which are present, either by being installed, replaced or provided
- *
- * @return string[]
- * @psalm-return list
- */
- public static function getInstalledPackages()
- {
- $packages = array();
- foreach (self::getInstalled() as $installed) {
- $packages[] = array_keys($installed['versions']);
- }
-
- if (1 === \count($packages)) {
- return $packages[0];
- }
-
- return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
- }
-
- /**
- * Returns a list of all package names with a specific type e.g. 'library'
- *
- * @param string $type
- * @return string[]
- * @psalm-return list
- */
- public static function getInstalledPackagesByType($type)
- {
- $packagesByType = array();
-
- foreach (self::getInstalled() as $installed) {
- foreach ($installed['versions'] as $name => $package) {
- if (isset($package['type']) && $package['type'] === $type) {
- $packagesByType[] = $name;
- }
- }
- }
-
- return $packagesByType;
- }
-
- /**
- * Checks whether the given package is installed
- *
- * This also returns true if the package name is provided or replaced by another package
- *
- * @param string $packageName
- * @param bool $includeDevRequirements
- * @return bool
- */
- public static function isInstalled($packageName, $includeDevRequirements = true)
- {
- foreach (self::getInstalled() as $installed) {
- if (isset($installed['versions'][$packageName])) {
- return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
- }
- }
-
- return false;
- }
-
- /**
- * Checks whether the given package satisfies a version constraint
- *
- * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
- *
- * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
- *
- * @param VersionParser $parser Install composer/semver to have access to this class and functionality
- * @param string $packageName
- * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
- * @return bool
- */
- public static function satisfies(VersionParser $parser, $packageName, $constraint)
- {
- $constraint = $parser->parseConstraints((string) $constraint);
- $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
-
- return $provided->matches($constraint);
- }
-
- /**
- * Returns a version constraint representing all the range(s) which are installed for a given package
- *
- * It is easier to use this via isInstalled() with the $constraint argument if you need to check
- * whether a given version of a package is installed, and not just whether it exists
- *
- * @param string $packageName
- * @return string Version constraint usable with composer/semver
- */
- public static function getVersionRanges($packageName)
- {
- foreach (self::getInstalled() as $installed) {
- if (!isset($installed['versions'][$packageName])) {
- continue;
- }
-
- $ranges = array();
- if (isset($installed['versions'][$packageName]['pretty_version'])) {
- $ranges[] = $installed['versions'][$packageName]['pretty_version'];
- }
- if (array_key_exists('aliases', $installed['versions'][$packageName])) {
- $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
- }
- if (array_key_exists('replaced', $installed['versions'][$packageName])) {
- $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
- }
- if (array_key_exists('provided', $installed['versions'][$packageName])) {
- $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
- }
-
- return implode(' || ', $ranges);
- }
-
- throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
- }
-
- /**
- * @param string $packageName
- * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
- */
- public static function getVersion($packageName)
- {
- foreach (self::getInstalled() as $installed) {
- if (!isset($installed['versions'][$packageName])) {
- continue;
- }
-
- if (!isset($installed['versions'][$packageName]['version'])) {
- return null;
- }
-
- return $installed['versions'][$packageName]['version'];
- }
-
- throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
- }
-
- /**
- * @param string $packageName
- * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
- */
- public static function getPrettyVersion($packageName)
- {
- foreach (self::getInstalled() as $installed) {
- if (!isset($installed['versions'][$packageName])) {
- continue;
- }
-
- if (!isset($installed['versions'][$packageName]['pretty_version'])) {
- return null;
- }
-
- return $installed['versions'][$packageName]['pretty_version'];
- }
-
- throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
- }
-
- /**
- * @param string $packageName
- * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
- */
- public static function getReference($packageName)
- {
- foreach (self::getInstalled() as $installed) {
- if (!isset($installed['versions'][$packageName])) {
- continue;
- }
-
- if (!isset($installed['versions'][$packageName]['reference'])) {
- return null;
- }
-
- return $installed['versions'][$packageName]['reference'];
- }
-
- throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
- }
-
- /**
- * @param string $packageName
- * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
- */
- public static function getInstallPath($packageName)
- {
- foreach (self::getInstalled() as $installed) {
- if (!isset($installed['versions'][$packageName])) {
- continue;
- }
-
- return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
- }
-
- throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
- }
-
- /**
- * @return array
- * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
- */
- public static function getRootPackage()
- {
- $installed = self::getInstalled();
-
- return $installed[0]['root'];
- }
-
- /**
- * Returns the raw installed.php data for custom implementations
- *
- * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
- * @return array[]
- * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}
- */
- public static function getRawData()
- {
- @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
-
- if (null === self::$installed) {
- // only require the installed.php file if this file is loaded from its dumped location,
- // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
- if (substr(__DIR__, -8, 1) !== 'C') {
- self::$installed = include __DIR__ . '/installed.php';
- } else {
- self::$installed = array();
- }
- }
-
- return self::$installed;
- }
-
- /**
- * Returns the raw data of all installed.php which are currently loaded for custom implementations
- *
- * @return array[]
- * @psalm-return list}>
- */
- public static function getAllRawData()
- {
- return self::getInstalled();
- }
-
- /**
- * Lets you reload the static array from another file
- *
- * This is only useful for complex integrations in which a project needs to use
- * this class but then also needs to execute another project's autoloader in process,
- * and wants to ensure both projects have access to their version of installed.php.
- *
- * A typical case would be PHPUnit, where it would need to make sure it reads all
- * the data it needs from this class, then call reload() with
- * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
- * the project in which it runs can then also use this class safely, without
- * interference between PHPUnit's dependencies and the project's dependencies.
- *
- * @param array[] $data A vendor/composer/installed.php data set
- * @return void
- *
- * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data
- */
- public static function reload($data)
- {
- self::$installed = $data;
- self::$installedByVendor = array();
-
- // when using reload, we disable the duplicate protection to ensure that self::$installed data is
- // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
- // so we have to assume it does not, and that may result in duplicate data being returned when listing
- // all installed packages for example
- self::$installedIsLocalDir = false;
- }
-
- /**
- * @return string
- */
- private static function getSelfDir()
- {
- if (self::$selfDir === null) {
- self::$selfDir = strtr(__DIR__, '\\', '/');
- }
-
- return self::$selfDir;
- }
-
- /**
- * @return array[]
- * @psalm-return list}>
- */
- private static function getInstalled()
- {
- if (null === self::$canGetVendors) {
- self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
- }
-
- $installed = array();
- $copiedLocalDir = false;
-
- if (self::$canGetVendors) {
- $selfDir = self::getSelfDir();
- foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
- $vendorDir = strtr($vendorDir, '\\', '/');
- if (isset(self::$installedByVendor[$vendorDir])) {
- $installed[] = self::$installedByVendor[$vendorDir];
- } elseif (is_file($vendorDir.'/composer/installed.php')) {
- /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
- $required = require $vendorDir.'/composer/installed.php';
- self::$installedByVendor[$vendorDir] = $required;
- $installed[] = $required;
- if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
- self::$installed = $required;
- self::$installedIsLocalDir = true;
- }
- }
- if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
- $copiedLocalDir = true;
- }
- }
- }
-
- if (null === self::$installed) {
- // only require the installed.php file if this file is loaded from its dumped location,
- // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
- if (substr(__DIR__, -8, 1) !== 'C') {
- /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
- $required = require __DIR__ . '/installed.php';
- self::$installed = $required;
- } else {
- self::$installed = array();
- }
- }
-
- if (self::$installed !== array() && !$copiedLocalDir) {
- $installed[] = self::$installed;
- }
-
- return $installed;
- }
-}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
deleted file mode 100644
index f27399a0..00000000
--- a/vendor/composer/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-
-Copyright (c) Nils Adermann, Jordi Boggiano
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is furnished
-to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
deleted file mode 100644
index 5522a610..00000000
--- a/vendor/composer/autoload_classmap.php
+++ /dev/null
@@ -1,12 +0,0 @@
- $baseDir . '/src/device-detection/class-device-detection.php',
- 'Automattic\\WPSC\\Device_Detection\\User_Agent_Info' => $baseDir . '/src/device-detection/class-user-agent-info.php',
- 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
-);
diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php
deleted file mode 100644
index 15a2ff3a..00000000
--- a/vendor/composer/autoload_namespaces.php
+++ /dev/null
@@ -1,9 +0,0 @@
-setClassMapAuthoritative(true);
- $loader->register(true);
-
- return $loader;
- }
-}
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
deleted file mode 100644
index dae8dc37..00000000
--- a/vendor/composer/autoload_static.php
+++ /dev/null
@@ -1,22 +0,0 @@
- __DIR__ . '/../..' . '/src/device-detection/class-device-detection.php',
- 'Automattic\\WPSC\\Device_Detection\\User_Agent_Info' => __DIR__ . '/../..' . '/src/device-detection/class-user-agent-info.php',
- 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
- );
-
- public static function getInitializer(ClassLoader $loader)
- {
- return \Closure::bind(function () use ($loader) {
- $loader->classMap = ComposerStaticInit6fe342bc02f0b440f7b3c8d8ade42286_super_cacheⓥ4_0_0_alpha::$classMap;
-
- }, null, ClassLoader::class);
- }
-}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
deleted file mode 100644
index f20a6c47..00000000
--- a/vendor/composer/installed.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "packages": [],
- "dev": false,
- "dev-package-names": []
-}
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
deleted file mode 100644
index 7253fa84..00000000
--- a/vendor/composer/installed.php
+++ /dev/null
@@ -1,23 +0,0 @@
- array(
- 'name' => 'automattic/wp-super-cache',
- 'pretty_version' => 'dev-trunk',
- 'version' => 'dev-trunk',
- 'reference' => null,
- 'type' => 'wordpress-plugin',
- 'install_path' => __DIR__ . '/../../',
- 'aliases' => array(),
- 'dev' => false,
- ),
- 'versions' => array(
- 'automattic/wp-super-cache' => array(
- 'pretty_version' => 'dev-trunk',
- 'version' => 'dev-trunk',
- 'reference' => null,
- 'type' => 'wordpress-plugin',
- 'install_path' => __DIR__ . '/../../',
- 'aliases' => array(),
- 'dev_requirement' => false,
- ),
- ),
-);