diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 00000000..b8a0d179 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,59 @@ +name: WP PHPUnit Tests + +on: + push: + branches: [ trunk ] + pull_request: + branches: [ trunk, develop ] + +jobs: + build: + strategy: + matrix: + operating-system: [ubuntu-latest] + php-versions: [ '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + runs-on: ${{ matrix.operating-system }} + services: + # Setup MYSQL + mysql-service: + image: 'mysql:5.7' + env: + MYSQL_ROOT_PASSWORD: 'poetry' + MYSQL_DATABASE: wlf_tests + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + steps: + - uses: actions/checkout@v4 + - name: Setup proper PHP version + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: composer + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer run-script packages-install + - name: Run Tests on Latest Version - WP6.8 + env: + environment_github: true + run: composer test:php + diff --git a/.gitignore b/.gitignore index 6b19221e..afb302d3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ wordpress/ tests/.env -composer.lock vendor/ # Do not track the project workspace for VSCode diff --git a/README.md b/README.md index da46a568..f459171c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ **License:** GPLv3 or later \ **License URI:** http://www.gnu.org/licenses/gpl-3.0.html +[![Unit Tests](https://github.com/a8cteam51/wayback-link-fixer/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/a8cteam51/wayback-link-fixer/actions/workflows/unit-tests.yml) + + ## Description Welcome to **Wayback Link Fixer**, a powerful tool designed to enhance your WordPress site by automatically scanning posts for links, retrieving the latest snapshots from the Wayback Machine, and seamlessly replacing broken links with archived versions. This innovative solution ensures that your posts remain resilient against `BITROT` , preserving the integrity of linked content over time. diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..b0aadb28 --- /dev/null +++ b/composer.lock @@ -0,0 +1,6360 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "aa6b5b0a826cd64e44f346822e45e811", + "packages": [], + "packages-dev": [ + { + "name": "a8cteam51/team51-configs", + "version": "dev-trunk", + "source": { + "type": "git", + "url": "https://github.com/a8cteam51/team51-configs.git", + "reference": "1f025c367e0287886d1f7490618950131fa8d491" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/a8cteam51/team51-configs/zipball/1f025c367e0287886d1f7490618950131fa8d491", + "reference": "1f025c367e0287886d1f7490618950131fa8d491", + "shasum": "" + }, + "require": { + "ext-json": "*", + "johnbillion/wp-compat": "^1", + "php": ">=8.3", + "phpcompatibility/phpcompatibility-wp": "*", + "phpmd/phpmd": "^2", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "swissspidy/phpstan-no-private": "^1", + "szepeviktor/phpstan-wordpress": "^2", + "wp-coding-standards/wpcs": "^3" + }, + "require-dev": { + "composer/composer": "^2" + }, + "default-branch": true, + "type": "library", + "autoload": { + "classmap": [ + "composer" + ] + }, + "scripts": { + "generate-autoloader": [ + "@composer dump-autoload --ignore-platform-reqs -o" + ], + "packages-install": [ + "@composer install --ignore-platform-reqs --no-interaction" + ], + "packages-update": [ + "@composer clear-cache", + "@composer update --prefer-stable --ignore-platform-reqs --no-interaction" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/a8cteam51/team51-configs/graphs/contributors" + } + ], + "description": "A collection of shared configuration files for Team51 projects.", + "support": { + "source": "https://github.com/a8cteam51/team51-configs/tree/trunk", + "issues": "https://github.com/a8cteam51/team51-configs/issues" + }, + "time": "2025-05-25T15:59:20+00:00" + }, + { + "name": "automattic/jetpack-constants", + "version": "v3.0.8", + "source": { + "type": "git", + "url": "https://github.com/Automattic/jetpack-constants.git", + "reference": "f9bf00ab48956b8326209e7c0baf247a0ed721c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/f9bf00ab48956b8326209e7c0baf247a0ed721c4", + "reference": "f9bf00ab48956b8326209e7c0baf247a0ed721c4", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "automattic/jetpack-changelogger": "^6.0.5", + "automattic/phpunit-select-config": "^1.0.3", + "brain/monkey": "^2.6.2", + "yoast/phpunit-polyfills": "^4.0.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-constants", + "branch-alias": { + "dev-trunk": "3.0.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-constants/compare/v${old}...v${new}" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "A wrapper for defining constants in a more testable way.", + "support": { + "source": "https://github.com/Automattic/jetpack-constants/tree/v3.0.8" + }, + "time": "2025-04-28T15:12:45+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": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "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": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "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-11-12T16:29:46+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "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.1.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "18a95476797ed480b3f2598984bc6f7e1eecc9a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/18a95476797ed480b3f2598984bc6f7e1eecc9a8", + "reference": "18a95476797ed480b3f2598984bc6f7e1eecc9a8", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.2", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "^2.2", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "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-05-28T16:05:48+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:23:10+00:00" + }, + { + "name": "eftec/bladeone", + "version": "3.52", + "source": { + "type": "git", + "url": "https://github.com/EFTEC/BladeOne.git", + "reference": "a19bf66917de0b29836983db87a455a4f6e32148" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EFTEC/BladeOne/zipball/a19bf66917de0b29836983db87a455a4f6e32148", + "reference": "a19bf66917de0b29836983db87a455a4f6e32148", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.6" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16.1", + "phpunit/phpunit": "^5.7", + "squizlabs/php_codesniffer": "^3.5.4" + }, + "suggest": { + "eftec/bladeonehtml": "Extension to create forms", + "ext-mbstring": "This extension is used if it's active" + }, + "type": "library", + "autoload": { + "psr-4": { + "eftec\\bladeone\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jorge Patricio Castro Castillo", + "email": "jcastro@eftec.cl" + } + ], + "description": "The standalone version Blade Template Engine from Laravel in a single php file", + "homepage": "https://github.com/EFTEC/BladeOne", + "keywords": [ + "blade", + "php", + "template", + "templating", + "view" + ], + "support": { + "issues": "https://github.com/EFTEC/BladeOne/issues", + "source": "https://github.com/EFTEC/BladeOne/tree/3.52" + }, + "time": "2021-04-17T13:49:01+00:00" + }, + { + "name": "gettext/gettext", + "version": "v4.8.12", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/Gettext.git", + "reference": "11af89ee6c087db3cf09ce2111a150bca5c46e12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/Gettext/zipball/11af89ee6c087db3cf09ce2111a150bca5c46e12", + "reference": "11af89ee6c087db3cf09ce2111a150bca5c46e12", + "shasum": "" + }, + "require": { + "gettext/languages": "^2.3", + "php": ">=5.4.0" + }, + "require-dev": { + "illuminate/view": "^5.0.x-dev", + "phpunit/phpunit": "^4.8|^5.7|^6.5", + "squizlabs/php_codesniffer": "^3.0", + "symfony/yaml": "~2", + "twig/extensions": "*", + "twig/twig": "^1.31|^2.0" + }, + "suggest": { + "illuminate/view": "Is necessary if you want to use the Blade extractor", + "symfony/yaml": "Is necessary if you want to use the Yaml extractor/generator", + "twig/extensions": "Is necessary if you want to use the Twig extractor", + "twig/twig": "Is necessary if you want to use the Twig extractor" + }, + "type": "library", + "autoload": { + "psr-4": { + "Gettext\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oscar Otero", + "email": "oom@oscarotero.com", + "homepage": "http://oscarotero.com", + "role": "Developer" + } + ], + "description": "PHP gettext manager", + "homepage": "https://github.com/oscarotero/Gettext", + "keywords": [ + "JS", + "gettext", + "i18n", + "mo", + "po", + "translation" + ], + "support": { + "email": "oom@oscarotero.com", + "issues": "https://github.com/oscarotero/Gettext/issues", + "source": "https://github.com/php-gettext/Gettext/tree/v4.8.12" + }, + "funding": [ + { + "url": "https://paypal.me/oscarotero", + "type": "custom" + }, + { + "url": "https://github.com/oscarotero", + "type": "github" + }, + { + "url": "https://www.patreon.com/misteroom", + "type": "patreon" + } + ], + "time": "2024-05-18T10:25:07+00:00" + }, + { + "name": "gettext/languages", + "version": "2.12.1", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/Languages.git", + "reference": "0b0b0851c55168e1dfb14305735c64019732b5f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/Languages/zipball/0b0b0851c55168e1dfb14305735c64019732b5f1", + "reference": "0b0b0851c55168e1dfb14305735c64019732b5f1", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4" + }, + "bin": [ + "bin/export-plural-rules", + "bin/import-cldr-data" + ], + "type": "library", + "autoload": { + "psr-4": { + "Gettext\\Languages\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michele Locati", + "email": "mlocati@gmail.com", + "role": "Developer" + } + ], + "description": "gettext languages with plural rules", + "homepage": "https://github.com/php-gettext/Languages", + "keywords": [ + "cldr", + "i18n", + "internationalization", + "l10n", + "language", + "languages", + "localization", + "php", + "plural", + "plural rules", + "plurals", + "translate", + "translations", + "unicode" + ], + "support": { + "issues": "https://github.com/php-gettext/Languages/issues", + "source": "https://github.com/php-gettext/Languages/tree/2.12.1" + }, + "funding": [ + { + "url": "https://paypal.me/mlocati", + "type": "custom" + }, + { + "url": "https://github.com/mlocati", + "type": "github" + } + ], + "time": "2025-03-19T11:14:02+00:00" + }, + { + "name": "gin0115/wpunit-helpers", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/gin0115/WPUnit_Helpers.git", + "reference": "7fe5e7d0b3a8b2949a2df3ef5daf7b7209b4748b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gin0115/WPUnit_Helpers/zipball/7fe5e7d0b3a8b2949a2df3ef5daf7b7209b4748b", + "reference": "7fe5e7d0b3a8b2949a2df3ef5daf7b7209b4748b", + "shasum": "" + }, + "require": { + "automattic/jetpack-constants": "*", + "php": ">=7.1.0", + "pinkcrab/function-constructors": "*" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "*", + "php-stubs/wordpress-stubs": "^6.0 || ^5.9", + "phpstan/phpstan": "^1.0", + "phpunit/phpunit": "^7.0 || ^8.0", + "roots/wordpress": "^6.1", + "symfony/var-dumper": "*", + "szepeviktor/phpstan-wordpress": "^1.0", + "vlucas/phpdotenv": "^5.4", + "wp-coding-standards/wpcs": "*", + "wp-phpunit/wp-phpunit": "^6.1", + "yoast/phpunit-polyfills": "^0.2.0 || ^1.0.0" + }, + "type": "library", + "autoload": { + "files": [], + "psr-4": { + "Gin0115\\WPUnit_Helpers\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Glynn Quelch", + "email": "glynn.quelch@pinkcrab.co.uk", + "homepage": "http://clappo.co.uk", + "role": "Developer" + } + ], + "description": "A series of helper classes, functions and traits for testing with WPUnit for WordPress", + "homepage": "https://pinkcrab.co.uk", + "support": { + "issues": "https://github.com/gin0115/WPUnit_Helpers/issues", + "source": "https://github.com/gin0115/WPUnit_Helpers/tree/1.1.1" + }, + "time": "2023-02-26T14:46:18+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:45:45+00:00" + }, + { + "name": "johnbillion/wp-compat", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/johnbillion/wp-compat.git", + "reference": "7677396167ffbff22708114e1e795a7e90d1ff7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/johnbillion/wp-compat/zipball/7677396167ffbff22708114e1e795a7e90d1ff7a", + "reference": "7677396167ffbff22708114e1e795a7e90d1ff7a", + "shasum": "" + }, + "require": { + "php": ">= 7.4", + "phpstan/phpstan": "^2.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "johnbillion/plugin-infrastructure": "dev-trunk", + "nikic/php-parser": "^5.1", + "php-stubs/wordpress-stubs": "^6.6", + "phpstan/phpstan-deprecation-rules": "2.0.0", + "phpstan/phpstan-phpunit": "2.0.1", + "phpstan/phpstan-strict-rules": "2.0.0", + "phpunit/phpunit": "^9.0", + "roots/wordpress-core-installer": "1.100.0", + "roots/wordpress-full": "^6.8.0", + "wp-coding-standards/wpcs": "3.1.0" + }, + "suggest": { + "phpstan/phpstan-deprecation-rules": "PHPStan rules for detecting usage of deprecated symbols", + "swissspidy/phpstan-no-private": "PHPStan rules for detecting usage of pseudo-private functions, classes, and methods", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "wordpress-install-dir": "vendor/wordpress/wordpress" + }, + "autoload": { + "psr-4": { + "WPCompat\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Blackbourn", + "homepage": "https://johnblackbourn.com/" + } + ], + "description": "PHPStan extension to help verify that your PHP code is compatible with a given version of WordPress", + "keywords": [ + "PHPStan", + "wordpress" + ], + "support": { + "issues": "https://github.com/johnbillion/wp-compat/issues", + "source": "https://github.com/johnbillion/wp-compat" + }, + "funding": [ + { + "url": "https://github.com/sponsors/johnbillion", + "type": "github" + } + ], + "time": "2025-04-16T18:49:28+00:00" + }, + { + "name": "mck89/peast", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/mck89/peast.git", + "reference": "3a752d39bd7d8dc1e19bcf424f3d5ac1a1ca6ad5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mck89/peast/zipball/3a752d39bd7d8dc1e19bcf424f3d5ac1a1ca6ad5", + "reference": "3a752d39bd7d8dc1e19bcf424f3d5ac1a1ca6ad5", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17.0-dev" + } + }, + "autoload": { + "psr-4": { + "Peast\\": "lib/Peast/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marco Marchiò", + "email": "marco.mm89@gmail.com" + } + ], + "description": "Peast is PHP library that generates AST for JavaScript code", + "support": { + "issues": "https://github.com/mck89/peast/issues", + "source": "https://github.com/mck89/peast/tree/v1.17.0" + }, + "time": "2025-03-07T19:44:14+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "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.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-04-29T12:36:36+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.5.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "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.0-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.5.0" + }, + "time": "2025-05-31T08:24:38+00:00" + }, + { + "name": "pdepend/pdepend", + "version": "2.16.2", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "shasum": "" + }, + "require": { + "php": ">=5.3.7", + "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/polyfill-mbstring": "^1.19" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0|^1.2.3", + "gregwar/rst": "^1.0", + "squizlabs/php_codesniffer": "^2.0.0" + }, + "bin": [ + "src/bin/pdepend" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PDepend\\": "src/main/php/PDepend" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of pdepend to be handled with Composer", + "keywords": [ + "PHP Depend", + "PHP_Depend", + "dev", + "pdepend" + ], + "support": { + "issues": "https://github.com/pdepend/pdepend/issues", + "source": "https://github.com/pdepend/pdepend/tree/2.16.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", + "type": "tidelift" + } + ], + "time": "2023-12-17T18:09:59+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.6.2", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-stubs.git", + "reference": "f50fd7ed45894d036e4fef9ab7e5bbbaff6a30cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/f50fd7ed45894d036e4fef9ab7e5bbbaff6a30cc", + "reference": "f50fd7ed45894d036e4fef9ab7e5bbbaff6a30cc", + "shasum": "" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "nikic/php-parser": "^4.13", + "php": "^7.4 || ^8.0", + "php-stubs/generator": "^0.8.3", + "phpdocumentor/reflection-docblock": "^5.4.1", + "phpstan/phpstan": "^1.10.49", + "phpunit/phpunit": "^9.5", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.0", + "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.6.2" + }, + "time": "2024-09-30T07:10:48+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.3", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/293975b465e0e709b571cbf0c957c6c0a7b9a2ac", + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^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" + } + ], + "time": "2024-04-24T21:30:46+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-wp", + "version": "2.1.7", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", + "reference": "5bfbbfbabb3df2b9a83e601de9153e4a7111962c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/5bfbbfbabb3df2b9a83e601de9153e4a7111962c", + "reference": "5bfbbfbabb3df2b9a83e601de9153e4a7111962c", + "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-05-12T16:38:37+00:00" + }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/fa4b8d051e278072928e32d817456a7fdb57b6ca", + "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.1.0", + "squizlabs/php_codesniffer": "^3.13.0 || ^4.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "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", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "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-06-14T07:40:39+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "65355670ac17c34cd235cf9d3ceae1b9252c4dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/65355670ac17c34cd235cf9d3ceae1b9252c4dad", + "reference": "65355670ac17c34cd235cf9d3ceae1b9252c4dad", + "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.13.0 || ^4.0" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.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", + "phpcs4", + "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" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-06-12T04:32:33+00:00" + }, + { + "name": "phpmd/phpmd", + "version": "2.15.0", + "source": { + "type": "git", + "url": "https://github.com/phpmd/phpmd.git", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", + "shasum": "" + }, + "require": { + "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", + "ext-xml": "*", + "pdepend/pdepend": "^2.16.1", + "php": ">=5.3.9" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0 || ^1.3.2", + "ext-json": "*", + "ext-simplexml": "*", + "gregwar/rst": "^1.0", + "mikey179/vfsstream": "^1.6.8", + "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2" + }, + "bin": [ + "src/bin/phpmd" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPMD\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Manuel Pichler", + "email": "github@manuel-pichler.de", + "homepage": "https://github.com/manuelpichler", + "role": "Project Founder" + }, + { + "name": "Marc Würth", + "email": "ravage@bluewin.ch", + "homepage": "https://github.com/ravage84", + "role": "Project Maintainer" + }, + { + "name": "Other contributors", + "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", + "role": "Contributors" + } + ], + "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", + "homepage": "https://phpmd.org/", + "keywords": [ + "dev", + "mess detection", + "mess detector", + "pdepend", + "phpmd", + "pmd" + ], + "support": { + "irc": "irc://irc.freenode.org/phpmd", + "issues": "https://github.com/phpmd/phpmd/issues", + "source": "https://github.com/phpmd/phpmd/tree/2.15.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd", + "type": "tidelift" + } + ], + "time": "2023-12-11T08:22:20+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:41:07+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" + }, + "time": "2024-09-04T20:21:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.17", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053", + "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-05-21T20:55:28+00:00" + }, + { + "name": "phpstan/phpstan-deprecation-rules", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", + "reference": "468e02c9176891cc901143da118f09dc9505fc2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/468e02c9176891cc901143da118f09dc9505fc2f", + "reference": "468e02c9176891cc901143da118f09dc9505fc2f", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.15" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "support": { + "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.3" + }, + "time": "2025-05-14T10:56:57+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/3e139cbe67fafa3588e1dbe27ca50f31fdb6236a", + "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.4" + }, + "time": "2025-03-18T11:42:40+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.32", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.2.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" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:23:01+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-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": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.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": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.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": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.23", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.5.0 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.1", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" + }, + "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" + } + ], + "time": "2025-05-02T06:40:34+00:00" + }, + { + "name": "pinkcrab/function-constructors", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/gin0115/pinkcrab_function_constructors.git", + "reference": "1c6e6fc9fa27fdf170af0f296237b4c88386c489" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gin0115/pinkcrab_function_constructors/zipball/1c6e6fc9fa27fdf170af0f296237b4c88386c489", + "reference": "1c6e6fc9fa27fdf170af0f296237b4c88386c489", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3", + "phpstan/phpstan": "^1.0", + "phpunit/phpunit": "^7.0 || ^9.0", + "yoast/phpunit-polyfills": "^1.0.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/function-constants.php", + "src/constants.php", + "src/procedural.php", + "src/arrays.php", + "src/comparisons.php", + "src/general.php", + "src/strings.php", + "src/numbers.php", + "src/objects.php" + ], + "psr-4": { + "PinkCrab\\FunctionConstructors\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Glynn Quelch", + "email": "glynn.quelch@pinkcrab.co.uk", + "homepage": "https://github.com/gin0115", + "role": "Developer" + } + ], + "description": "A collection of functions to make working with the standard php library a little easier for function composition. Allows the creation of partially applied library functions, to work as close to pure fucntions as possible.", + "homepage": "https://pinkcrab.co.uk", + "keywords": [ + "currying", + "fp", + "functional", + "partial application", + "piping" + ], + "support": { + "issues": "https://github.com/gin0115/pinkcrab_function_constructors/issues", + "source": "https://github.com/gin0115/pinkcrab_function_constructors/tree/0.2.0" + }, + "time": "2023-01-28T01:15:55+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "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": "roave/security-advisories", + "version": "dev-latest", + "source": { + "type": "git", + "url": "https://github.com/Roave/SecurityAdvisories.git", + "reference": "3f898892b8f57730ba475903d5a38e3a305476e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/3f898892b8f57730ba475903d5a38e3a305476e2", + "reference": "3f898892b8f57730ba475903d5a38e3a305476e2", + "shasum": "" + }, + "conflict": { + "3f/pygmentize": "<1.2", + "adaptcms/adaptcms": "<=1.3", + "admidio/admidio": "<4.3.12", + "adodb/adodb-php": "<=5.22.8", + "aheinze/cockpit": "<2.2", + "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2", + "aimeos/ai-admin-jsonadm": "<2020.10.13|>=2021.04.1,<2021.10.6|>=2022.04.1,<2022.10.3|>=2023.04.1,<2023.10.4|==2024.04.1", + "aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.22|>=2022.04.1,<2022.10.13|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.04.7", + "aimeos/ai-controller-frontend": "<2020.10.15|>=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.8|>=2023.04.1,<2023.10.9|==2024.04.1", + "aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7", + "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", + "airesvsg/acf-to-rest-api": "<=3.1", + "akaunting/akaunting": "<2.1.13", + "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", + "alextselegidis/easyappointments": "<=1.5.1", + "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", + "amazing/media2click": ">=1,<1.3.3", + "ameos/ameos_tarteaucitron": "<1.2.23", + "amphp/artax": "<1.0.6|>=2,<2.0.6", + "amphp/http": "<=1.7.2|>=2,<=2.1", + "amphp/http-client": ">=4,<4.4", + "anchorcms/anchor-cms": "<=0.12.7", + "andreapollastri/cipi": "<=3.1.15", + "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<1.0.2|>=2,<2.2.5", + "aoe/restler": "<1.7.1", + "apache-solr-for-typo3/solr": "<2.8.3", + "apereo/phpcas": "<1.6", + "api-platform/core": "<3.4.17|>=4.0.0.0-alpha1,<4.0.22", + "api-platform/graphql": "<3.4.17|>=4.0.0.0-alpha1,<4.0.22", + "appwrite/server-ce": "<=1.2.1", + "arc/web": "<3", + "area17/twill": "<1.2.5|>=2,<2.5.3", + "artesaos/seotools": "<0.17.2", + "asymmetricrypt/asymmetricrypt": "<9.9.99", + "athlon1600/php-proxy": "<=5.1", + "athlon1600/php-proxy-app": "<=3", + "athlon1600/youtube-downloader": "<=4", + "austintoddj/canvas": "<=3.4.2", + "auth0/auth0-php": ">=8.0.0.0-beta1,<8.14", + "auth0/login": "<7.17", + "auth0/symfony": "<5.4", + "auth0/wordpress": "<5.3", + "automad/automad": "<2.0.0.0-alpha5", + "automattic/jetpack": "<9.8", + "awesome-support/awesome-support": "<=6.0.7", + "aws/aws-sdk-php": "<3.288.1", + "azuracast/azuracast": "<0.18.3", + "b13/seo_basics": "<0.8.2", + "backdrop/backdrop": "<1.27.3|>=1.28,<1.28.2", + "backpack/crud": "<3.4.9", + "backpack/filemanager": "<2.0.2|>=3,<3.0.9", + "bacula-web/bacula-web": "<8.0.0.0-RC2-dev", + "badaso/core": "<2.7", + "bagisto/bagisto": "<2.1", + "barrelstrength/sprout-base-email": "<1.2.7", + "barrelstrength/sprout-forms": "<3.9", + "barryvdh/laravel-translation-manager": "<0.6.8", + "barzahlen/barzahlen-php": "<2.0.1", + "baserproject/basercms": "<=5.1.1", + "bassjobsen/bootstrap-3-typeahead": ">4.0.2", + "bbpress/bbpress": "<2.6.5", + "bcit-ci/codeigniter": "<3.1.3", + "bcosca/fatfree": "<3.7.2", + "bedita/bedita": "<4", + "bednee/cooluri": "<1.0.30", + "bigfork/silverstripe-form-capture": ">=3,<3.1.1", + "billz/raspap-webgui": "<=3.1.4", + "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", + "blueimp/jquery-file-upload": "==6.4.4", + "bmarshall511/wordpress_zero_spam": "<5.2.13", + "bolt/bolt": "<3.7.2", + "bolt/core": "<=4.2", + "born05/craft-twofactorauthentication": "<3.3.4", + "bottelet/flarepoint": "<2.2.1", + "bref/bref": "<2.1.17", + "brightlocal/phpwhois": "<=4.2.5", + "brotkrueml/codehighlight": "<2.7", + "brotkrueml/schema": "<1.13.1|>=2,<2.5.1", + "brotkrueml/typo3-matomo-integration": "<1.3.2", + "buddypress/buddypress": "<7.2.1", + "bugsnag/bugsnag-laravel": ">=2,<2.0.2", + "bvbmedia/multishop": "<2.0.39", + "bytefury/crater": "<6.0.2", + "cachethq/cachet": "<2.5.1", + "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", + "cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", + "cardgate/magento2": "<2.0.33", + "cardgate/woocommerce": "<=3.1.15", + "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cart2quote/module-quotation-encoded": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cartalyst/sentry": "<=2.1.6", + "catfan/medoo": "<1.7.5", + "causal/oidc": "<4", + "cecil/cecil": "<7.47.1", + "centreon/centreon": "<22.10.15", + "cesnet/simplesamlphp-module-proxystatistics": "<3.1", + "chriskacerguis/codeigniter-restserver": "<=2.7.1", + "chrome-php/chrome": "<1.14", + "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", + "ckeditor/ckeditor": "<4.25", + "clickstorm/cs-seo": ">=6,<6.8|>=7,<7.5|>=8,<8.4|>=9,<9.3", + "co-stack/fal_sftp": "<0.2.6", + "cockpit-hq/cockpit": "<2.7|==2.7", + "codeception/codeception": "<3.1.3|>=4,<4.1.22", + "codeigniter/framework": "<3.1.10", + "codeigniter4/framework": "<4.5.8", + "codeigniter4/shield": "<1.0.0.0-beta8", + "codiad/codiad": "<=2.8.4", + "codingms/additional-tca": ">=1.7,<1.15.17|>=1.16,<1.16.9", + "commerceteam/commerce": ">=0.9.6,<0.9.9", + "components/jquery": ">=1.0.3,<3.5", + "composer/composer": "<1.10.27|>=2,<2.2.24|>=2.3,<2.7.7", + "concrete5/concrete5": "<9.4.0.0-RC2-dev", + "concrete5/core": "<8.5.8|>=9,<9.1", + "contao-components/mediaelement": ">=2.14.2,<2.21.1", + "contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4", + "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4", + "contao/core": "<3.5.39", + "contao/core-bundle": "<4.13.54|>=5,<5.3.30|>=5.4,<5.5.6", + "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8", + "contao/managed-edition": "<=1.5", + "corveda/phpsandbox": "<1.3.5", + "cosenary/instagram": "<=2.3", + "couleurcitron/tarteaucitron-wp": "<0.3", + "craftcms/cms": "<4.15.3|>=5,<5.7.5", + "croogo/croogo": "<4", + "cuyz/valinor": "<0.12", + "czim/file-handling": "<1.5|>=2,<2.3", + "czproject/git-php": "<4.0.3", + "damienharper/auditor-bundle": "<5.2.6", + "dapphp/securimage": "<3.6.6", + "darylldoyle/safe-svg": "<1.9.10", + "datadog/dd-trace": ">=0.30,<0.30.2", + "datatables/datatables": "<1.10.10", + "david-garcia/phpwhois": "<=4.3.1", + "dbrisinajumi/d2files": "<1", + "dcat/laravel-admin": "<=2.1.3|==2.2.0.0-beta|==2.2.2.0-beta", + "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", + "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", + "desperado/xml-bundle": "<=0.1.7", + "dev-lancer/minecraft-motd-parser": "<=1.0.5", + "devgroup/dotplant": "<2020.09.14-dev", + "digimix/wp-svg-upload": "<=1", + "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", + "dl/yag": "<3.0.1", + "dmk/webkitpdf": "<1.1.4", + "dnadesign/silverstripe-elemental": "<5.3.12", + "doctrine/annotations": "<1.2.7", + "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", + "doctrine/common": "<2.4.3|>=2.5,<2.5.1", + "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2|>=3,<3.1.4", + "doctrine/doctrine-bundle": "<1.5.2", + "doctrine/doctrine-module": "<0.7.2", + "doctrine/mongodb-odm": "<1.0.2", + "doctrine/mongodb-odm-bundle": "<3.0.1", + "doctrine/orm": ">=1,<1.2.4|>=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", + "dolibarr/dolibarr": "<19.0.2|==21.0.0.0-beta", + "dompdf/dompdf": "<2.0.4", + "doublethreedigital/guest-entries": "<3.1.2", + "drupal/admin_audit_trail": "<1.0.5", + "drupal/ai": "<1.0.5", + "drupal/alogin": "<2.0.6", + "drupal/cache_utility": "<1.2.1", + "drupal/commerce_alphabank_redirect": "<1.0.3", + "drupal/commerce_eurobank_redirect": "<2.1.1", + "drupal/config_split": "<1.10|>=2,<2.0.2", + "drupal/core": ">=6,<6.38|>=7,<7.102|>=8,<10.3.14|>=10.4,<10.4.5|>=11,<11.0.13|>=11.1,<11.1.5", + "drupal/core-recommended": ">=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", + "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", + "drupal/formatter_suite": "<2.1", + "drupal/gdpr": "<3.0.1|>=3.1,<3.1.2", + "drupal/google_tag": "<1.8|>=2,<2.0.8", + "drupal/ignition": "<1.0.4", + "drupal/lightgallery": "<1.6", + "drupal/link_field_display_mode_formatter": "<1.6", + "drupal/matomo": "<1.24", + "drupal/oauth2_client": "<4.1.3", + "drupal/oauth2_server": "<2.1", + "drupal/obfuscate": "<2.0.1", + "drupal/quick_node_block": "<2", + "drupal/rapidoc_elements_field_formatter": "<1.0.1", + "drupal/spamspan": "<3.2.1", + "drupal/tfa": "<1.10", + "duncanmcclean/guest-entries": "<3.1.2", + "dweeves/magmi": "<=0.7.24", + "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.1.2", + "ecodev/newsletter": "<=4", + "ectouch/ectouch": "<=2.7.2", + "egroupware/egroupware": "<23.1.20240624", + "elefant/cms": "<2.0.7", + "elgg/elgg": "<3.3.24|>=4,<4.0.5", + "elijaa/phpmemcacheadmin": "<=1.3", + "elmsln/haxcms": "<11", + "encore/laravel-admin": "<=1.8.19", + "endroid/qr-code-bundle": "<3.4.2", + "enhavo/enhavo-app": "<=0.13.1", + "enshrined/svg-sanitize": "<0.15", + "erusev/parsedown": "<1.7.2", + "ether/logs": "<3.0.4", + "evolutioncms/evolution": "<=3.2.3", + "exceedone/exment": "<4.4.3|>=5,<5.0.3", + "exceedone/laravel-admin": "<2.2.3|==3", + "ezsystems/demobundle": ">=5.4,<5.4.6.1-dev", + "ezsystems/ez-support-tools": ">=2.2,<2.2.3", + "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev", + "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev", + "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", + "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.38|>=3.3,<3.3.39", + "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1|>=5.3.0.0-beta1,<5.3.5", + "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", + "ezsystems/ezplatform-http-cache": "<2.3.16", + "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.35", + "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", + "ezsystems/ezplatform-richtext": ">=2.3,<2.3.26|>=3.3,<3.3.40", + "ezsystems/ezplatform-solr-search-engine": ">=1.7,<1.7.12|>=2,<2.0.2|>=3.3,<3.3.15", + "ezsystems/ezplatform-user": ">=1,<1.0.1", + "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31", + "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", + "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", + "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", + "ezyang/htmlpurifier": "<=4.2", + "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", + "facturascripts/facturascripts": "<=2022.08", + "fastly/magento2": "<1.2.26", + "feehi/cms": "<=2.1.1", + "feehi/feehicms": "<=2.1.1", + "fenom/fenom": "<=2.12.1", + "filament/actions": ">=3.2,<3.2.123", + "filament/infolists": ">=3,<3.2.115", + "filament/tables": ">=3,<3.2.115", + "filegator/filegator": "<7.8", + "filp/whoops": "<2.1.13", + "fineuploader/php-traditional-server": "<=1.2.2", + "firebase/php-jwt": "<6", + "fisharebest/webtrees": "<=2.1.18", + "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", + "fixpunkt/fp-newsletter": "<1.1.1|>=1.2,<2.1.2|>=2.2,<3.2.6", + "flarum/core": "<1.8.10", + "flarum/flarum": "<0.1.0.0-beta8", + "flarum/framework": "<1.8.10", + "flarum/mentions": "<1.6.3", + "flarum/sticky": ">=0.1.0.0-beta14,<=0.1.0.0-beta15", + "flarum/tags": "<=0.1.0.0-beta13", + "floriangaerber/magnesium": "<0.3.1", + "fluidtypo3/vhs": "<5.1.1", + "fof/byobu": ">=0.3.0.0-beta2,<1.1.7", + "fof/upload": "<1.2.3", + "foodcoopshop/foodcoopshop": ">=3.2,<3.6.1", + "fooman/tcpdf": "<6.2.22", + "forkcms/forkcms": "<5.11.1", + "fossar/tcpdf-parser": "<6.2.22", + "francoisjacquet/rosariosis": "<=11.5.1", + "frappant/frp-form-answers": "<3.1.2|>=4,<4.0.2", + "friendsofsymfony/oauth2-php": "<1.3", + "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", + "friendsofsymfony/user-bundle": ">=1,<1.3.5", + "friendsofsymfony1/swiftmailer": ">=4,<5.4.13|>=6,<6.2.5", + "friendsofsymfony1/symfony1": ">=1.1,<1.5.19", + "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", + "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", + "froala/wysiwyg-editor": "<=4.3", + "froxlor/froxlor": "<=2.2.5", + "frozennode/administrator": "<=5.0.12", + "fuel/core": "<1.8.1", + "funadmin/funadmin": "<=5.0.2", + "gaoming13/wechat-php-sdk": "<=1.10.2", + "genix/cms": "<=1.1.11", + "georgringer/news": "<1.3.3", + "geshi/geshi": "<=1.0.9.1", + "getformwork/formwork": "<1.13.1|>=2.0.0.0-beta1,<2.0.0.0-beta4", + "getgrav/grav": "<1.7.46", + "getkirby/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", + "getkirby/kirby": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", + "getkirby/panel": "<2.5.14", + "getkirby/starterkit": "<=3.7.0.2", + "gilacms/gila": "<=1.15.4", + "gleez/cms": "<=1.3|==2", + "globalpayments/php-sdk": "<2", + "goalgorilla/open_social": "<12.3.11|>=12.4,<12.4.10|>=13.0.0.0-alpha1,<13.0.0.0-alpha11", + "gogentooss/samlbase": "<1.2.7", + "google/protobuf": "<3.15", + "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", + "gree/jose": "<2.2.1", + "gregwar/rst": "<1.0.3", + "grumpydictator/firefly-iii": "<6.1.17", + "gugoan/economizzer": "<=0.9.0.0-beta1", + "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", + "guzzlehttp/oauth-subscriber": "<0.8.1", + "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", + "haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2", + "handcraftedinthealps/goodby-csv": "<1.4.3", + "harvesthq/chosen": "<1.8.7", + "helloxz/imgurl": "<=2.31", + "hhxsv5/laravel-s": "<3.7.36", + "hillelcoren/invoice-ninja": "<5.3.35", + "himiklab/yii2-jqgrid-widget": "<1.0.8", + "hjue/justwriting": "<=1", + "hov/jobfair": "<1.0.13|>=2,<2.0.2", + "httpsoft/http-message": "<1.0.12", + "hyn/multi-tenant": ">=5.6,<5.7.2", + "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6,<4.6.21", + "ibexa/admin-ui-assets": ">=4.6.0.0-alpha1,<4.6.21", + "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.6|>=4.6,<4.6.2", + "ibexa/fieldtype-richtext": ">=4.6,<4.6.21", + "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", + "ibexa/http-cache": ">=4.6,<4.6.14", + "ibexa/post-install": "<1.0.16|>=4.6,<4.6.14", + "ibexa/solr": ">=4.5,<4.5.4", + "ibexa/user": ">=4,<4.4.3", + "icecoder/icecoder": "<=8.1", + "idno/known": "<=1.3.1", + "ilicmiljan/secure-props": ">=1.2,<1.2.2", + "illuminate/auth": "<5.5.10", + "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<6.18.31|>=7,<7.22.4", + "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40", + "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", + "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", + "imdbphp/imdbphp": "<=5.1.1", + "impresscms/impresscms": "<=1.4.5", + "impresspages/impresspages": "<1.0.13", + "in2code/femanager": "<5.5.5|>=6,<6.4.1|>=7,<7.4.2|>=8,<8.2.2", + "in2code/ipandlanguageredirect": "<5.1.2", + "in2code/lux": "<17.6.1|>=18,<24.0.2", + "in2code/powermail": "<7.5.1|>=8,<8.5.1|>=9,<10.9.1|>=11,<12.4.1", + "innologi/typo3-appointments": "<2.0.6", + "intelliants/subrion": "<4.2.2", + "inter-mediator/inter-mediator": "==5.5", + "ipl/web": "<0.10.1", + "islandora/crayfish": "<4.1", + "islandora/islandora": ">=2,<2.4.1", + "ivankristianto/phpwhois": "<=4.3", + "jackalope/jackalope-doctrine-dbal": "<1.7.4", + "jambagecom/div2007": "<0.10.2", + "james-heinrich/getid3": "<1.9.21", + "james-heinrich/phpthumb": "<1.7.12", + "jasig/phpcas": "<1.3.3", + "jbartels/wec-map": "<3.0.3", + "jcbrand/converse.js": "<3.3.3", + "joelbutcher/socialstream": "<5.6|>=6,<6.2", + "johnbillion/wp-crontrol": "<1.16.2", + "joomla/application": "<1.0.13", + "joomla/archive": "<1.1.12|>=2,<2.0.1", + "joomla/database": ">=1,<2.2|>=3,<3.4", + "joomla/filesystem": "<1.6.2|>=2,<2.0.1", + "joomla/filter": "<1.4.4|>=2,<2.0.1", + "joomla/framework": "<1.5.7|>=2.5.4,<=3.8.12", + "joomla/input": ">=2,<2.0.2", + "joomla/joomla-cms": "<3.9.12|>=4,<4.4.13|>=5,<5.2.6", + "joomla/joomla-platform": "<1.5.4", + "joomla/session": "<1.3.1", + "joyqi/hyper-down": "<=2.4.27", + "jsdecena/laracom": "<2.0.9", + "jsmitty12/phpwhois": "<5.1", + "juzaweb/cms": "<=3.4.2", + "jweiland/events2": "<8.3.8|>=9,<9.0.6", + "jweiland/kk-downloader": "<1.2.2", + "kazist/phpwhois": "<=4.2.6", + "kelvinmo/simplexrd": "<3.1.1", + "kevinpapst/kimai2": "<1.16.7", + "khodakhah/nodcms": "<=3", + "kimai/kimai": "<=2.20.1", + "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", + "klaviyo/magento2-extension": ">=1,<3", + "knplabs/knp-snappy": "<=1.4.2", + "kohana/core": "<3.3.3", + "koillection/koillection": "<1.6.12", + "krayin/laravel-crm": "<=1.3", + "kreait/firebase-php": ">=3.2,<3.8.1", + "kumbiaphp/kumbiapp": "<=1.1.1", + "la-haute-societe/tcpdf": "<6.2.22", + "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", + "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", + "laminas/laminas-http": "<2.14.2", + "lara-zeus/artemis": ">=1,<=1.0.6", + "lara-zeus/dynamic-dashboard": ">=3,<=3.0.1", + "laravel/fortify": "<1.11.1", + "laravel/framework": "<10.48.29|>=11,<11.44.1|>=12,<12.1.1", + "laravel/laravel": ">=5.4,<5.4.22", + "laravel/pulse": "<1.3.1", + "laravel/reverb": "<1.4", + "laravel/socialite": ">=1,<2.0.10", + "latte/latte": "<2.10.8", + "lavalite/cms": "<=9|==10.1", + "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5", + "league/commonmark": "<2.7", + "league/flysystem": "<1.1.4|>=2,<2.1.1", + "league/oauth2-server": ">=8.3.2,<8.4.2|>=8.5,<8.5.3", + "leantime/leantime": "<3.3", + "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", + "libreform/libreform": ">=2,<=2.0.8", + "librenms/librenms": "<2017.08.18", + "liftkit/database": "<2.13.2", + "lightsaml/lightsaml": "<1.3.5", + "limesurvey/limesurvey": "<6.5.12", + "livehelperchat/livehelperchat": "<=3.91", + "livewire/livewire": "<2.12.7|>=3.0.0.0-beta1,<3.5.2", + "livewire/volt": "<1.7", + "lms/routes": "<2.1.1", + "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", + "lomkit/laravel-rest-api": "<2.13", + "luracast/restler": "<3.1", + "luyadev/yii-helpers": "<1.2.1", + "macropay-solutions/laravel-crud-wizard-free": "<3.4.17", + "maestroerror/php-heic-to-jpg": "<1.0.5", + "magento/community-edition": "<2.4.5|==2.4.5|>=2.4.5.0-patch1,<2.4.5.0-patch12|==2.4.6|>=2.4.6.0-patch1,<2.4.6.0-patch10|>=2.4.7.0-beta1,<2.4.7.0-patch5|>=2.4.8.0-beta1,<2.4.8.0-beta2", + "magento/core": "<=1.9.4.5", + "magento/magento1ce": "<1.9.4.3-dev", + "magento/magento1ee": ">=1,<1.14.4.3-dev", + "magento/product-community-edition": "<2.4.4.0-patch9|>=2.4.5,<2.4.5.0-patch8|>=2.4.6,<2.4.6.0-patch6|>=2.4.7,<2.4.7.0-patch1", + "magento/project-community-edition": "<=2.0.2", + "magneto/core": "<1.9.4.4-dev", + "maikuolan/phpmussel": ">=1,<1.6", + "mainwp/mainwp": "<=4.4.3.3", + "mantisbt/mantisbt": "<=2.26.3", + "marcwillmann/turn": "<0.3.3", + "matomo/matomo": "<1.11", + "matyhtf/framework": "<3.0.6", + "mautic/core": "<5.2.6|>=6.0.0.0-alpha,<6.0.2", + "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1", + "maximebf/debugbar": "<1.19", + "mdanter/ecc": "<2", + "mediawiki/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2", + "mediawiki/cargo": "<3.6.1", + "mediawiki/core": "<1.39.5|==1.40", + "mediawiki/data-transfer": ">=1.39,<1.39.11|>=1.41,<1.41.3|>=1.42,<1.42.2", + "mediawiki/matomo": "<2.4.3", + "mediawiki/semantic-media-wiki": "<4.0.2", + "mehrwert/phpmyadmin": "<3.2", + "melisplatform/melis-asset-manager": "<5.0.1", + "melisplatform/melis-cms": "<5.0.1", + "melisplatform/melis-front": "<5.0.1", + "mezzio/mezzio-swoole": "<3.7|>=4,<4.3", + "mgallegos/laravel-jqgrid": "<=1.3", + "microsoft/microsoft-graph": ">=1.16,<1.109.1|>=2,<2.0.1", + "microsoft/microsoft-graph-beta": "<2.0.1", + "microsoft/microsoft-graph-core": "<2.0.2", + "microweber/microweber": "<=2.0.16", + "mikehaertl/php-shellcommand": "<1.6.1", + "miniorange/miniorange-saml": "<1.4.3", + "mittwald/typo3_forum": "<1.2.1", + "mobiledetect/mobiledetectlib": "<2.8.32", + "modx/revolution": "<=3.1", + "mojo42/jirafeau": "<4.4", + "mongodb/mongodb": ">=1,<1.9.2", + "monolog/monolog": ">=1.8,<1.12", + "moodle/moodle": "<4.3.12|>=4.4,<4.4.8|>=4.5.0.0-beta,<4.5.4", + "mos/cimage": "<0.7.19", + "movim/moxl": ">=0.8,<=0.10", + "movingbytes/social-network": "<=1.2.1", + "mpdf/mpdf": "<=7.1.7", + "munkireport/comment": "<4.1", + "munkireport/managedinstalls": "<2.6", + "munkireport/munki_facts": "<1.5", + "munkireport/munkireport": ">=2.5.3,<5.6.3", + "munkireport/reportdata": "<3.5", + "munkireport/softwareupdate": "<1.6", + "mustache/mustache": ">=2,<2.14.1", + "mwdelaney/wp-enable-svg": "<=0.2", + "namshi/jose": "<2.2", + "nasirkhan/laravel-starter": "<11.11", + "nategood/httpful": "<1", + "neoan3-apps/template": "<1.1.1", + "neorazorx/facturascripts": "<2022.04", + "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", + "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3", + "neos/media-browser": "<7.3.19|>=8,<8.0.16|>=8.1,<8.1.11|>=8.2,<8.2.11|>=8.3,<8.3.9", + "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", + "neos/swiftmailer": "<5.4.5", + "nesbot/carbon": "<2.72.6|>=3,<3.8.4", + "netcarver/textile": "<=4.1.2", + "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", + "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", + "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", + "nilsteampassnet/teampass": "<3.1.3.1-dev", + "nitsan/ns-backup": "<13.0.1", + "nonfiction/nterchange": "<4.1.1", + "notrinos/notrinos-erp": "<=0.7", + "noumo/easyii": "<=0.9", + "novaksolutions/infusionsoft-php-sdk": "<1", + "nukeviet/nukeviet": "<4.5.02", + "nyholm/psr7": "<1.6.1", + "nystudio107/craft-seomatic": "<3.4.12", + "nzedb/nzedb": "<0.8", + "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", + "october/backend": "<1.1.2", + "october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1", + "october/october": "<3.7.5", + "october/rain": "<1.0.472|>=1.1,<1.1.2", + "october/system": "<3.7.5", + "oliverklee/phpunit": "<3.5.15", + "omeka/omeka-s": "<4.0.3", + "onelogin/php-saml": "<2.10.4", + "oneup/uploader-bundle": ">=1,<1.9.3|>=2,<2.1.5", + "open-web-analytics/open-web-analytics": "<1.7.4", + "opencart/opencart": ">=0", + "openid/php-openid": "<2.3", + "openmage/magento-lts": "<20.12.3", + "opensolutions/vimbadmin": "<=3.0.15", + "opensource-workshop/connect-cms": "<1.8.7|>=2,<2.4.7", + "orchid/platform": ">=8,<14.43", + "oro/calendar-bundle": ">=4.2,<=4.2.6|>=5,<=5.0.6|>=5.1,<5.1.1", + "oro/commerce": ">=4.1,<5.0.11|>=5.1,<5.1.1", + "oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7", + "oro/crm-call-bundle": ">=4.2,<=4.2.5|>=5,<5.0.4|>=5.1,<5.1.1", + "oro/customer-portal": ">=4.1,<=4.1.13|>=4.2,<=4.2.10|>=5,<=5.0.11|>=5.1,<=5.1.3", + "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<=4.2.10|>=5,<=5.0.12|>=5.1,<=5.1.3", + "oveleon/contao-cookiebar": "<1.16.3|>=2,<2.1.3", + "oxid-esales/oxideshop-ce": "<=7.0.5", + "oxid-esales/paymorrow-module": ">=1,<1.0.2|>=2,<2.0.1", + "packbackbooks/lti-1-3-php-library": "<5", + "padraic/humbug_get_contents": "<1.1.2", + "pagarme/pagarme-php": "<3", + "pagekit/pagekit": "<=1.0.18", + "paragonie/ecc": "<2.0.1", + "paragonie/random_compat": "<2", + "passbolt/passbolt_api": "<4.6.2", + "paypal/adaptivepayments-sdk-php": "<=3.9.2", + "paypal/invoice-sdk-php": "<=3.9", + "paypal/merchant-sdk-php": "<3.12", + "paypal/permissions-sdk-php": "<=3.9.1", + "pear/archive_tar": "<1.4.14", + "pear/auth": "<1.2.4", + "pear/crypt_gpg": "<1.6.7", + "pear/http_request2": "<2.7", + "pear/pear": "<=1.10.1", + "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", + "personnummer/personnummer": "<3.0.2", + "phanan/koel": "<5.1.4", + "phenx/php-svg-lib": "<0.5.2", + "php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5", + "php-mod/curl": "<2.3.2", + "phpbb/phpbb": "<3.3.11", + "phpems/phpems": ">=6,<=6.1.3", + "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", + "phpmailer/phpmailer": "<6.5", + "phpmussel/phpmussel": ">=1,<1.6", + "phpmyadmin/phpmyadmin": "<5.2.2", + "phpmyfaq/phpmyfaq": "<3.2.5|==3.2.5|>=3.2.10,<=4.0.1", + "phpoffice/common": "<0.2.9", + "phpoffice/math": "<=0.2", + "phpoffice/phpexcel": "<=1.8.2", + "phpoffice/phpspreadsheet": "<1.29.9|>=2,<2.1.8|>=2.2,<2.3.7|>=3,<3.9", + "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", + "phpservermon/phpservermon": "<3.6", + "phpsysinfo/phpsysinfo": "<3.4.3", + "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", + "phpwhois/phpwhois": "<=4.2.5", + "phpxmlrpc/extras": "<0.6.1", + "phpxmlrpc/phpxmlrpc": "<4.9.2", + "pi/pi": "<=2.5", + "pimcore/admin-ui-classic-bundle": "<1.7.6", + "pimcore/customer-management-framework-bundle": "<4.2.1", + "pimcore/data-hub": "<1.2.4", + "pimcore/data-importer": "<1.8.9|>=1.9,<1.9.3", + "pimcore/demo": "<10.3", + "pimcore/ecommerce-framework-bundle": "<1.0.10", + "pimcore/perspective-editor": "<1.5.1", + "pimcore/pimcore": "<11.5.4", + "piwik/piwik": "<1.11", + "pixelfed/pixelfed": "<0.12.5", + "plotly/plotly.js": "<2.25.2", + "pocketmine/bedrock-protocol": "<8.0.2", + "pocketmine/pocketmine-mp": "<5.25.2", + "pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1", + "pressbooks/pressbooks": "<5.18", + "prestashop/autoupgrade": ">=4,<4.10.1", + "prestashop/blockreassurance": "<=5.1.3", + "prestashop/blockwishlist": ">=2,<2.1.1", + "prestashop/contactform": ">=1.0.1,<4.3", + "prestashop/gamification": "<2.3.2", + "prestashop/prestashop": "<8.1.6", + "prestashop/productcomments": "<5.0.2", + "prestashop/ps_contactinfo": "<=3.3.2", + "prestashop/ps_emailsubscription": "<2.6.1", + "prestashop/ps_facetedsearch": "<3.4.1", + "prestashop/ps_linklist": "<3.1", + "privatebin/privatebin": "<1.4|>=1.5,<1.7.4", + "processwire/processwire": "<=3.0.229", + "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", + "propel/propel1": ">=1,<=1.7.1", + "pterodactyl/panel": "<=1.11.10", + "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", + "ptrofimov/beanstalk_console": "<1.7.14", + "pubnub/pubnub": "<6.1", + "punktde/pt_extbase": "<1.5.1", + "pusher/pusher-php-server": "<2.2.1", + "pwweb/laravel-core": "<=0.3.6.0-beta", + "pxlrbt/filament-excel": "<1.1.14|>=2.0.0.0-alpha,<2.3.3", + "pyrocms/pyrocms": "<=3.9.1", + "qcubed/qcubed": "<=3.1.1", + "quickapps/cms": "<=2.0.0.0-beta2", + "rainlab/blog-plugin": "<1.4.1", + "rainlab/debugbar-plugin": "<3.1", + "rainlab/user-plugin": "<=1.4.5", + "rankmath/seo-by-rank-math": "<=1.0.95", + "rap2hpoutre/laravel-log-viewer": "<0.13", + "react/http": ">=0.7,<1.9", + "really-simple-plugins/complianz-gdpr": "<6.4.2", + "redaxo/source": "<5.18.3", + "remdex/livehelperchat": "<4.29", + "renolit/reint-downloadmanager": "<4.0.2|>=5,<5.0.1", + "reportico-web/reportico": "<=8.1", + "rhukster/dom-sanitizer": "<1.0.7", + "rmccue/requests": ">=1.6,<1.8", + "robrichards/xmlseclibs": ">=1,<3.0.4", + "roots/soil": "<4.1", + "roundcube/roundcubemail": "<1.5.10|>=1.6,<1.6.11", + "rudloff/alltube": "<3.0.3", + "rudloff/rtmpdump-bin": "<=2.3.1", + "s-cart/core": "<6.9", + "s-cart/s-cart": "<6.9", + "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", + "sabre/dav": ">=1.6,<1.7.11|>=1.8,<1.8.9", + "samwilson/unlinked-wikibase": "<1.42", + "scheb/two-factor-bundle": "<3.26|>=4,<4.11", + "sensiolabs/connect": "<4.2.3", + "serluck/phpwhois": "<=4.2.6", + "sfroemken/url_redirect": "<=1.2.1", + "sheng/yiicms": "<1.2.1", + "shopware/core": "<6.5.8.18-dev|>=6.6,<6.6.10.3-dev|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev", + "shopware/platform": "<6.5.8.18-dev|>=6.6,<6.6.10.3-dev|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev", + "shopware/production": "<=6.3.5.2", + "shopware/shopware": "<=5.7.17", + "shopware/storefront": "<=6.4.8.1|>=6.5.8,<6.5.8.7-dev", + "shopxo/shopxo": "<=6.4", + "showdoc/showdoc": "<2.10.4", + "shuchkin/simplexlsx": ">=1.0.12,<1.1.13", + "silverstripe-australia/advancedreports": ">=1,<=2", + "silverstripe/admin": "<1.13.19|>=2,<2.1.8", + "silverstripe/assets": ">=1,<1.11.1", + "silverstripe/cms": "<4.11.3", + "silverstripe/comments": ">=1.3,<3.1.1", + "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", + "silverstripe/framework": "<5.3.23", + "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", + "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", + "silverstripe/recipe-cms": ">=4.5,<4.5.3", + "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", + "silverstripe/reports": "<5.2.3", + "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4|>=2.1,<2.1.2", + "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", + "silverstripe/subsites": ">=2,<2.6.1", + "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", + "silverstripe/userforms": "<3|>=5,<5.4.2", + "silverstripe/versioned-admin": ">=1,<1.11.1", + "simple-updates/phpwhois": "<=1", + "simplesamlphp/saml2": "<=4.16.15|>=5.0.0.0-alpha1,<=5.0.0.0-alpha19", + "simplesamlphp/saml2-legacy": "<=4.16.15", + "simplesamlphp/simplesamlphp": "<1.18.6", + "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", + "simplesamlphp/simplesamlphp-module-openid": "<1", + "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", + "simplesamlphp/xml-common": "<1.20", + "simplesamlphp/xml-security": "==1.6.11", + "simplito/elliptic-php": "<1.0.6", + "sitegeist/fluid-components": "<3.5", + "sjbr/sr-feuser-register": "<2.6.2|>=5.1,<12.5", + "sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3", + "sjbr/static-info-tables": "<2.3.1", + "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", + "slim/slim": "<2.6", + "slub/slub-events": "<3.0.3", + "smarty/smarty": "<4.5.3|>=5,<5.1.1", + "snipe/snipe-it": "<8.1", + "socalnick/scn-social-auth": "<1.15.2", + "socialiteproviders/steam": "<1.1", + "spatie/browsershot": "<5.0.5", + "spatie/image-optimizer": "<1.7.3", + "spencer14420/sp-php-email-handler": "<1", + "spipu/html2pdf": "<5.2.8", + "spoon/library": "<1.4.1", + "spoonity/tcpdf": "<6.2.22", + "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", + "ssddanbrown/bookstack": "<24.05.1", + "starcitizentools/citizen-skin": ">=2.4.2,<3.3.1", + "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2", + "statamic/cms": "<=5.16", + "stormpath/sdk": "<9.9.99", + "studio-42/elfinder": "<=2.1.64", + "studiomitte/friendlycaptcha": "<0.1.4", + "subhh/libconnect": "<7.0.8|>=8,<8.1", + "sukohi/surpass": "<1", + "sulu/form-bundle": ">=2,<2.5.3", + "sulu/sulu": "<1.6.44|>=2,<2.5.25|>=2.6,<2.6.9|>=3.0.0.0-alpha1,<3.0.0.0-alpha3", + "sumocoders/framework-user-bundle": "<1.4", + "superbig/craft-audit": "<3.0.2", + "svewap/a21glossary": "<=0.4.10", + "swag/paypal": "<5.4.4", + "swiftmailer/swiftmailer": "<6.2.5", + "swiftyedit/swiftyedit": "<1.2", + "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", + "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", + "sylius/grid-bundle": "<1.10.1", + "sylius/paypal-plugin": "<1.6.2|>=1.7,<1.7.2|>=2,<2.0.2", + "sylius/resource-bundle": ">=1,<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", + "sylius/sylius": "<1.12.19|>=1.13.0.0-alpha1,<1.13.4", + "symbiote/silverstripe-multivaluefield": ">=3,<3.1", + "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", + "symbiote/silverstripe-seed": "<6.0.3", + "symbiote/silverstripe-versionedfiles": "<=2.0.3", + "symfont/process": ">=0", + "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", + "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", + "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", + "symfony/http-client": ">=4.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", + "symfony/http-foundation": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", + "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", + "symfony/mime": ">=4.3,<4.3.8", + "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/polyfill": ">=1,<1.10", + "symfony/polyfill-php55": ">=1,<1.10", + "symfony/process": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", + "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/routing": ">=2,<2.0.19", + "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", + "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", + "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.4.10|>=7,<7.0.10|>=7.1,<7.1.3", + "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", + "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", + "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", + "symfony/symfony": "<5.4.47|>=6,<6.4.15|>=7,<7.1.8", + "symfony/translation": ">=2,<2.0.17", + "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", + "symfony/ux-autocomplete": "<2.11.2", + "symfony/ux-live-component": "<2.25.1", + "symfony/ux-twig-component": "<2.25.1", + "symfony/validator": "<5.4.43|>=6,<6.4.11|>=7,<7.1.4", + "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", + "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", + "symfony/webhook": ">=6.3,<6.3.8", + "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7|>=2.2.0.0-beta1,<2.2.0.0-beta2", + "symphonycms/symphony-2": "<2.6.4", + "t3/dce": "<0.11.5|>=2.2,<2.6.2", + "t3g/svg-sanitizer": "<1.0.3", + "t3s/content-consent": "<1.0.3|>=2,<2.0.2", + "tastyigniter/tastyigniter": "<4", + "tcg/voyager": "<=1.8", + "tecnickcom/tc-lib-pdf-font": "<2.6.4", + "tecnickcom/tcpdf": "<6.8", + "terminal42/contao-tablelookupwizard": "<3.3.5", + "thelia/backoffice-default-template": ">=2.1,<2.1.2", + "thelia/thelia": ">=2.1,<2.1.3", + "theonedemon/phpwhois": "<=4.2.5", + "thinkcmf/thinkcmf": "<6.0.8", + "thorsten/phpmyfaq": "<=4.0.1", + "tikiwiki/tiki-manager": "<=17.1", + "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", + "tinymce/tinymce": "<7.2", + "tinymighty/wiki-seo": "<1.2.2", + "titon/framework": "<9.9.99", + "tltneon/lgsl": "<7", + "tobiasbg/tablepress": "<=2.0.0.0-RC1", + "topthink/framework": "<6.0.17|>=6.1,<=8.0.4", + "topthink/think": "<=6.1.1", + "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4", + "torrentpier/torrentpier": "<=2.4.3", + "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", + "tribalsystems/zenario": "<=9.7.61188", + "truckersmp/phpwhois": "<=4.3.1", + "ttskch/pagination-service-provider": "<1", + "twbs/bootstrap": "<=3.4.1|>=4,<=4.6.2", + "twig/twig": "<3.11.2|>=3.12,<3.14.1|>=3.16,<3.19", + "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", + "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<10.4.46|>=11,<11.5.40|>=12,<=12.4.30|>=13,<=13.4.11", + "typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-beuser": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-core": "<=8.7.56|>=9,<=9.5.50|>=10,<=10.4.49|>=11,<=11.5.43|>=12,<=12.4.30|>=13,<=13.4.11", + "typo3/cms-dashboard": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", + "typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-felogin": ">=4.2,<4.2.3", + "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", + "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5", + "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8|==13.4.2", + "typo3/cms-lowlevel": ">=11,<=11.5.41", + "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", + "typo3/cms-scheduler": ">=11,<=11.5.41", + "typo3/cms-setup": ">=9,<=9.5.50|>=10,<=10.4.49|>=11,<=11.5.43|>=12,<=12.4.30|>=13,<=13.4.11", + "typo3/cms-webhooks": ">=12,<=12.4.30|>=13,<=13.4.11", + "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", + "typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3", + "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", + "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", + "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", + "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10", + "ua-parser/uap-php": "<3.8", + "uasoft-indonesia/badaso": "<=2.9.7", + "unisharp/laravel-filemanager": "<2.9.1", + "unopim/unopim": "<0.1.5", + "userfrosting/userfrosting": ">=0.3.1,<4.6.3", + "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", + "uvdesk/community-skeleton": "<=1.1.1", + "uvdesk/core-framework": "<=1.1.1", + "vanilla/safecurl": "<0.9.2", + "verbb/comments": "<1.5.5", + "verbb/formie": "<=2.1.43", + "verbb/image-resizer": "<2.0.9", + "verbb/knock-knock": "<1.2.8", + "verot/class.upload.php": "<=2.1.6", + "vertexvaar/falsftp": "<0.2.6", + "villagedefrance/opencart-overclocked": "<=1.11.1", + "vova07/yii2-fileapi-widget": "<0.1.9", + "vrana/adminer": "<4.8.1", + "vufind/vufind": ">=2,<9.1.1", + "waldhacker/hcaptcha": "<2.1.2", + "wallabag/tcpdf": "<6.2.22", + "wallabag/wallabag": "<2.6.11", + "wanglelecc/laracms": "<=1.0.3", + "wapplersystems/a21glossary": "<=0.4.10", + "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9", + "web-auth/webauthn-lib": ">=4.5,<4.9", + "web-feet/coastercms": "==5.5", + "web-tp3/wec_map": "<3.0.3", + "webbuilders-group/silverstripe-kapost-bridge": "<0.4", + "webcoast/deferred-image-processing": "<1.0.2", + "webklex/laravel-imap": "<5.3", + "webklex/php-imap": "<5.3", + "webpa/webpa": "<3.1.2", + "wikibase/wikibase": "<=1.39.3", + "wikimedia/parsoid": "<0.12.2", + "willdurand/js-translation-bundle": "<2.1.1", + "winter/wn-backend-module": "<1.2.4", + "winter/wn-cms-module": "<1.0.476|>=1.1,<1.1.11|>=1.2,<1.2.7", + "winter/wn-dusk-plugin": "<2.1", + "winter/wn-system-module": "<1.2.4", + "wintercms/winter": "<=1.2.3", + "wireui/wireui": "<1.19.3|>=2,<2.1.3", + "woocommerce/woocommerce": "<6.6|>=8.8,<8.8.5|>=8.9,<8.9.3", + "wp-cli/wp-cli": ">=0.12,<2.5", + "wp-graphql/wp-graphql": "<=1.14.5", + "wp-premium/gravityforms": "<2.4.21", + "wpanel/wpanel4-cms": "<=4.3.1", + "wpcloud/wp-stateless": "<3.2", + "wpglobus/wpglobus": "<=1.9.6", + "wwbn/avideo": "<14.3", + "xataface/xataface": "<3", + "xpressengine/xpressengine": "<3.0.15", + "yab/quarx": "<2.4.5", + "yeswiki/yeswiki": "<4.5.4", + "yetiforce/yetiforce-crm": "<6.5", + "yidashi/yii2cmf": "<=2", + "yii2mod/yii2-cms": "<1.9.2", + "yiisoft/yii": "<1.1.31", + "yiisoft/yii2": "<2.0.52", + "yiisoft/yii2-authclient": "<2.2.15", + "yiisoft/yii2-bootstrap": "<2.0.4", + "yiisoft/yii2-dev": "<=2.0.45", + "yiisoft/yii2-elasticsearch": "<2.0.5", + "yiisoft/yii2-gii": "<=2.2.4", + "yiisoft/yii2-jui": "<2.0.4", + "yiisoft/yii2-redis": "<2.0.20", + "yikesinc/yikes-inc-easy-mailchimp-extender": "<6.8.6", + "yoast-seo-for-typo3/yoast_seo": "<7.2.3", + "yourls/yourls": "<=1.8.2", + "yuan1994/tpadmin": "<=1.3.12", + "zencart/zencart": "<=1.5.7.0-beta", + "zendesk/zendesk_api_client_php": "<2.2.11", + "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", + "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-db": "<2.2.10|>=2.3,<2.3.5", + "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", + "zendframework/zend-diactoros": "<1.8.4", + "zendframework/zend-feed": "<2.10.3", + "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-http": "<2.8.1", + "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", + "zendframework/zend-mail": "<2.4.11|>=2.5,<2.7.2", + "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-session": ">=2,<2.2.9|>=2.3,<2.3.4", + "zendframework/zend-validator": ">=2.3,<2.3.6", + "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zendframework": "<=3", + "zendframework/zendframework1": "<1.12.20", + "zendframework/zendopenid": "<2.0.2", + "zendframework/zendrest": "<2.0.2", + "zendframework/zendservice-amazon": "<2.0.3", + "zendframework/zendservice-api": "<1", + "zendframework/zendservice-audioscrobbler": "<2.0.2", + "zendframework/zendservice-nirvanix": "<2.0.2", + "zendframework/zendservice-slideshare": "<2.0.2", + "zendframework/zendservice-technorati": "<2.0.2", + "zendframework/zendservice-windowsazure": "<2.0.2", + "zendframework/zendxml": ">=1,<1.0.1", + "zenstruck/collection": "<0.2.1", + "zetacomponents/mail": "<1.8.2", + "zf-commons/zfc-user": "<1.2.2", + "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", + "zfr/zfr-oauth2-server-module": "<0.1.2", + "zoujingli/thinkadmin": "<=6.1.53" + }, + "default-branch": true, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "role": "maintainer" + }, + { + "name": "Ilya Tribusean", + "email": "slash3b@gmail.com", + "role": "maintainer" + } + ], + "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "keywords": [ + "dev" + ], + "support": { + "issues": "https://github.com/Roave/SecurityAdvisories/issues", + "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" + }, + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", + "type": "tidelift" + } + ], + "time": "2025-06-23T23:04:33+00:00" + }, + { + "name": "roots/wordpress", + "version": "6.6.2", + "source": { + "type": "git", + "url": "https://github.com/roots/wordpress.git", + "reference": "1bdabdb9171ac5323edbf4792ce353d475467d27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/roots/wordpress/zipball/1bdabdb9171ac5323edbf4792ce353d475467d27", + "reference": "1bdabdb9171ac5323edbf4792ce353d475467d27", + "shasum": "" + }, + "require": { + "roots/wordpress-core-installer": "^1.0.0", + "roots/wordpress-no-content": "self.version" + }, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT", + "GPL-2.0-or-later" + ], + "description": "WordPress is open source software you can use to create a beautiful website, blog, or app.", + "homepage": "https://wordpress.org/", + "keywords": [ + "blog", + "cms", + "wordpress" + ], + "support": { + "issues": "https://github.com/roots/wordpress/issues", + "source": "https://github.com/roots/wordpress/tree/6.6.2" + }, + "funding": [ + { + "url": "https://github.com/roots", + "type": "github" + } + ], + "time": "2024-07-16T12:03:00+00:00" + }, + { + "name": "roots/wordpress-core-installer", + "version": "1.100.0", + "source": { + "type": "git", + "url": "https://github.com/roots/wordpress-core-installer.git", + "reference": "73f8488e5178c5d54234b919f823a9095e2b1847" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/roots/wordpress-core-installer/zipball/73f8488e5178c5d54234b919f823a9095e2b1847", + "reference": "73f8488e5178c5d54234b919f823a9095e2b1847", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.6.0" + }, + "conflict": { + "composer/installers": "<1.0.6" + }, + "replace": { + "johnpbloch/wordpress-core-installer": "*" + }, + "require-dev": { + "composer/composer": "^1.0 || ^2.0", + "phpunit/phpunit": ">=5.7.27" + }, + "type": "composer-plugin", + "extra": { + "class": "Roots\\Composer\\WordPressCorePlugin" + }, + "autoload": { + "psr-4": { + "Roots\\Composer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "John P. Bloch", + "email": "me@johnpbloch.com" + }, + { + "name": "Roots", + "email": "team@roots.io" + } + ], + "description": "A custom installer to handle deploying WordPress with composer", + "keywords": [ + "wordpress" + ], + "support": { + "issues": "https://github.com/roots/wordpress-core-installer/issues", + "source": "https://github.com/roots/wordpress-core-installer/tree/master" + }, + "funding": [ + { + "url": "https://github.com/roots", + "type": "github" + }, + { + "url": "https://www.patreon.com/rootsdev", + "type": "patreon" + } + ], + "time": "2020-08-20T00:27:30+00:00" + }, + { + "name": "roots/wordpress-no-content", + "version": "6.6.2", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress.git", + "reference": "6.6.2" + }, + "dist": { + "type": "zip", + "url": "https://downloads.wordpress.org/release/wordpress-6.6.2-no-content.zip", + "shasum": "b496b8a9bb3c6d20a781344cbe3e189f7fd83871" + }, + "require": { + "php": ">= 7.2.24" + }, + "provide": { + "wordpress/core-implementation": "6.6.2" + }, + "suggest": { + "ext-curl": "Performs remote request operations.", + "ext-dom": "Used to validate Text Widget content and to automatically configuring IIS7+.", + "ext-exif": "Works with metadata stored in images.", + "ext-fileinfo": "Used to detect mimetype of file uploads.", + "ext-hash": "Used for hashing, including passwords and update packages.", + "ext-imagick": "Provides better image quality for media uploads.", + "ext-json": "Used for communications with other servers.", + "ext-libsodium": "Validates Signatures and provides securely random bytes.", + "ext-mbstring": "Used to properly handle UTF8 text.", + "ext-mysqli": "Connects to MySQL for database interactions.", + "ext-openssl": "Permits SSL-based connections to other hosts.", + "ext-pcre": "Increases performance of pattern matching in code searches.", + "ext-xml": "Used for XML parsing, such as from a third-party site.", + "ext-zip": "Used for decompressing Plugins, Themes, and WordPress update packages." + }, + "type": "wordpress-core", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "WordPress Community", + "homepage": "https://wordpress.org/about/" + } + ], + "description": "WordPress is open source software you can use to create a beautiful website, blog, or app.", + "homepage": "https://wordpress.org/", + "keywords": [ + "blog", + "cms", + "wordpress" + ], + "support": { + "docs": "https://developer.wordpress.org/", + "forum": "https://wordpress.org/support/", + "irc": "irc://irc.freenode.net/wordpress", + "issues": "https://core.trac.wordpress.org/", + "rss": "https://wordpress.org/news/feed/", + "source": "https://core.trac.wordpress.org/browser", + "wiki": "https://codex.wordpress.org/" + }, + "funding": [ + { + "url": "https://wordpressfoundation.org/donate/", + "type": "other" + } + ], + "time": "2024-09-10T15:25:31+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.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 for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.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": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.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 for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:33:00+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:35:11+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.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 for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-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": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c", + "shasum": "" + }, + "require": { + "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-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "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": "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/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/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-06-17T22:17:01+00:00" + }, + { + "name": "swissspidy/phpstan-no-private", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/swissspidy/phpstan-no-private.git", + "reference": "559cb0e8d092df7314ed4254db83db0427440af2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swissspidy/phpstan-no-private/zipball/559cb0e8d092df7314ed4254db83db0427440af2", + "reference": "559cb0e8d092df7314ed4254db83db0427440af2", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", + "nikic/php-parser": "^v5.3.1", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.5", + "slevomat/coding-standard": "^8.8.0", + "squizlabs/php_codesniffer": "^3.5.3" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Swissspidy\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan rules for detecting usage of pseudo-private functions, classes, and methods.", + "support": { + "issues": "https://github.com/swissspidy/phpstan-no-private/issues", + "source": "https://github.com/swissspidy/phpstan-no-private/tree/v1.0.0" + }, + "time": "2024-11-11T11:04:45+00:00" + }, + { + "name": "symfony/config", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "ba62ae565f1327c2f6366726312ed828c85853bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/ba62ae565f1327c2f6366726312ed828c85853bc", + "reference": "ba62ae565f1327c2f6366726312ed828c85853bc", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^7.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-15T09:04:05+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "f64a8f3fa7d4ad5e85de1b128a0e03faed02b732" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f64a8f3fa7d4ad5e85de1b128a0e03faed02b732", + "reference": "f64a8f3fa7d4ad5e85de1b128a0e03faed02b732", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.5", + "symfony/var-exporter": "^6.4.20|^7.2.5" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-19T13:28:56+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:15:23+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-30T19:00:26+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.32.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.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-25T09:37:31+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v5.4.48", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "42f18f170aa86d612c3559cfb3bd11a375df32c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/42f18f170aa86d612c3559cfb3bd11a375df32c8", + "reference": "42f18f170aa86d612c3559cfb3bd11a375df32c8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/console": "<4.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/uid": "^5.1|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v5.4.48" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-08T15:21:10+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/c9a1168891b5aaadfd6332ef44393330b3498c4c", + "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-15T09:04:05+00:00" + }, + { + "name": "szepeviktor/phpstan-wordpress", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/szepeviktor/phpstan-wordpress.git", + "reference": "963887b04c21fe7ac78e61c1351f8b00fff9f8f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/963887b04c21fe7ac78e61c1351f8b00fff9f8f8", + "reference": "963887b04c21fe7ac78e61c1351f8b00fff9f8f8", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "php-stubs/wordpress-stubs": "^6.6.2", + "phpstan/phpstan": "^2.0" + }, + "require-dev": { + "composer/composer": "^2.1.14", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.0", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.0", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "swissspidy/phpstan-no-private": "Detect usage of internal core functions, classes and methods" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "SzepeViktor\\PHPStan\\WordPress\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress extensions for PHPStan", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/szepeviktor/phpstan-wordpress/issues", + "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v2.0.2" + }, + "time": "2025-02-12T18:43:37+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "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" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.5.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.2", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.8", + "symfony/polyfill-ctype": "^1.23", + "symfony/polyfill-mbstring": "^1.23.1", + "symfony/polyfill-php80": "^1.23.1" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "5.5-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2022-10-16T01:01:54+00:00" + }, + { + "name": "wp-cli/i18n-command", + "version": "v2.6.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/i18n-command.git", + "reference": "5e73d417398993625331a9f69f6c2ef60f234070" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/5e73d417398993625331a9f69f6c2ef60f234070", + "reference": "5e73d417398993625331a9f69f6c2ef60f234070", + "shasum": "" + }, + "require": { + "eftec/bladeone": "3.52", + "gettext/gettext": "^4.8", + "mck89/peast": "^1.13.11", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/scaffold-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4.3.9" + }, + "suggest": { + "ext-json": "Used for reading and generating JSON translation files", + "ext-mbstring": "Used for calculating include/exclude matches in code extraction" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "i18n", + "i18n make-pot", + "i18n make-json", + "i18n make-mo", + "i18n make-php", + "i18n update-po" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "i18n-command.php" + ], + "psr-4": { + "WP_CLI\\I18n\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Pascal Birchler", + "homepage": "https://pascalbirchler.com/" + } + ], + "description": "Provides internationalization tools for WordPress projects.", + "homepage": "https://github.com/wp-cli/i18n-command", + "support": { + "issues": "https://github.com/wp-cli/i18n-command/issues", + "source": "https://github.com/wp-cli/i18n-command/tree/v2.6.5" + }, + "time": "2025-04-25T21:49:29+00:00" + }, + { + "name": "wp-cli/mustache", + "version": "v2.14.99", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/mustache.php.git", + "reference": "ca23b97ac35fbe01c160549eb634396183d04a59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/mustache.php/zipball/ca23b97ac35fbe01c160549eb634396183d04a59", + "reference": "ca23b97ac35fbe01c160549eb634396183d04a59", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "replace": { + "mustache/mustache": "^2.14.2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.19.3", + "yoast/phpunit-polyfills": "^2.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "source": "https://github.com/wp-cli/mustache.php/tree/v2.14.99" + }, + "time": "2025-05-06T16:15:37+00:00" + }, + { + "name": "wp-cli/mustangostang-spyc", + "version": "0.6.3", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/spyc.git", + "reference": "6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/spyc/zipball/6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7", + "reference": "6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "autoload": { + "files": [ + "includes/functions.php" + ], + "psr-4": { + "Mustangostang\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP (WP-CLI fork)", + "homepage": "https://github.com/mustangostang/spyc/", + "support": { + "source": "https://github.com/wp-cli/spyc/tree/autoload" + }, + "time": "2017-04-25T11:26:20+00:00" + }, + { + "name": "wp-cli/php-cli-tools", + "version": "v0.12.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/php-cli-tools.git", + "reference": "34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da", + "reference": "34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da", + "shasum": "" + }, + "require": { + "php": ">= 5.6.0" + }, + "require-dev": { + "roave/security-advisories": "dev-latest", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.11.x-dev" + } + }, + "autoload": { + "files": [ + "lib/cli/cli.php" + ], + "psr-0": { + "cli": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@handbuilt.co", + "role": "Maintainer" + }, + { + "name": "James Logsdon", + "email": "jlogsdon@php.net", + "role": "Developer" + } + ], + "description": "Console utilities for PHP", + "homepage": "http://github.com/wp-cli/php-cli-tools", + "keywords": [ + "cli", + "console" + ], + "support": { + "issues": "https://github.com/wp-cli/php-cli-tools/issues", + "source": "https://github.com/wp-cli/php-cli-tools/tree/v0.12.5" + }, + "time": "2025-03-26T16:13:46+00:00" + }, + { + "name": "wp-cli/wp-cli", + "version": "v2.12.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/wp-cli.git", + "reference": "03d30d4138d12b4bffd8b507b82e56e129e0523f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/03d30d4138d12b4bffd8b507b82e56e129e0523f", + "reference": "03d30d4138d12b4bffd8b507b82e56e129e0523f", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": "^5.6 || ^7.0 || ^8.0", + "symfony/finder": ">2.7", + "wp-cli/mustache": "^2.14.99", + "wp-cli/mustangostang-spyc": "^0.6.3", + "wp-cli/php-cli-tools": "~0.12.4" + }, + "require-dev": { + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.2 || ^2", + "wp-cli/extension-command": "^1.1 || ^2", + "wp-cli/package-command": "^1 || ^2", + "wp-cli/wp-cli-tests": "^4.3.10" + }, + "suggest": { + "ext-readline": "Include for a better --prompt implementation", + "ext-zip": "Needed to support extraction of ZIP archives when doing downloads or updates" + }, + "bin": [ + "bin/wp", + "bin/wp.bat" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.12.x-dev" + } + }, + "autoload": { + "psr-0": { + "WP_CLI\\": "php/" + }, + "classmap": [ + "php/class-wp-cli.php", + "php/class-wp-cli-command.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WP-CLI framework", + "homepage": "https://wp-cli.org", + "keywords": [ + "cli", + "wordpress" + ], + "support": { + "docs": "https://make.wordpress.org/cli/handbook/", + "issues": "https://github.com/wp-cli/wp-cli/issues", + "source": "https://github.com/wp-cli/wp-cli" + }, + "time": "2025-05-07T01:16:12+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=5.4", + "phpcsstandards/phpcsextra": "^1.2.1", + "phpcsstandards/phpcsutils": "^1.0.10", + "squizlabs/php_codesniffer": "^3.9.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.0", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.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/php_codesniffer", + "type": "custom" + } + ], + "time": "2024-03-25T16:39:00+00:00" + }, + { + "name": "wp-phpunit/wp-phpunit", + "version": "6.6.2", + "source": { + "type": "git", + "url": "https://github.com/wp-phpunit/wp-phpunit.git", + "reference": "7a1d3a2150033a3d3e19de40aa5b2ef2fee36bc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-phpunit/wp-phpunit/zipball/7a1d3a2150033a3d3e19de40aa5b2ef2fee36bc3", + "reference": "7a1d3a2150033a3d3e19de40aa5b2ef2fee36bc3", + "shasum": "" + }, + "type": "library", + "autoload": { + "files": [ + "__loaded.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Evan Mattson", + "email": "me@aaemnnost.tv" + }, + { + "name": "WordPress Community", + "homepage": "https://wordpress.org/about/" + } + ], + "description": "WordPress core PHPUnit library", + "homepage": "https://github.com/wp-phpunit", + "keywords": [ + "phpunit", + "test", + "wordpress" + ], + "support": { + "docs": "https://github.com/wp-phpunit/docs", + "issues": "https://github.com/wp-phpunit/issues", + "source": "https://github.com/wp-phpunit/wp-phpunit" + }, + "time": "2024-07-17T01:13:44+00:00" + }, + { + "name": "yoast/phpunit-polyfills", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", + "reference": "134921bfca9b02d8f374c48381451da1d98402f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/134921bfca9b02d8f374c48381451da1d98402f9", + "reference": "134921bfca9b02d8f374c48381451da1d98402f9", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0 || ^11.0 || ^12.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "yoast/yoastcs": "^3.1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.x-dev" + } + }, + "autoload": { + "files": [ + "phpunitpolyfills-autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Team Yoast", + "email": "support@yoast.com", + "homepage": "https://yoast.com" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/graphs/contributors" + } + ], + "description": "Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills", + "keywords": [ + "phpunit", + "polyfill", + "testing" + ], + "support": { + "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", + "source": "https://github.com/Yoast/PHPUnit-Polyfills" + }, + "time": "2025-02-09T18:58:54+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "a8cteam51/team51-configs": 20, + "roave/security-advisories": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.4", + "ext-json": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/lib/action-scheduler/action-scheduler.php b/lib/action-scheduler/action-scheduler.php index 1d5cf7b0..210d821a 100644 --- a/lib/action-scheduler/action-scheduler.php +++ b/lib/action-scheduler/action-scheduler.php @@ -5,11 +5,11 @@ * Description: A robust scheduling library for use in WordPress plugins. * Author: Automattic * Author URI: https://automattic.com/ - * Version: 3.8.1 + * Version: 3.9.2 * License: GPLv3 - * Requires at least: 6.2 - * Tested up to: 6.5 - * Requires PHP: 5.6 + * Requires at least: 6.5 + * Tested up to: 6.7 + * Requires PHP: 7.2 * * Copyright 2019 Automattic, Inc. (https://automattic.com/contact/) * @@ -29,29 +29,31 @@ * @package ActionScheduler */ -if ( ! function_exists( 'action_scheduler_register_3_dot_8_dot_1' ) && function_exists( 'add_action' ) ) { // WRCS: DEFINED_VERSION. +if ( ! function_exists( 'action_scheduler_register_3_dot_9_dot_2' ) && function_exists( 'add_action' ) ) { // WRCS: DEFINED_VERSION. if ( ! class_exists( 'ActionScheduler_Versions', false ) ) { require_once __DIR__ . '/classes/ActionScheduler_Versions.php'; add_action( 'plugins_loaded', array( 'ActionScheduler_Versions', 'initialize_latest_version' ), 1, 0 ); } - add_action( 'plugins_loaded', 'action_scheduler_register_3_dot_8_dot_1', 0, 0 ); // WRCS: DEFINED_VERSION. + add_action( 'plugins_loaded', 'action_scheduler_register_3_dot_9_dot_2', 0, 0 ); // WRCS: DEFINED_VERSION. // phpcs:disable Generic.Functions.OpeningFunctionBraceKernighanRitchie.ContentAfterBrace /** * Registers this version of Action Scheduler. */ - function action_scheduler_register_3_dot_8_dot_1() { // WRCS: DEFINED_VERSION. + function action_scheduler_register_3_dot_9_dot_2() { + // WRCS: DEFINED_VERSION. $versions = ActionScheduler_Versions::instance(); - $versions->register( '3.8.1', 'action_scheduler_initialize_3_dot_8_dot_1' ); // WRCS: DEFINED_VERSION. + $versions->register( '3.9.2', 'action_scheduler_initialize_3_dot_9_dot_2' ); // WRCS: DEFINED_VERSION. } // phpcs:disable Generic.Functions.OpeningFunctionBraceKernighanRitchie.ContentAfterBrace /** * Initializes this version of Action Scheduler. */ - function action_scheduler_initialize_3_dot_8_dot_1() { // WRCS: DEFINED_VERSION. + function action_scheduler_initialize_3_dot_9_dot_2() { + // WRCS: DEFINED_VERSION. // A final safety check is required even here, because historic versions of Action Scheduler // followed a different pattern (in some unusual cases, we could reach this point and the // ActionScheduler class is already defined—so we need to guard against that). @@ -63,7 +65,7 @@ function action_scheduler_initialize_3_dot_8_dot_1() { // WRCS: DEFINED_VERSION. // Support usage in themes - load this version if no plugin has loaded a version yet. if ( did_action( 'plugins_loaded' ) && ! doing_action( 'plugins_loaded' ) && ! class_exists( 'ActionScheduler', false ) ) { - action_scheduler_initialize_3_dot_8_dot_1(); // WRCS: DEFINED_VERSION. + action_scheduler_initialize_3_dot_9_dot_2(); // WRCS: DEFINED_VERSION. do_action( 'action_scheduler_pre_theme_init' ); ActionScheduler_Versions::initialize_latest_version(); } diff --git a/lib/action-scheduler/changelog.txt b/lib/action-scheduler/changelog.txt index 97c537d9..43ca7829 100644 --- a/lib/action-scheduler/changelog.txt +++ b/lib/action-scheduler/changelog.txt @@ -1,5 +1,29 @@ *** Changelog *** += 3.9.2 - 2025-02-03 = +* Fixed fatal errors by moving version info methods to a new class and deprecating conflicting ones in ActionScheduler_Versions + += 3.9.1 - 2025-01-21 = +* A number of new WP CLI commands have been added, making it easier to manage actions in the terminal and from scripts. +* New wp action-scheduler source command to help determine how Action Scheduler is being loaded. +* Additional information about the active instance of Action Scheduler is now available in the Help pull-down drawer. +* Make some other nullable parameters explicitly nullable. +* Set option value to `no` rather than deleting. + += 3.9.0 - 2024-11-14 = +* Minimum required version of PHP is now 7.1. +* Performance improvements for the `as_pending_actions_due()` function. +* Existing filter hook `action_scheduler_claim_actions_order_by` enhanced to provide callbacks with additional information. +* Improved compatibility with PHP 8.4, specifically by making implicitly nullable parameters explicitly nullable. +* A large number of coding standards-enhancements, to help reduce friction when submitting plugins to marketplaces and plugin directories. Special props @crstauf for this effort. +* Minor documentation tweaks and improvements. + += 3.8.2 - 2024-09-12 = +* Add missing parameter to the `pre_as_enqueue_async_action` hook. +* Bump minimum PHP version to 7.0. +* Bump minimum WordPress version to 6.4. +* Make the batch size adjustable during processing. + = 3.8.1 - 2024-06-20 = * Fix typos. * Improve the messaging in our unidentified action exceptions. diff --git a/lib/action-scheduler/classes/ActionScheduler_ActionClaim.php b/lib/action-scheduler/classes/ActionScheduler_ActionClaim.php index 8b568162..8eb01eaa 100644 --- a/lib/action-scheduler/classes/ActionScheduler_ActionClaim.php +++ b/lib/action-scheduler/classes/ActionScheduler_ActionClaim.php @@ -4,20 +4,42 @@ * Class ActionScheduler_ActionClaim */ class ActionScheduler_ActionClaim { + /** + * Claim ID. + * + * @var string + */ private $id = ''; + + /** + * Claimed action IDs. + * + * @var int[] + */ private $action_ids = array(); + /** + * Construct. + * + * @param string $id Claim ID. + * @param int[] $action_ids Action IDs. + */ public function __construct( $id, array $action_ids ) { - $this->id = $id; + $this->id = $id; $this->action_ids = $action_ids; } + /** + * Get claim ID. + */ public function get_id() { return $this->id; } + /** + * Get IDs of claimed actions. + */ public function get_actions() { return $this->action_ids; } } - \ No newline at end of file diff --git a/lib/action-scheduler/classes/ActionScheduler_ActionFactory.php b/lib/action-scheduler/classes/ActionScheduler_ActionFactory.php index c8d68af3..ea60d60b 100644 --- a/lib/action-scheduler/classes/ActionScheduler_ActionFactory.php +++ b/lib/action-scheduler/classes/ActionScheduler_ActionFactory.php @@ -8,17 +8,17 @@ class ActionScheduler_ActionFactory { /** * Return stored actions for given params. * - * @param string $status The action's status in the data store. - * @param string $hook The hook to trigger when this action runs. - * @param array $args Args to pass to callbacks when the hook is triggered. - * @param ActionScheduler_Schedule $schedule The action's schedule. - * @param string $group A group to put the action in. + * @param string $status The action's status in the data store. + * @param string $hook The hook to trigger when this action runs. + * @param array $args Args to pass to callbacks when the hook is triggered. + * @param ActionScheduler_Schedule|null $schedule The action's schedule. + * @param string $group A group to put the action in. * phpcs:ignore Squiz.Commenting.FunctionComment.ExtraParamComment - * @param int $priority The action priority. + * @param int $priority The action priority. * * @return ActionScheduler_Action An instance of the stored action. */ - public function get_stored_action( $status, $hook, array $args = array(), ActionScheduler_Schedule $schedule = null, $group = '' ) { + public function get_stored_action( $status, $hook, array $args = array(), ?ActionScheduler_Schedule $schedule = null, $group = '' ) { // The 6th parameter ($priority) is not formally declared in the method signature to maintain compatibility with // third-party subclasses created before this param was added. $priority = func_num_args() >= 6 ? (int) func_get_arg( 5 ) : 10; @@ -307,6 +307,7 @@ public function create( array $options = array() ) { break; default: + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( "Unknown action type '{$options['type']}' specified when trying to create an action for '{$options['hook']}'." ); return 0; } @@ -318,6 +319,7 @@ public function create( array $options = array() ) { try { $action_id = $options['unique'] ? $this->store_unique_action( $action ) : $this->store( $action ); } catch ( Exception $e ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( sprintf( /* translators: %1$s is the name of the hook to be enqueued, %2$s is the exception message. */ diff --git a/lib/action-scheduler/classes/ActionScheduler_AdminView.php b/lib/action-scheduler/classes/ActionScheduler_AdminView.php index ed30950a..1c8306e4 100644 --- a/lib/action-scheduler/classes/ActionScheduler_AdminView.php +++ b/lib/action-scheduler/classes/ActionScheduler_AdminView.php @@ -2,25 +2,42 @@ /** * Class ActionScheduler_AdminView + * * @codeCoverageIgnore */ class ActionScheduler_AdminView extends ActionScheduler_AdminView_Deprecated { - private static $admin_view = NULL; + /** + * Instance. + * + * @var null|self + */ + private static $admin_view = null; + /** + * Screen ID. + * + * @var string + */ private static $screen_id = 'tools_page_action-scheduler'; - /** @var ActionScheduler_ListTable */ + /** + * ActionScheduler_ListTable instance. + * + * @var ActionScheduler_ListTable + */ protected $list_table; /** + * Get instance. + * * @return ActionScheduler_AdminView * @codeCoverageIgnore */ public static function instance() { if ( empty( self::$admin_view ) ) { - $class = apply_filters('action_scheduler_admin_view_class', 'ActionScheduler_AdminView'); + $class = apply_filters( 'action_scheduler_admin_view_class', 'ActionScheduler_AdminView' ); self::$admin_view = new $class(); } @@ -28,10 +45,12 @@ public static function instance() { } /** + * Initialize. + * * @codeCoverageIgnore */ public function init() { - if ( is_admin() && ( ! defined( 'DOING_AJAX' ) || false == DOING_AJAX ) ) { + if ( is_admin() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) ) { if ( class_exists( 'WooCommerce' ) ) { add_action( 'woocommerce_admin_status_content_action-scheduler', array( $this, 'render_admin_ui' ) ); @@ -45,6 +64,9 @@ public function init() { } } + /** + * Print system status report. + */ public function system_status_report() { $table = new ActionScheduler_wcSystemStatus( ActionScheduler::store() ); $table->render(); @@ -78,7 +100,7 @@ public function register_menu() { 'action-scheduler', array( $this, 'render_admin_ui' ) ); - add_action( 'load-' . $hook_suffix , array( $this, 'process_admin_ui' ) ); + add_action( 'load-' . $hook_suffix, array( $this, 'process_admin_ui' ) ); } /** @@ -119,33 +141,31 @@ protected function get_list_table() { */ public function maybe_check_pastdue_actions() { - # Filter to prevent checking actions (ex: inappropriate user). + // Filter to prevent checking actions (ex: inappropriate user). if ( ! apply_filters( 'action_scheduler_check_pastdue_actions', current_user_can( 'manage_options' ) ) ) { return; } - # Get last check transient. + // Get last check transient. $last_check = get_transient( 'action_scheduler_last_pastdue_actions_check' ); - # If transient exists, we're within interval, so bail. + // If transient exists, we're within interval, so bail. if ( ! empty( $last_check ) ) { return; } - # Perform the check. + // Perform the check. $this->check_pastdue_actions(); } /** * Check past-due actions, and print notice. - * - * @todo update $link_url to "Past-due" filter when released (see issue #510, PR #511) */ protected function check_pastdue_actions() { - # Set thresholds. - $threshold_seconds = ( int ) apply_filters( 'action_scheduler_pastdue_actions_seconds', DAY_IN_SECONDS ); - $threshold_min = ( int ) apply_filters( 'action_scheduler_pastdue_actions_min', 1 ); + // Set thresholds. + $threshold_seconds = (int) apply_filters( 'action_scheduler_pastdue_actions_seconds', DAY_IN_SECONDS ); + $threshold_min = (int) apply_filters( 'action_scheduler_pastdue_actions_min', 1 ); // Set fallback value for past-due actions count. $num_pastdue_actions = 0; @@ -158,24 +178,24 @@ protected function check_pastdue_actions() { return; } - # Scheduled actions query arguments. + // Scheduled actions query arguments. $query_args = array( 'date' => as_get_datetime_object( time() - $threshold_seconds ), 'status' => ActionScheduler_Store::STATUS_PENDING, 'per_page' => $threshold_min, ); - # If no third-party preempted, run default check. + // If no third-party preempted, run default check. if ( is_null( $check ) ) { - $store = ActionScheduler_Store::instance(); - $num_pastdue_actions = ( int ) $store->query_actions( $query_args, 'count' ); + $store = ActionScheduler_Store::instance(); + $num_pastdue_actions = (int) $store->query_actions( $query_args, 'count' ); - # Check if past-due actions count is greater than or equal to threshold. + // Check if past-due actions count is greater than or equal to threshold. $check = ( $num_pastdue_actions >= $threshold_min ); - $check = ( bool ) apply_filters( 'action_scheduler_pastdue_actions_check', $check, $num_pastdue_actions, $threshold_seconds, $threshold_min ); + $check = (bool) apply_filters( 'action_scheduler_pastdue_actions_check', $check, $num_pastdue_actions, $threshold_seconds, $threshold_min ); } - # If check failed, set transient and abort. + // If check failed, set transient and abort. if ( ! boolval( $check ) ) { $interval = apply_filters( 'action_scheduler_pastdue_actions_check_interval', round( $threshold_seconds / 4 ), $threshold_seconds ); set_transient( 'action_scheduler_last_pastdue_actions_check', time(), $interval ); @@ -183,28 +203,40 @@ protected function check_pastdue_actions() { return; } - $actions_url = add_query_arg( array( - 'page' => 'action-scheduler', - 'status' => 'past-due', - 'order' => 'asc', - ), admin_url( 'tools.php' ) ); + $actions_url = add_query_arg( + array( + 'page' => 'action-scheduler', + 'status' => 'past-due', + 'order' => 'asc', + ), + admin_url( 'tools.php' ) + ); - # Print notice. + // Print notice. echo '

'; printf( - // translators: 1) is the number of affected actions, 2) is a link to an admin screen. - _n( - 'Action Scheduler: %1$d past-due action found; something may be wrong. Read documentation »', - 'Action Scheduler: %1$d past-due actions found; something may be wrong. Read documentation »', - $num_pastdue_actions, - 'action-scheduler' + wp_kses( + // translators: 1) is the number of affected actions, 2) is a link to an admin screen. + _n( + 'Action Scheduler: %1$d past-due action found; something may be wrong. Read documentation »', + 'Action Scheduler: %1$d past-due actions found; something may be wrong. Read documentation »', + $num_pastdue_actions, + 'action-scheduler' + ), + array( + 'strong' => array(), + 'a' => array( + 'href' => true, + 'target' => true, + ), + ) ), - $num_pastdue_actions, + absint( $num_pastdue_actions ), esc_attr( esc_url( $actions_url ) ) ); echo '

'; - # Facilitate third-parties to evaluate and print notices. + // Facilitate third-parties to evaluate and print notices. do_action( 'action_scheduler_pastdue_actions_extra_notices', $query_args ); } @@ -214,11 +246,24 @@ protected function check_pastdue_actions() { public function add_help_tabs() { $screen = get_current_screen(); - if ( ! $screen || self::$screen_id != $screen->id ) { + if ( ! $screen || self::$screen_id !== $screen->id ) { return; } - $as_version = ActionScheduler_Versions::instance()->latest_version(); + $as_version = ActionScheduler_Versions::instance()->latest_version(); + $as_source = ActionScheduler_SystemInformation::active_source(); + $as_source_path = ActionScheduler_SystemInformation::active_source_path(); + $as_source_markup = sprintf( '%s', esc_html( $as_source_path ) ); + + if ( ! empty( $as_source ) ) { + $as_source_markup = sprintf( + '%s: %s', + ucfirst( $as_source['type'] ), + esc_attr( $as_source_path ), + esc_html( $as_source['name'] ) + ); + } + $screen->add_help_tab( array( 'id' => 'action_scheduler_about', @@ -228,6 +273,19 @@ public function add_help_tabs() { '

' . sprintf( __( 'About Action Scheduler %s', 'action-scheduler' ), $as_version ) . '

' . '

' . __( 'Action Scheduler is a scalable, traceable job queue for background processing large sets of actions. Action Scheduler works by triggering an action hook to run at some time in the future. Scheduled actions can also be scheduled to run on a recurring schedule.', 'action-scheduler' ) . + '

' . + '

' . esc_html__( 'Source', 'action-scheduler' ) . '

' . + '

' . + esc_html__( 'Action Scheduler is currently being loaded from the following location. This can be useful when debugging, or if requested by the support team.', 'action-scheduler' ) . + '

' . + '

' . $as_source_markup . '

' . + '

' . esc_html__( 'WP CLI', 'action-scheduler' ) . '

' . + '

' . + sprintf( + /* translators: %1$s is WP CLI command (not translatable) */ + esc_html__( 'WP CLI commands are available: execute %1$s for a list of available commands.', 'action-scheduler' ), + 'wp help action-scheduler' + ) . '

', ) ); diff --git a/lib/action-scheduler/classes/ActionScheduler_AsyncRequest_QueueRunner.php b/lib/action-scheduler/classes/ActionScheduler_AsyncRequest_QueueRunner.php index 57706a24..2eaf86d5 100644 --- a/lib/action-scheduler/classes/ActionScheduler_AsyncRequest_QueueRunner.php +++ b/lib/action-scheduler/classes/ActionScheduler_AsyncRequest_QueueRunner.php @@ -1,7 +1,4 @@ get_sleep_seconds(); diff --git a/lib/action-scheduler/classes/ActionScheduler_Compatibility.php b/lib/action-scheduler/classes/ActionScheduler_Compatibility.php index bb28023b..ede48aa6 100644 --- a/lib/action-scheduler/classes/ActionScheduler_Compatibility.php +++ b/lib/action-scheduler/classes/ActionScheduler_Compatibility.php @@ -49,7 +49,7 @@ public static function raise_memory_limit() { return wp_raise_memory_limit( 'admin' ); } - $current_limit = @ini_get( 'memory_limit' ); + $current_limit = @ini_get( 'memory_limit' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $current_limit_int = self::convert_hr_to_bytes( $current_limit ); if ( -1 === $current_limit_int ) { @@ -61,6 +61,9 @@ public static function raise_memory_limit() { $filtered_limit = apply_filters( 'admin_memory_limit', $wp_max_limit ); $filtered_limit_int = self::convert_hr_to_bytes( $filtered_limit ); + // phpcs:disable WordPress.PHP.IniSet.memory_limit_Blacklisted + // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged + if ( -1 === $filtered_limit_int || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) { if ( false !== @ini_set( 'memory_limit', $filtered_limit ) ) { return $filtered_limit; @@ -74,6 +77,9 @@ public static function raise_memory_limit() { return false; } } + + // phpcs:enable + return false; } @@ -85,7 +91,7 @@ public static function raise_memory_limit() { * @param int $limit The time limit in seconds. */ public static function raise_time_limit( $limit = 0 ) { - $limit = (int) $limit; + $limit = (int) $limit; $max_execution_time = (int) ini_get( 'max_execution_time' ); // If the max execution time is already set to zero (unlimited), there is no reason to make a further change. diff --git a/lib/action-scheduler/classes/ActionScheduler_DataController.php b/lib/action-scheduler/classes/ActionScheduler_DataController.php index eb69847b..89ca4a00 100644 --- a/lib/action-scheduler/classes/ActionScheduler_DataController.php +++ b/lib/action-scheduler/classes/ActionScheduler_DataController.php @@ -18,10 +18,10 @@ class ActionScheduler_DataController { const DATASTORE_CLASS = 'ActionScheduler_DBStore'; /** Logger data store class name. */ - const LOGGER_CLASS = 'ActionScheduler_DBLogger'; + const LOGGER_CLASS = 'ActionScheduler_DBLogger'; /** Migration status option name. */ - const STATUS_FLAG = 'action_scheduler_migration_status'; + const STATUS_FLAG = 'action_scheduler_migration_status'; /** Migration status option value. */ const STATUS_COMPLETE = 'complete'; @@ -29,13 +29,25 @@ class ActionScheduler_DataController { /** Migration minimum required PHP version. */ const MIN_PHP_VERSION = '5.5'; - /** @var ActionScheduler_DataController */ + /** + * Instance. + * + * @var ActionScheduler_DataController + */ private static $instance; - /** @var int */ + /** + * Sleep time in seconds. + * + * @var int + */ private static $sleep_time = 0; - /** @var int */ + /** + * Tick count required for freeing memory. + * + * @var int + */ private static $free_ticks = 50; /** @@ -137,6 +149,8 @@ public static function free_memory() { \WP_CLI::warning( __( 'Attempting to reduce used memory...', 'action-scheduler' ) ); /** + * Globals. + * * @var $wpdb \wpdb * @var $wp_object_cache \WP_Object_Cache */ @@ -148,13 +162,22 @@ public static function free_memory() { return; } - $wp_object_cache->group_ops = array(); - $wp_object_cache->stats = array(); - $wp_object_cache->memcache_debug = array(); - $wp_object_cache->cache = array(); + // Not all drop-ins support these props, however, there may be existing installations that rely on these being cleared. + if ( property_exists( $wp_object_cache, 'group_ops' ) ) { + $wp_object_cache->group_ops = array(); + } + if ( property_exists( $wp_object_cache, 'stats' ) ) { + $wp_object_cache->stats = array(); + } + if ( property_exists( $wp_object_cache, 'memcache_debug' ) ) { + $wp_object_cache->memcache_debug = array(); + } + if ( property_exists( $wp_object_cache, 'cache' ) ) { + $wp_object_cache->cache = array(); + } if ( is_callable( array( $wp_object_cache, '__remoteset' ) ) ) { - call_user_func( array( $wp_object_cache, '__remoteset' ) ); // important + call_user_func( array( $wp_object_cache, '__remoteset' ) ); // important! } } diff --git a/lib/action-scheduler/classes/ActionScheduler_DateTime.php b/lib/action-scheduler/classes/ActionScheduler_DateTime.php index b142ca81..3d3f68bf 100644 --- a/lib/action-scheduler/classes/ActionScheduler_DateTime.php +++ b/lib/action-scheduler/classes/ActionScheduler_DateTime.php @@ -15,7 +15,7 @@ class ActionScheduler_DateTime extends DateTime { * * @var int */ - protected $utcOffset = 0; + protected $utcOffset = 0; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase /** * Get the unix timestamp of the current object. @@ -34,10 +34,10 @@ public function getTimestamp() { * * This represents a fixed offset instead of a timezone setting. * - * @param $offset + * @param string|int $offset UTC offset value. */ public function setUtcOffset( $offset ) { - $this->utcOffset = intval( $offset ); + $this->utcOffset = intval( $offset ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase } /** @@ -48,20 +48,20 @@ public function setUtcOffset( $offset ) { */ #[\ReturnTypeWillChange] public function getOffset() { - return $this->utcOffset ? $this->utcOffset : parent::getOffset(); + return $this->utcOffset ? $this->utcOffset : parent::getOffset(); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase } /** * Set the TimeZone associated with the DateTime * - * @param DateTimeZone $timezone + * @param DateTimeZone $timezone Timezone object. * * @return static * @link http://php.net/manual/en/datetime.settimezone.php */ #[\ReturnTypeWillChange] public function setTimezone( $timezone ) { - $this->utcOffset = 0; + $this->utcOffset = 0; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase parent::setTimezone( $timezone ); return $this; diff --git a/lib/action-scheduler/classes/ActionScheduler_FatalErrorMonitor.php b/lib/action-scheduler/classes/ActionScheduler_FatalErrorMonitor.php index 5fa67d68..26eb93ae 100644 --- a/lib/action-scheduler/classes/ActionScheduler_FatalErrorMonitor.php +++ b/lib/action-scheduler/classes/ActionScheduler_FatalErrorMonitor.php @@ -4,51 +4,94 @@ * Class ActionScheduler_FatalErrorMonitor */ class ActionScheduler_FatalErrorMonitor { - /** @var ActionScheduler_ActionClaim */ - private $claim = NULL; - /** @var ActionScheduler_Store */ - private $store = NULL; + + /** + * ActionScheduler_ActionClaim instance. + * + * @var ActionScheduler_ActionClaim + */ + private $claim = null; + + /** + * ActionScheduler_Store instance. + * + * @var ActionScheduler_Store + */ + private $store = null; + + /** + * Current action's ID. + * + * @var int + */ private $action_id = 0; + /** + * Construct. + * + * @param ActionScheduler_Store $store Action store. + */ public function __construct( ActionScheduler_Store $store ) { $this->store = $store; } + /** + * Start monitoring. + * + * @param ActionScheduler_ActionClaim $claim Claimed actions. + */ public function attach( ActionScheduler_ActionClaim $claim ) { $this->claim = $claim; add_action( 'shutdown', array( $this, 'handle_unexpected_shutdown' ) ); add_action( 'action_scheduler_before_execute', array( $this, 'track_current_action' ), 0, 1 ); - add_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0, 0 ); - add_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0, 0 ); - add_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0, 0 ); + add_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0, 0 ); + add_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0, 0 ); + add_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0, 0 ); } + /** + * Stop monitoring. + */ public function detach() { - $this->claim = NULL; + $this->claim = null; $this->untrack_action(); remove_action( 'shutdown', array( $this, 'handle_unexpected_shutdown' ) ); remove_action( 'action_scheduler_before_execute', array( $this, 'track_current_action' ), 0 ); - remove_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0 ); - remove_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0 ); - remove_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0 ); + remove_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0 ); + remove_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0 ); + remove_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0 ); } + /** + * Track specified action. + * + * @param int $action_id Action ID to track. + */ public function track_current_action( $action_id ) { $this->action_id = $action_id; } + /** + * Un-track action. + */ public function untrack_action() { $this->action_id = 0; } + /** + * Handle unexpected shutdown. + */ public function handle_unexpected_shutdown() { - if ( $error = error_get_last() ) { - if ( in_array( $error['type'], array( E_ERROR, E_PARSE, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR ) ) ) { - if ( !empty($this->action_id) ) { + $error = error_get_last(); + + if ( $error ) { + if ( in_array( $error['type'], array( E_ERROR, E_PARSE, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR ), true ) ) { + if ( ! empty( $this->action_id ) ) { $this->store->mark_failure( $this->action_id ); do_action( 'action_scheduler_unexpected_shutdown', $this->action_id, $error ); } } + $this->store->release_claim( $this->claim ); } } diff --git a/lib/action-scheduler/classes/ActionScheduler_InvalidActionException.php b/lib/action-scheduler/classes/ActionScheduler_InvalidActionException.php index 40b45599..c64e971e 100644 --- a/lib/action-scheduler/classes/ActionScheduler_InvalidActionException.php +++ b/lib/action-scheduler/classes/ActionScheduler_InvalidActionException.php @@ -13,6 +13,7 @@ class ActionScheduler_InvalidActionException extends \InvalidArgumentException i * Create a new exception when the action's schedule cannot be fetched. * * @param string $action_id The action ID with bad args. + * @param mixed $schedule Passed schedule. * @return static */ public static function from_schedule( $action_id, $schedule ) { @@ -20,7 +21,7 @@ public static function from_schedule( $action_id, $schedule ) { /* translators: 1: action ID 2: schedule */ __( 'Action [%1$s] has an invalid schedule: %2$s', 'action-scheduler' ), $action_id, - var_export( $schedule, true ) + var_export( $schedule, true ) // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export ); return new static( $message ); @@ -29,9 +30,8 @@ public static function from_schedule( $action_id, $schedule ) { /** * Create a new exception when the action's args cannot be decoded to an array. * - * @author Jeremy Pry - * * @param string $action_id The action ID with bad args. + * @param mixed $args Passed arguments. * @return static */ public static function from_decoding_args( $action_id, $args = array() ) { @@ -39,7 +39,7 @@ public static function from_decoding_args( $action_id, $args = array() ) { /* translators: 1: action ID 2: arguments */ __( 'Action [%1$s] has invalid arguments. It cannot be JSON decoded to an array. $args = %2$s', 'action-scheduler' ), $action_id, - var_export( $args, true ) + var_export( $args, true ) // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export ); return new static( $message ); diff --git a/lib/action-scheduler/classes/ActionScheduler_ListTable.php b/lib/action-scheduler/classes/ActionScheduler_ListTable.php index abf767ce..e8f1e431 100644 --- a/lib/action-scheduler/classes/ActionScheduler_ListTable.php +++ b/lib/action-scheduler/classes/ActionScheduler_ListTable.php @@ -2,6 +2,7 @@ /** * Implements the admin view of the actions. + * * @codeCoverageIgnore */ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable { @@ -49,9 +50,8 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable { protected $runner; /** - * Bulk actions. The key of the array is the method name of the implementation: - * - * bulk_(array $ids, string $sql_in). + * Bulk actions. The key of the array is the method name of the implementation. + * Example: bulk_(array $ids, string $sql_in). * * See the comments in the parent class for further details * @@ -76,9 +76,9 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable { /** * Sets the current data store object into `store->action` and initialises the object. * - * @param ActionScheduler_Store $store - * @param ActionScheduler_Logger $logger - * @param ActionScheduler_QueueRunner $runner + * @param ActionScheduler_Store $store Store object. + * @param ActionScheduler_Logger $logger Logger object. + * @param ActionScheduler_QueueRunner $runner Runner object. */ public function __construct( ActionScheduler_Store $store, ActionScheduler_Logger $logger, ActionScheduler_QueueRunner $runner ) { @@ -118,16 +118,16 @@ public function __construct( ActionScheduler_Store $store, ActionScheduler_Logge if ( empty( $request_status ) ) { $this->sort_by[] = 'status'; - } elseif ( in_array( $request_status, array( 'in-progress', 'failed' ) ) ) { + } elseif ( in_array( $request_status, array( 'in-progress', 'failed' ), true ) ) { $this->columns += array( 'claim_id' => __( 'Claim ID', 'action-scheduler' ) ); $this->sort_by[] = 'claim_id'; } $this->row_actions = array( 'hook' => array( - 'run' => array( - 'name' => __( 'Run', 'action-scheduler' ), - 'desc' => __( 'Process the action now as if it were run as part of a queue', 'action-scheduler' ), + 'run' => array( + 'name' => __( 'Run', 'action-scheduler' ), + 'desc' => __( 'Process the action now as if it were run as part of a queue', 'action-scheduler' ), ), 'cancel' => array( 'name' => __( 'Cancel', 'action-scheduler' ), @@ -224,9 +224,10 @@ private static function human_interval( $interval, $periods_to_include = 2 ) { return __( 'Now!', 'action-scheduler' ); } - $output = ''; + $output = ''; + $num_time_periods = count( self::$time_periods ); - for ( $time_period_index = 0, $periods_included = 0, $seconds_remaining = $interval; $time_period_index < count( self::$time_periods ) && $seconds_remaining > 0 && $periods_included < $periods_to_include; $time_period_index++ ) { + for ( $time_period_index = 0, $periods_included = 0, $seconds_remaining = $interval; $time_period_index < $num_time_periods && $seconds_remaining > 0 && $periods_included < $periods_to_include; $time_period_index++ ) { $periods_in_interval = floor( $seconds_remaining / self::$time_periods[ $time_period_index ]['seconds'] ); @@ -234,7 +235,7 @@ private static function human_interval( $interval, $periods_to_include = 2 ) { if ( ! empty( $output ) ) { $output .= ' '; } - $output .= sprintf( translate_nooped_plural( self::$time_periods[ $time_period_index ]['names'], $periods_in_interval, 'action-scheduler' ), $periods_in_interval ); + $output .= sprintf( translate_nooped_plural( self::$time_periods[ $time_period_index ]['names'], $periods_in_interval, 'action-scheduler' ), $periods_in_interval ); $seconds_remaining -= $periods_in_interval * self::$time_periods[ $time_period_index ]['seconds']; $periods_included++; } @@ -246,7 +247,7 @@ private static function human_interval( $interval, $periods_to_include = 2 ) { /** * Returns the recurrence of an action or 'Non-repeating'. The output is human readable. * - * @param ActionScheduler_Action $action + * @param ActionScheduler_Action $action Action object. * * @return string */ @@ -269,7 +270,7 @@ protected function get_recurrence( $action ) { /** * Serializes the argument of an action to render it in a human friendly format. * - * @param array $row The array representation of the current row of the table + * @param array $row The array representation of the current row of the table. * * @return string */ @@ -280,7 +281,7 @@ public function column_args( array $row ) { $row_html = ''; @@ -311,8 +312,8 @@ public function column_log_entries( array $row ) { /** * Prints the logs entries inline. We do so to avoid loading Javascript and other hacks to show it in a modal. * - * @param ActionScheduler_LogEntry $log_entry - * @param DateTimezone $timezone + * @param ActionScheduler_LogEntry $log_entry Log entry object. + * @param DateTimezone $timezone Timestamp. * @return string */ protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, DateTimezone $timezone ) { @@ -324,13 +325,13 @@ protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, Date /** * Only display row actions for pending actions. * - * @param array $row Row to render - * @param string $column_name Current row + * @param array $row Row to render. + * @param string $column_name Current row. * * @return string */ protected function maybe_render_actions( $row, $column_name ) { - if ( 'pending' === strtolower( $row[ 'status_name' ] ) ) { + if ( 'pending' === strtolower( $row['status_name'] ) ) { return parent::maybe_render_actions( $row, $column_name ); } @@ -358,10 +359,10 @@ public function display_admin_notices() { $found_tables = $wpdb->get_col( "SHOW TABLES LIKE '{$wpdb->prefix}actionscheduler%'" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared foreach ( $table_list as $table_name ) { - if ( ! in_array( $wpdb->prefix . $table_name, $found_tables ) ) { + if ( ! in_array( $wpdb->prefix . $table_name, $found_tables, true ) ) { $this->admin_notices[] = array( 'class' => 'error', - 'message' => __( 'It appears one or more database tables were missing. Attempting to re-create the missing table(s).' , 'action-scheduler' ), + 'message' => __( 'It appears one or more database tables were missing. Attempting to re-create the missing table(s).', 'action-scheduler' ), ); $this->recreate_tables(); parent::display_admin_notices(); @@ -390,9 +391,9 @@ public function display_admin_notices() { $async_request_lock_expiration = ActionScheduler::lock()->get_expiration( 'async-request-runner' ); - // No lock set or lock expired + // No lock set or lock expired. if ( false === $async_request_lock_expiration || $async_request_lock_expiration < time() ) { - $in_progress_url = add_query_arg( 'status', 'in-progress', remove_query_arg( 'status' ) ); + $in_progress_url = add_query_arg( 'status', 'in-progress', remove_query_arg( 'status' ) ); /* translators: %s: process URL */ $async_request_message = sprintf( __( 'A new queue has begun processing. View actions in-progress »', 'action-scheduler' ), esc_url( $in_progress_url ) ); } else { @@ -411,20 +412,21 @@ public function display_admin_notices() { if ( is_array( $notification ) ) { delete_transient( 'action_scheduler_admin_notice' ); - $action = $this->store->fetch_action( $notification['action_id'] ); + $action = $this->store->fetch_action( $notification['action_id'] ); $action_hook_html = '' . $action->get_hook() . ''; - if ( 1 == $notification['success'] ) { + + if ( 1 === absint( $notification['success'] ) ) { $class = 'updated'; switch ( $notification['row_action_type'] ) { - case 'run' : + case 'run': /* translators: %s: action HTML */ $action_message_html = sprintf( __( 'Successfully executed action: %s', 'action-scheduler' ), $action_hook_html ); break; - case 'cancel' : + case 'cancel': /* translators: %s: action HTML */ $action_message_html = sprintf( __( 'Successfully canceled action: %s', 'action-scheduler' ), $action_hook_html ); break; - default : + default: /* translators: %s: action HTML */ $action_message_html = sprintf( __( 'Successfully processed change for action: %s', 'action-scheduler' ), $action_hook_html ); break; @@ -449,7 +451,7 @@ public function display_admin_notices() { /** * Prints the scheduled date in a human friendly format. * - * @param array $row The array representation of the current row of the table + * @param array $row The array representation of the current row of the table. * * @return string */ @@ -460,7 +462,7 @@ public function column_schedule( $row ) { /** * Get the scheduled date in a human friendly format. * - * @param ActionScheduler_Schedule $schedule + * @param ActionScheduler_Schedule $schedule Action's schedule. * @return string */ protected function get_schedule_display_string( ActionScheduler_Schedule $schedule ) { @@ -492,13 +494,13 @@ protected function get_schedule_display_string( ActionScheduler_Schedule $schedu } /** - * Bulk delete + * Bulk delete. * * Deletes actions based on their ID. This is the handler for the bulk delete. It assumes the data * properly validated by the callee and it will delete the actions without any extra validation. * - * @param array $ids - * @param string $ids_sql Inherited and unused + * @param int[] $ids Action IDs. + * @param string $ids_sql Inherited and unused. */ protected function bulk_delete( array $ids, $ids_sql ) { foreach ( $ids as $id ) { @@ -507,6 +509,7 @@ protected function bulk_delete( array $ids, $ids_sql ) { } catch ( Exception $e ) { // A possible reason for an exception would include a scenario where the same action is deleted by a // concurrent request. + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( sprintf( /* translators: 1: action ID 2: exception message. */ @@ -523,7 +526,7 @@ protected function bulk_delete( array $ids, $ids_sql ) { * Implements the logic behind running an action. ActionScheduler_Abstract_ListTable validates the request and their * parameters are valid. * - * @param int $action_id + * @param int $action_id Action ID. */ protected function row_action_cancel( $action_id ) { $this->process_row_action( $action_id, 'cancel' ); @@ -533,7 +536,7 @@ protected function row_action_cancel( $action_id ) { * Implements the logic behind running an action. ActionScheduler_Abstract_ListTable validates the request and their * parameters are valid. * - * @param int $action_id + * @param int $action_id Action ID. */ protected function row_action_run( $action_id ) { $this->process_row_action( $action_id, 'run' ); @@ -560,23 +563,23 @@ protected function recreate_tables() { /** * Implements the logic behind processing an action once an action link is clicked on the list table. * - * @param int $action_id + * @param int $action_id Action ID. * @param string $row_action_type The type of action to perform on the action. */ protected function process_row_action( $action_id, $row_action_type ) { try { switch ( $row_action_type ) { - case 'run' : + case 'run': $this->runner->process_action( $action_id, 'Admin List Table' ); break; - case 'cancel' : + case 'cancel': $this->store->cancel_action( $action_id ); break; } - $success = 1; + $success = 1; $error_message = ''; } catch ( Exception $e ) { - $success = 0; + $success = 0; $error_message = $e->getMessage(); } @@ -639,11 +642,13 @@ public function prepare_items() { ); } - $this->set_pagination_args( array( - 'total_items' => $total_items, - 'per_page' => $per_page, - 'total_pages' => ceil( $total_items / $per_page ), - ) ); + $this->set_pagination_args( + array( + 'total_items' => $total_items, + 'per_page' => $per_page, + 'total_pages' => ceil( $total_items / $per_page ), + ) + ); } /** diff --git a/lib/action-scheduler/classes/ActionScheduler_LogEntry.php b/lib/action-scheduler/classes/ActionScheduler_LogEntry.php index 649636de..726fea2e 100644 --- a/lib/action-scheduler/classes/ActionScheduler_LogEntry.php +++ b/lib/action-scheduler/classes/ActionScheduler_LogEntry.php @@ -6,16 +6,22 @@ class ActionScheduler_LogEntry { /** + * Action's ID for log entry. + * * @var int $action_id */ - protected $action_id = ''; + protected $action_id = ''; /** + * Log entry's message. + * * @var string $message */ - protected $message = ''; + protected $message = ''; /** + * Log entry's date. + * * @var Datetime $date */ protected $date; @@ -23,13 +29,12 @@ class ActionScheduler_LogEntry { /** * Constructor * - * @param mixed $action_id Action ID - * @param string $message Message - * @param Datetime $date Datetime object with the time when this log entry was created. If this parameter is - * not provided a new Datetime object (with current time) will be created. + * @param mixed $action_id Action ID. + * @param string $message Message. + * @param Datetime $date Datetime object with the time when this log entry was created. If this parameter is + * not provided a new Datetime object (with current time) will be created. */ public function __construct( $action_id, $message, $date = null ) { - /* * ActionScheduler_wpCommentLogger::get_entry() previously passed a 3rd param of $comment->comment_type * to ActionScheduler_LogEntry::__construct(), goodness knows why, and the Follow-up Emails plugin @@ -44,7 +49,7 @@ public function __construct( $action_id, $message, $date = null ) { $this->action_id = $action_id; $this->message = $message; - $this->date = $date ? $date : new Datetime; + $this->date = $date ? $date : new Datetime(); } /** @@ -56,10 +61,16 @@ public function get_date() { return $this->date; } + /** + * Get action ID of log entry. + */ public function get_action_id() { return $this->action_id; } + /** + * Get log entry message. + */ public function get_message() { return $this->message; } diff --git a/lib/action-scheduler/classes/ActionScheduler_NullLogEntry.php b/lib/action-scheduler/classes/ActionScheduler_NullLogEntry.php index 6f8f218a..4df0f05a 100644 --- a/lib/action-scheduler/classes/ActionScheduler_NullLogEntry.php +++ b/lib/action-scheduler/classes/ActionScheduler_NullLogEntry.php @@ -4,8 +4,15 @@ * Class ActionScheduler_NullLogEntry */ class ActionScheduler_NullLogEntry extends ActionScheduler_LogEntry { + + /** + * Construct. + * + * @param string $action_id Action ID. + * @param string $message Log entry. + */ public function __construct( $action_id = '', $message = '' ) { - // nothing to see here + // nothing to see here. } + } - \ No newline at end of file diff --git a/lib/action-scheduler/classes/ActionScheduler_OptionLock.php b/lib/action-scheduler/classes/ActionScheduler_OptionLock.php index 911f9b77..547857a0 100644 --- a/lib/action-scheduler/classes/ActionScheduler_OptionLock.php +++ b/lib/action-scheduler/classes/ActionScheduler_OptionLock.php @@ -5,6 +5,7 @@ * for up-to a given duration. * * Class ActionScheduler_OptionLock + * * @since 3.0.0 */ class ActionScheduler_OptionLock extends ActionScheduler_Lock { diff --git a/lib/action-scheduler/classes/ActionScheduler_QueueCleaner.php b/lib/action-scheduler/classes/ActionScheduler_QueueCleaner.php index 6b066dca..7029d0b2 100644 --- a/lib/action-scheduler/classes/ActionScheduler_QueueCleaner.php +++ b/lib/action-scheduler/classes/ActionScheduler_QueueCleaner.php @@ -5,10 +5,18 @@ */ class ActionScheduler_QueueCleaner { - /** @var int */ + /** + * The batch size. + * + * @var int + */ protected $batch_size; - /** @var ActionScheduler_Store */ + /** + * ActionScheduler_Store instance. + * + * @var ActionScheduler_Store + */ private $store = null; /** @@ -19,21 +27,23 @@ class ActionScheduler_QueueCleaner { private $month_in_seconds = 2678400; /** - * @var string[] Default list of statuses purged by the cleaner process. + * Default list of statuses purged by the cleaner process. + * + * @var string[] */ - private $default_statuses_to_purge = [ + private $default_statuses_to_purge = array( ActionScheduler_Store::STATUS_COMPLETE, ActionScheduler_Store::STATUS_CANCELED, - ]; + ); /** * ActionScheduler_QueueCleaner constructor. * - * @param ActionScheduler_Store $store The store instance. - * @param int $batch_size The batch size. + * @param ActionScheduler_Store|null $store The store instance. + * @param int $batch_size The batch size. */ - public function __construct( ActionScheduler_Store $store = null, $batch_size = 20 ) { - $this->store = $store ? $store : ActionScheduler_Store::instance(); + public function __construct( ?ActionScheduler_Store $store = null, $batch_size = 20 ) { + $this->store = $store ? $store : ActionScheduler_Store::instance(); $this->batch_size = $batch_size; } @@ -66,7 +76,6 @@ public function delete_old_actions() { return array(); } - /** * Filter the statuses when cleaning the queue. * @@ -83,26 +92,30 @@ public function delete_old_actions() { * @param string[] $statuses_to_purge List of action statuses to purge. Defaults to canceled, complete. * @param DateTime $cutoff_date Date limit for selecting actions. Defaults to 31 days ago. * @param int|null $batch_size Maximum number of actions per status to delete. Defaults to 20. - * @param string $context Calling process context. Defaults to `old`. + * @param string $context Calling process context. Defaults to `old`. * @return array Actions deleted. */ public function clean_actions( array $statuses_to_purge, DateTime $cutoff_date, $batch_size = null, $context = 'old' ) { - $batch_size = $batch_size !== null ? $batch_size : $this->batch_size; - $cutoff = $cutoff_date !== null ? $cutoff_date : as_get_datetime_object( $this->month_in_seconds . ' seconds ago' ); + $batch_size = ! is_null( $batch_size ) ? $batch_size : $this->batch_size; + $cutoff = ! is_null( $cutoff_date ) ? $cutoff_date : as_get_datetime_object( $this->month_in_seconds . ' seconds ago' ); $lifespan = time() - $cutoff->getTimestamp(); + if ( empty( $statuses_to_purge ) ) { $statuses_to_purge = $this->default_statuses_to_purge; } - $deleted_actions = []; + $deleted_actions = array(); + foreach ( $statuses_to_purge as $status ) { - $actions_to_delete = $this->store->query_actions( array( - 'status' => $status, - 'modified' => $cutoff, - 'modified_compare' => '<=', - 'per_page' => $batch_size, - 'orderby' => 'none', - ) ); + $actions_to_delete = $this->store->query_actions( + array( + 'status' => $status, + 'modified' => $cutoff, + 'modified_compare' => '<=', + 'per_page' => $batch_size, + 'orderby' => 'none', + ) + ); $deleted_actions = array_merge( $deleted_actions, $this->delete_actions( $actions_to_delete, $lifespan, $context ) ); } @@ -111,14 +124,17 @@ public function clean_actions( array $statuses_to_purge, DateTime $cutoff_date, } /** - * @param int[] $actions_to_delete List of action IDs to delete. - * @param int $lifespan Minimum scheduled age in seconds of the actions being deleted. + * Delete actions. + * + * @param int[] $actions_to_delete List of action IDs to delete. + * @param int $lifespan Minimum scheduled age in seconds of the actions being deleted. * @param string $context Context of the delete request. * @return array Deleted action IDs. */ private function delete_actions( array $actions_to_delete, $lifespan = null, $context = 'old' ) { - $deleted_actions = []; - if ( $lifespan === null ) { + $deleted_actions = array(); + + if ( is_null( $lifespan ) ) { $lifespan = $this->month_in_seconds; } @@ -138,7 +154,6 @@ private function delete_actions( array $actions_to_delete, $lifespan = null, $co * @param int $lifespan The retention period, in seconds, for old actions * @param int $count_of_actions_to_delete The number of old actions being deleted in this batch * @since 2.0.0 - * */ do_action( "action_scheduler_failed_{$context}_action_deletion", $action_id, $e, $lifespan, count( $actions_to_delete ) ); } @@ -156,18 +171,22 @@ private function delete_actions( array $actions_to_delete, $lifespan = null, $co */ public function reset_timeouts( $time_limit = 300 ) { $timeout = apply_filters( 'action_scheduler_timeout_period', $time_limit ); + if ( $timeout < 0 ) { return; } - $cutoff = as_get_datetime_object($timeout.' seconds ago'); - $actions_to_reset = $this->store->query_actions( array( - 'status' => ActionScheduler_Store::STATUS_PENDING, - 'modified' => $cutoff, - 'modified_compare' => '<=', - 'claimed' => true, - 'per_page' => $this->get_batch_size(), - 'orderby' => 'none', - ) ); + + $cutoff = as_get_datetime_object( $timeout . ' seconds ago' ); + $actions_to_reset = $this->store->query_actions( + array( + 'status' => ActionScheduler_Store::STATUS_PENDING, + 'modified' => $cutoff, + 'modified_compare' => '<=', + 'claimed' => true, + 'per_page' => $this->get_batch_size(), + 'orderby' => 'none', + ) + ); foreach ( $actions_to_reset as $action_id ) { $this->store->unclaim_action( $action_id ); @@ -186,17 +205,21 @@ public function reset_timeouts( $time_limit = 300 ) { */ public function mark_failures( $time_limit = 300 ) { $timeout = apply_filters( 'action_scheduler_failure_period', $time_limit ); + if ( $timeout < 0 ) { return; } - $cutoff = as_get_datetime_object($timeout.' seconds ago'); - $actions_to_reset = $this->store->query_actions( array( - 'status' => ActionScheduler_Store::STATUS_RUNNING, - 'modified' => $cutoff, - 'modified_compare' => '<=', - 'per_page' => $this->get_batch_size(), - 'orderby' => 'none', - ) ); + + $cutoff = as_get_datetime_object( $timeout . ' seconds ago' ); + $actions_to_reset = $this->store->query_actions( + array( + 'status' => ActionScheduler_Store::STATUS_RUNNING, + 'modified' => $cutoff, + 'modified_compare' => '<=', + 'per_page' => $this->get_batch_size(), + 'orderby' => 'none', + ) + ); foreach ( $actions_to_reset as $action_id ) { $this->store->mark_failure( $action_id ); @@ -208,7 +231,6 @@ public function mark_failures( $time_limit = 300 ) { * Do all of the cleaning actions. * * @param int $time_limit The number of seconds to use as the timeout and failure period. Default 300 (5 minutes). - * @author Jeremy Pry */ public function clean( $time_limit = 300 ) { $this->delete_old_actions(); @@ -219,7 +241,6 @@ public function clean( $time_limit = 300 ) { /** * Get the batch size for cleaning the queue. * - * @author Jeremy Pry * @return int */ protected function get_batch_size() { diff --git a/lib/action-scheduler/classes/ActionScheduler_QueueRunner.php b/lib/action-scheduler/classes/ActionScheduler_QueueRunner.php index 90e64fc4..13a71e79 100644 --- a/lib/action-scheduler/classes/ActionScheduler_QueueRunner.php +++ b/lib/action-scheduler/classes/ActionScheduler_QueueRunner.php @@ -8,35 +8,51 @@ class ActionScheduler_QueueRunner extends ActionScheduler_Abstract_QueueRunner { const WP_CRON_SCHEDULE = 'every_minute'; - /** @var ActionScheduler_AsyncRequest_QueueRunner */ + /** + * ActionScheduler_AsyncRequest_QueueRunner instance. + * + * @var ActionScheduler_AsyncRequest_QueueRunner + */ protected $async_request; - /** @var ActionScheduler_QueueRunner */ + /** + * ActionScheduler_QueueRunner instance. + * + * @var ActionScheduler_QueueRunner + */ private static $runner = null; - /** @var int */ + /** + * Number of processed actions. + * + * @var int + */ private $processed_actions_count = 0; /** + * Get instance. + * * @return ActionScheduler_QueueRunner * @codeCoverageIgnore */ public static function instance() { - if ( empty(self::$runner) ) { - $class = apply_filters('action_scheduler_queue_runner_class', 'ActionScheduler_QueueRunner'); + if ( empty( self::$runner ) ) { + $class = apply_filters( 'action_scheduler_queue_runner_class', 'ActionScheduler_QueueRunner' ); self::$runner = new $class(); } + return self::$runner; } /** * ActionScheduler_QueueRunner constructor. * - * @param ActionScheduler_Store $store - * @param ActionScheduler_FatalErrorMonitor $monitor - * @param ActionScheduler_QueueCleaner $cleaner + * @param ActionScheduler_Store|null $store Store object. + * @param ActionScheduler_FatalErrorMonitor|null $monitor Monitor object. + * @param ActionScheduler_QueueCleaner|null $cleaner Cleaner object. + * @param ActionScheduler_AsyncRequest_QueueRunner|null $async_request Async request runner object. */ - public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null, ActionScheduler_AsyncRequest_QueueRunner $async_request = null ) { + public function __construct( ?ActionScheduler_Store $store = null, ?ActionScheduler_FatalErrorMonitor $monitor = null, ?ActionScheduler_QueueCleaner $cleaner = null, ?ActionScheduler_AsyncRequest_QueueRunner $async_request = null ) { parent::__construct( $store, $monitor, $cleaner ); if ( is_null( $async_request ) ) { @@ -47,13 +63,15 @@ public function __construct( ActionScheduler_Store $store = null, ActionSchedule } /** + * Initialize. + * * @codeCoverageIgnore */ public function init() { - add_filter( 'cron_schedules', array( self::instance(), 'add_wp_cron_schedule' ) ); + add_filter( 'cron_schedules', array( self::instance(), 'add_wp_cron_schedule' ) ); // phpcs:ignore WordPress.WP.CronInterval.CronSchedulesInterval - // Check for and remove any WP Cron hook scheduled by Action Scheduler < 3.0.0, which didn't include the $context param + // Check for and remove any WP Cron hook scheduled by Action Scheduler < 3.0.0, which didn't include the $context param. $next_timestamp = wp_next_scheduled( self::WP_CRON_HOOK ); if ( $next_timestamp ) { wp_unschedule_event( $next_timestamp, self::WP_CRON_HOOK ); @@ -120,6 +138,7 @@ public function maybe_dispatch_async_request() { * that was the only context in which this method was run, and the self::WP_CRON_HOOK hook had no context * passed along with it. New code calling this method directly, or by triggering the self::WP_CRON_HOOK, * should set a context as the first parameter. For an example of this, refer to the code seen in + * * @see ActionScheduler_AsyncRequest_QueueRunner::handle() * * @param string $context Optional identifier for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron' @@ -134,11 +153,11 @@ public function run( $context = 'WP Cron' ) { $this->processed_actions_count = 0; if ( false === $this->has_maximum_concurrent_batches() ) { - $batch_size = apply_filters( 'action_scheduler_queue_runner_batch_size', 25 ); do { + $batch_size = apply_filters( 'action_scheduler_queue_runner_batch_size', 25 ); $processed_actions_in_batch = $this->do_batch( $batch_size, $context ); $this->processed_actions_count += $processed_actions_in_batch; - } while ( $processed_actions_in_batch > 0 && ! $this->batch_limits_exceeded( $this->processed_actions_count ) ); // keep going until we run out of actions, time, or memory + } while ( $processed_actions_in_batch > 0 && ! $this->batch_limits_exceeded( $this->processed_actions_count ) ); // keep going until we run out of actions, time, or memory. } do_action( 'action_scheduler_after_process_queue' ); @@ -151,19 +170,19 @@ public function run( $context = 'WP Cron' ) { * Actions are processed by claiming a set of pending actions then processing each one until either the batch * size is completed, or memory or time limits are reached, defined by @see $this->batch_limits_exceeded(). * - * @param int $size The maximum number of actions to process in the batch. + * @param int $size The maximum number of actions to process in the batch. * @param string $context Optional identifier for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron' - * Generally, this should be capitalised and not localised as it's a proper noun. + * Generally, this should be capitalised and not localised as it's a proper noun. * @return int The number of actions processed. */ protected function do_batch( $size = 100, $context = '' ) { - $claim = $this->store->stake_claim($size); - $this->monitor->attach($claim); + $claim = $this->store->stake_claim( $size ); + $this->monitor->attach( $claim ); $processed_actions = 0; foreach ( $claim->get_actions() as $action_id ) { - // bail if we lost the claim - if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $claim->get_id() ) ) ) { + // bail if we lost the claim. + if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $claim->get_id() ), true ) ) { break; } $this->process_action( $action_id, $context ); @@ -173,7 +192,7 @@ protected function do_batch( $size = 100, $context = '' ) { break; } } - $this->store->release_claim($claim); + $this->store->release_claim( $claim ); $this->monitor->detach(); $this->clear_caches(); return $processed_actions; @@ -218,9 +237,15 @@ protected function clear_caches() { } } + /** + * Add schedule to WP cron. + * + * @param array> $schedules Schedules. + * @return array> + */ public function add_wp_cron_schedule( $schedules ) { $schedules['every_minute'] = array( - 'interval' => 60, // in seconds + 'interval' => 60, // in seconds. 'display' => __( 'Every minute', 'action-scheduler' ), ); diff --git a/lib/action-scheduler/classes/ActionScheduler_RecurringActionScheduler.php b/lib/action-scheduler/classes/ActionScheduler_RecurringActionScheduler.php new file mode 100644 index 00000000..2f393ffc --- /dev/null +++ b/lib/action-scheduler/classes/ActionScheduler_RecurringActionScheduler.php @@ -0,0 +1,81 @@ + 'plugin', # or 'theme' + * 'name' => 'Name', + * ] + * + * @return array + */ + public static function active_source(): array { + $plugins = get_plugins(); + $plugin_files = array_keys( $plugins ); + + foreach ( $plugin_files as $plugin_file ) { + $plugin_path = trailingslashit( WP_PLUGIN_DIR ) . dirname( $plugin_file ); + $plugin_file = trailingslashit( WP_PLUGIN_DIR ) . $plugin_file; + + if ( 0 !== strpos( dirname( __DIR__ ), $plugin_path ) ) { + continue; + } + + $plugin_data = get_plugin_data( $plugin_file ); + + if ( ! is_array( $plugin_data ) || empty( $plugin_data['Name'] ) ) { + continue; + } + + return array( + 'type' => 'plugin', + 'name' => $plugin_data['Name'], + ); + } + + $themes = (array) search_theme_directories(); + + foreach ( $themes as $slug => $data ) { + $needle = trailingslashit( $data['theme_root'] ) . $slug . '/'; + + if ( 0 !== strpos( __FILE__, $needle ) ) { + continue; + } + + $theme = wp_get_theme( $slug ); + + if ( ! is_object( $theme ) || ! is_a( $theme, \WP_Theme::class ) ) { + continue; + } + + return array( + 'type' => 'theme', + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + 'name' => $theme->Name, + ); + } + + return array(); + } + + /** + * Returns the directory path for the currently active installation of Action Scheduler. + * + * @return string + */ + public static function active_source_path(): string { + return trailingslashit( dirname( __DIR__ ) ); + } + + /** + * Get registered sources. + * + * It is not always possible to obtain this information. For instance, if earlier versions (<=3.9.0) of + * Action Scheduler register themselves first, then the necessary data about registered sources will + * not be available. + * + * @return array + */ + public static function get_sources() { + $versions = ActionScheduler_Versions::instance(); + return method_exists( $versions, 'get_sources' ) ? $versions->get_sources() : array(); + } +} diff --git a/lib/action-scheduler/classes/ActionScheduler_Versions.php b/lib/action-scheduler/classes/ActionScheduler_Versions.php index 915c2e63..c23e464a 100644 --- a/lib/action-scheduler/classes/ActionScheduler_Versions.php +++ b/lib/action-scheduler/classes/ActionScheduler_Versions.php @@ -5,58 +5,147 @@ */ class ActionScheduler_Versions { /** + * ActionScheduler_Versions instance. + * * @var ActionScheduler_Versions */ - private static $instance = NULL; + private static $instance = null; + /** + * Versions. + * + * @var array + */ private $versions = array(); + /** + * Registered sources. + * + * @var array + */ + private $sources = array(); + + /** + * Register version's callback. + * + * @param string $version_string Action Scheduler version. + * @param callable $initialization_callback Callback to initialize the version. + */ public function register( $version_string, $initialization_callback ) { - if ( isset($this->versions[$version_string]) ) { - return FALSE; + if ( isset( $this->versions[ $version_string ] ) ) { + return false; } - $this->versions[$version_string] = $initialization_callback; - return TRUE; + + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace + $backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); + $source = $backtrace[0]['file']; + + $this->versions[ $version_string ] = $initialization_callback; + $this->sources[ $source ] = $version_string; + return true; } + /** + * Get all versions. + */ public function get_versions() { return $this->versions; } + /** + * Get registered sources. + * + * Use with caution: this method is only available as of Action Scheduler's 3.9.1 + * release and, owing to the way Action Scheduler is loaded, it's possible that the + * class definition used at runtime will belong to an earlier version. + * + * @since 3.9.1 + * + * @return array + */ + public function get_sources() { + return $this->sources; + } + + /** + * Get latest version registered. + */ public function latest_version() { - $keys = array_keys($this->versions); - if ( empty($keys) ) { + $keys = array_keys( $this->versions ); + if ( empty( $keys ) ) { return false; } uasort( $keys, 'version_compare' ); - return end($keys); + return end( $keys ); } + /** + * Get callback for latest registered version. + */ public function latest_version_callback() { $latest = $this->latest_version(); - if ( empty($latest) || !isset($this->versions[$latest]) ) { + + if ( empty( $latest ) || ! isset( $this->versions[ $latest ] ) ) { return '__return_null'; } - return $this->versions[$latest]; + + return $this->versions[ $latest ]; } /** + * Get instance. + * * @return ActionScheduler_Versions * @codeCoverageIgnore */ public static function instance() { - if ( empty(self::$instance) ) { + if ( empty( self::$instance ) ) { self::$instance = new self(); } return self::$instance; } /** + * Initialize. + * * @codeCoverageIgnore */ public static function initialize_latest_version() { $self = self::instance(); - call_user_func($self->latest_version_callback()); + call_user_func( $self->latest_version_callback() ); + } + + /** + * Returns information about the plugin or theme which contains the current active version + * of Action Scheduler. + * + * If this cannot be determined, or if Action Scheduler is being loaded via some other + * method, then it will return an empty array. Otherwise, if populated, the array will + * look like the following: + * + * [ + * 'type' => 'plugin', # or 'theme' + * 'name' => 'Name', + * ] + * + * @deprecated 3.9.2 Use ActionScheduler_SystemInformation::active_source(). + * + * @return array + */ + public function active_source(): array { + _deprecated_function( __METHOD__, '3.9.2', 'ActionScheduler_SystemInformation::active_source()' ); + return ActionScheduler_SystemInformation::active_source(); + } + + /** + * Returns the directory path for the currently active installation of Action Scheduler. + * + * @deprecated 3.9.2 Use ActionScheduler_SystemInformation::active_source_path(). + * + * @return string + */ + public function active_source_path(): string { + _deprecated_function( __METHOD__, '3.9.2', 'ActionScheduler_SystemInformation::active_source_path()' ); + return ActionScheduler_SystemInformation::active_source_path(); } } - \ No newline at end of file diff --git a/lib/action-scheduler/classes/ActionScheduler_WPCommentCleaner.php b/lib/action-scheduler/classes/ActionScheduler_WPCommentCleaner.php index 1ba552c5..2725d1fe 100644 --- a/lib/action-scheduler/classes/ActionScheduler_WPCommentCleaner.php +++ b/lib/action-scheduler/classes/ActionScheduler_WPCommentCleaner.php @@ -42,10 +42,10 @@ public static function init() { // While there are orphaned logs left in the comments table, we need to attach the callbacks which filter comment counts. add_action( 'pre_get_comments', array( self::$wp_comment_logger, 'filter_comment_queries' ), 10, 1 ); - add_action( 'wp_count_comments', array( self::$wp_comment_logger, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs + add_action( 'wp_count_comments', array( self::$wp_comment_logger, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs. add_action( 'comment_feed_where', array( self::$wp_comment_logger, 'filter_comment_feed' ), 10, 2 ); - // Action Scheduler may be displayed as a Tools screen or WooCommerce > Status administration screen + // Action Scheduler may be displayed as a Tools screen or WooCommerce > Status administration screen. add_action( 'load-tools_page_action-scheduler', array( __CLASS__, 'register_admin_notice' ) ); add_action( 'load-woocommerce_page_wc-status', array( __CLASS__, 'register_admin_notice' ) ); } @@ -66,13 +66,23 @@ public static function has_logs() { * Attached to the migration complete hook 'action_scheduler/migration_complete'. */ public static function maybe_schedule_cleanup() { - if ( (bool) get_comments( array( 'type' => ActionScheduler_wpCommentLogger::TYPE, 'number' => 1, 'fields' => 'ids' ) ) ) { - update_option( self::$has_logs_option_key, 'yes' ); + $has_logs = 'no'; + + $args = array( + 'type' => ActionScheduler_wpCommentLogger::TYPE, + 'number' => 1, + 'fields' => 'ids', + ); + + if ( (bool) get_comments( $args ) ) { + $has_logs = 'yes'; if ( ! as_next_scheduled_action( self::$cleanup_hook ) ) { as_schedule_single_action( gmdate( 'U' ) + ( 6 * MONTH_IN_SECONDS ), self::$cleanup_hook ); } } + + update_option( self::$has_logs_option_key, $has_logs, true ); } /** @@ -80,8 +90,16 @@ public static function maybe_schedule_cleanup() { */ public static function delete_all_action_comments() { global $wpdb; - $wpdb->delete( $wpdb->comments, array( 'comment_type' => ActionScheduler_wpCommentLogger::TYPE, 'comment_agent' => ActionScheduler_wpCommentLogger::AGENT ) ); - delete_option( self::$has_logs_option_key ); + + $wpdb->delete( + $wpdb->comments, + array( + 'comment_type' => ActionScheduler_wpCommentLogger::TYPE, + 'comment_agent' => ActionScheduler_wpCommentLogger::AGENT, + ) + ); + + update_option( self::$has_logs_option_key, 'no', true ); } /** @@ -90,7 +108,7 @@ public static function delete_all_action_comments() { public static function register_admin_notice() { add_action( 'admin_notices', array( __CLASS__, 'print_admin_notice' ) ); } - + /** * Prints details about the orphaned action logs and includes information on where to learn more. */ diff --git a/lib/action-scheduler/classes/ActionScheduler_wcSystemStatus.php b/lib/action-scheduler/classes/ActionScheduler_wcSystemStatus.php index bca63e71..cd6ca940 100644 --- a/lib/action-scheduler/classes/ActionScheduler_wcSystemStatus.php +++ b/lib/action-scheduler/classes/ActionScheduler_wcSystemStatus.php @@ -76,7 +76,6 @@ protected function get_action_status_date( $status, $date_type = 'oldest' ) { $action = $this->store->query_actions( array( - 'claimed' => false, 'status' => $status, 'per_page' => 1, 'order' => $order, diff --git a/lib/action-scheduler/classes/WP_CLI/Action/Cancel_Command.php b/lib/action-scheduler/classes/WP_CLI/Action/Cancel_Command.php new file mode 100644 index 00000000..e9eb9f26 --- /dev/null +++ b/lib/action-scheduler/classes/WP_CLI/Action/Cancel_Command.php @@ -0,0 +1,120 @@ +assoc_args, 'group', '' ); + $callback_args = get_flag_value( $this->assoc_args, 'args', null ); + $all = get_flag_value( $this->assoc_args, 'all', false ); + + if ( ! empty( $this->args[0] ) ) { + $hook = $this->args[0]; + } + + if ( ! empty( $callback_args ) ) { + $callback_args = json_decode( $callback_args, true ); + } + + if ( $all ) { + $this->cancel_all( $hook, $callback_args, $group ); + return; + } + + $this->cancel_single( $hook, $callback_args, $group ); + } + + /** + * Cancel single action. + * + * @param string $hook The hook that the job will trigger. + * @param array $callback_args Args that would have been passed to the job. + * @param string $group The group the job is assigned to. + * @return void + */ + protected function cancel_single( $hook, $callback_args, $group ) { + if ( empty( $hook ) ) { + \WP_CLI::error( __( 'Please specify hook of action to cancel.', 'action-scheduler' ) ); + } + + try { + $result = as_unschedule_action( $hook, $callback_args, $group ); + } catch ( \Exception $e ) { + $this->print_error( $e, false ); + } + + if ( null === $result ) { + $e = new \Exception( __( 'Unable to cancel scheduled action: check the logs.', 'action-scheduler' ) ); + $this->print_error( $e, false ); + } + + $this->print_success( false ); + } + + /** + * Cancel all actions. + * + * @param string $hook The hook that the job will trigger. + * @param array $callback_args Args that would have been passed to the job. + * @param string $group The group the job is assigned to. + * @return void + */ + protected function cancel_all( $hook, $callback_args, $group ) { + if ( empty( $hook ) && empty( $group ) ) { + \WP_CLI::error( __( 'Please specify hook and/or group of actions to cancel.', 'action-scheduler' ) ); + } + + try { + $result = as_unschedule_all_actions( $hook, $callback_args, $group ); + } catch ( \Exception $e ) { + $this->print_error( $e, $multiple ); + } + + /** + * Because as_unschedule_all_actions() does not provide a result, + * neither confirm or deny actions cancelled. + */ + \WP_CLI::success( __( 'Request to cancel scheduled actions completed.', 'action-scheduler' ) ); + } + + /** + * Print a success message. + * + * @return void + */ + protected function print_success() { + \WP_CLI::success( __( 'Scheduled action cancelled.', 'action-scheduler' ) ); + } + + /** + * Convert an exception into a WP CLI error. + * + * @param \Exception $e The error object. + * @param bool $multiple Boolean if multiple actions. + * @throws \WP_CLI\ExitException When an error occurs. + * @return void + */ + protected function print_error( \Exception $e, $multiple ) { + \WP_CLI::error( + sprintf( + /* translators: %1$s: singular or plural %2$s: refers to the exception error message. */ + __( 'There was an error cancelling the %1$s: %2$s', 'action-scheduler' ), + $multiple ? __( 'scheduled actions', 'action-scheduler' ) : __( 'scheduled action', 'action-scheduler' ), + $e->getMessage() + ) + ); + } + +} diff --git a/lib/action-scheduler/classes/WP_CLI/Action/Create_Command.php b/lib/action-scheduler/classes/WP_CLI/Action/Create_Command.php new file mode 100644 index 00000000..fedd417e --- /dev/null +++ b/lib/action-scheduler/classes/WP_CLI/Action/Create_Command.php @@ -0,0 +1,151 @@ +args[0]; + $schedule_start = $this->args[1]; + $callback_args = get_flag_value( $this->assoc_args, 'args', array() ); + $group = get_flag_value( $this->assoc_args, 'group', '' ); + $interval = absint( get_flag_value( $this->assoc_args, 'interval', 0 ) ); + $cron = get_flag_value( $this->assoc_args, 'cron', '' ); + $unique = get_flag_value( $this->assoc_args, 'unique', false ); + $priority = absint( get_flag_value( $this->assoc_args, 'priority', 10 ) ); + + if ( ! empty( $callback_args ) ) { + $callback_args = json_decode( $callback_args, true ); + } + + $function_args = array( + 'start' => $schedule_start, + 'cron' => $cron, + 'interval' => $interval, + 'hook' => $hook, + 'callback_args' => $callback_args, + 'group' => $group, + 'unique' => $unique, + 'priority' => $priority, + ); + + try { + // Generate schedule start if appropriate. + if ( ! in_array( $schedule_start, static::ASYNC_OPTS, true ) ) { + $schedule_start = as_get_datetime_object( $schedule_start ); + $function_args['start'] = $schedule_start->format( 'U' ); + } + } catch ( \Exception $e ) { + \WP_CLI::error( $e->getMessage() ); + } + + // Default to creating single action. + $action_type = 'single'; + $function = 'as_schedule_single_action'; + + if ( ! empty( $interval ) ) { // Creating recurring action. + $action_type = 'recurring'; + $function = 'as_schedule_recurring_action'; + + $function_args = array_filter( + $function_args, + static function( $key ) { + return in_array( $key, array( 'start', 'interval', 'hook', 'callback_args', 'group', 'unique', 'priority' ), true ); + }, + ARRAY_FILTER_USE_KEY + ); + } elseif ( ! empty( $cron ) ) { // Creating cron action. + $action_type = 'cron'; + $function = 'as_schedule_cron_action'; + + $function_args = array_filter( + $function_args, + static function( $key ) { + return in_array( $key, array( 'start', 'cron', 'hook', 'callback_args', 'group', 'unique', 'priority' ), true ); + }, + ARRAY_FILTER_USE_KEY + ); + } elseif ( in_array( $function_args['start'], static::ASYNC_OPTS, true ) ) { // Enqueue async action. + $action_type = 'async'; + $function = 'as_enqueue_async_action'; + + $function_args = array_filter( + $function_args, + static function( $key ) { + return in_array( $key, array( 'hook', 'callback_args', 'group', 'unique', 'priority' ), true ); + }, + ARRAY_FILTER_USE_KEY + ); + } else { // Enqueue single action. + $function_args = array_filter( + $function_args, + static function( $key ) { + return in_array( $key, array( 'start', 'hook', 'callback_args', 'group', 'unique', 'priority' ), true ); + }, + ARRAY_FILTER_USE_KEY + ); + } + + $function_args = array_values( $function_args ); + + try { + $action_id = call_user_func_array( $function, $function_args ); + } catch ( \Exception $e ) { + $this->print_error( $e ); + } + + if ( 0 === $action_id ) { + $e = new \Exception( __( 'Unable to create a scheduled action.', 'action-scheduler' ) ); + $this->print_error( $e ); + } + + $this->print_success( $action_id, $action_type ); + } + + /** + * Print a success message with the action ID. + * + * @param int $action_id Created action ID. + * @param string $action_type Type of action. + * + * @return void + */ + protected function print_success( $action_id, $action_type ) { + \WP_CLI::success( + sprintf( + /* translators: %1$s: type of action, %2$d: ID of the created action */ + __( '%1$s action (%2$d) scheduled.', 'action-scheduler' ), + ucfirst( $action_type ), + $action_id + ) + ); + } + + /** + * Convert an exception into a WP CLI error. + * + * @param \Exception $e The error object. + * @throws \WP_CLI\ExitException When an error occurs. + * @return void + */ + protected function print_error( \Exception $e ) { + \WP_CLI::error( + sprintf( + /* translators: %s refers to the exception error message. */ + __( 'There was an error creating the scheduled action: %s', 'action-scheduler' ), + $e->getMessage() + ) + ); + } + +} diff --git a/lib/action-scheduler/classes/WP_CLI/Action/Delete_Command.php b/lib/action-scheduler/classes/WP_CLI/Action/Delete_Command.php new file mode 100644 index 00000000..a549e0b4 --- /dev/null +++ b/lib/action-scheduler/classes/WP_CLI/Action/Delete_Command.php @@ -0,0 +1,108 @@ + + */ + protected $action_counts = array( + 'deleted' => 0, + 'failed' => 0, + 'total' => 0, + ); + + /** + * Construct. + * + * @param string[] $args Positional arguments. + * @param array $assoc_args Keyed arguments. + */ + public function __construct( array $args, array $assoc_args ) { + parent::__construct( $args, $assoc_args ); + + $this->action_ids = array_map( 'absint', $args ); + $this->action_counts['total'] = count( $this->action_ids ); + + add_action( 'action_scheduler_deleted_action', array( $this, 'on_action_deleted' ) ); + } + + /** + * Execute. + * + * @return void + */ + public function execute() { + $store = \ActionScheduler::store(); + + $progress_bar = \WP_CLI\Utils\make_progress_bar( + sprintf( + /* translators: %d: number of actions to be deleted */ + _n( 'Deleting %d action', 'Deleting %d actions', $this->action_counts['total'], 'action-scheduler' ), + number_format_i18n( $this->action_counts['total'] ) + ), + $this->action_counts['total'] + ); + + foreach ( $this->action_ids as $action_id ) { + try { + $store->delete_action( $action_id ); + } catch ( \Exception $e ) { + $this->action_counts['failed']++; + \WP_CLI::warning( $e->getMessage() ); + } + + $progress_bar->tick(); + } + + $progress_bar->finish(); + + /* translators: %1$d: number of actions deleted */ + $format = _n( 'Deleted %1$d action', 'Deleted %1$d actions', $this->action_counts['deleted'], 'action-scheduler' ) . ', '; + /* translators: %2$d: number of actions deletions failed */ + $format .= _n( '%2$d failure.', '%2$d failures.', $this->action_counts['failed'], 'action-scheduler' ); + + \WP_CLI::success( + sprintf( + $format, + number_format_i18n( $this->action_counts['deleted'] ), + number_format_i18n( $this->action_counts['failed'] ) + ) + ); + } + + /** + * Action: action_scheduler_deleted_action + * + * @param int $action_id Action ID. + * @return void + */ + public function on_action_deleted( $action_id ) { + if ( 'action_scheduler_deleted_action' !== current_action() ) { + return; + } + + $action_id = absint( $action_id ); + + if ( ! in_array( $action_id, $this->action_ids, true ) ) { + return; + } + + $this->action_counts['deleted']++; + \WP_CLI::debug( sprintf( 'Action %d was deleted.', $action_id ) ); + } + +} diff --git a/lib/action-scheduler/classes/WP_CLI/Action/Generate_Command.php b/lib/action-scheduler/classes/WP_CLI/Action/Generate_Command.php new file mode 100644 index 00000000..6e6e8c77 --- /dev/null +++ b/lib/action-scheduler/classes/WP_CLI/Action/Generate_Command.php @@ -0,0 +1,121 @@ +args[0]; + $schedule_start = $this->args[1]; + $callback_args = get_flag_value( $this->assoc_args, 'args', array() ); + $group = get_flag_value( $this->assoc_args, 'group', '' ); + $interval = (int) get_flag_value( $this->assoc_args, 'interval', 0 ); // avoid absint() to support negative intervals + $count = absint( get_flag_value( $this->assoc_args, 'count', 1 ) ); + + if ( ! empty( $callback_args ) ) { + $callback_args = json_decode( $callback_args, true ); + } + + $schedule_start = as_get_datetime_object( $schedule_start ); + + $function_args = array( + 'start' => absint( $schedule_start->format( 'U' ) ), + 'interval' => $interval, + 'count' => $count, + 'hook' => $hook, + 'callback_args' => $callback_args, + 'group' => $group, + ); + + $function_args = array_values( $function_args ); + + try { + $actions_added = $this->generate( ...$function_args ); + } catch ( \Exception $e ) { + $this->print_error( $e ); + } + + $num_actions_added = count( (array) $actions_added ); + + $this->print_success( $num_actions_added, 'single' ); + } + + /** + * Schedule multiple single actions. + * + * @param int $schedule_start Starting timestamp of first action. + * @param int $interval How long to wait between runs. + * @param int $count Limit number of actions to schedule. + * @param string $hook The hook to trigger. + * @param array $args Arguments to pass when the hook triggers. + * @param string $group The group to assign this job to. + * @return int[] IDs of actions added. + */ + protected function generate( $schedule_start, $interval, $count, $hook, array $args = array(), $group = '' ) { + $actions_added = array(); + + $progress_bar = \WP_CLI\Utils\make_progress_bar( + sprintf( + /* translators: %d is number of actions to create */ + _n( 'Creating %d action', 'Creating %d actions', $count, 'action-scheduler' ), + number_format_i18n( $count ) + ), + $count + ); + + for ( $i = 0; $i < $count; $i++ ) { + $actions_added[] = as_schedule_single_action( $schedule_start + ( $i * $interval ), $hook, $args, $group ); + $progress_bar->tick(); + } + + $progress_bar->finish(); + + return $actions_added; + } + + /** + * Print a success message with the action ID. + * + * @param int $actions_added Number of actions generated. + * @param string $action_type Type of actions scheduled. + * @return void + */ + protected function print_success( $actions_added, $action_type ) { + \WP_CLI::success( + sprintf( + /* translators: %1$d refers to the total number of tasks added, %2$s is the action type */ + _n( '%1$d %2$s action scheduled.', '%1$d %2$s actions scheduled.', $actions_added, 'action-scheduler' ), + number_format_i18n( $actions_added ), + $action_type + ) + ); + } + + /** + * Convert an exception into a WP CLI error. + * + * @param \Exception $e The error object. + * @throws \WP_CLI\ExitException When an error occurs. + * @return void + */ + protected function print_error( \Exception $e ) { + \WP_CLI::error( + sprintf( + /* translators: %s refers to the exception error message. */ + __( 'There was an error creating the scheduled action: %s', 'action-scheduler' ), + $e->getMessage() + ) + ); + } + +} diff --git a/lib/action-scheduler/classes/WP_CLI/Action/Get_Command.php b/lib/action-scheduler/classes/WP_CLI/Action/Get_Command.php new file mode 100644 index 00000000..b0bbdee1 --- /dev/null +++ b/lib/action-scheduler/classes/WP_CLI/Action/Get_Command.php @@ -0,0 +1,75 @@ +args[0]; + $store = \ActionScheduler::store(); + $logger = \ActionScheduler::logger(); + $action = $store->fetch_action( $action_id ); + + if ( is_a( $action, ActionScheduler_NullAction::class ) ) { + /* translators: %d is action ID. */ + \WP_CLI::error( sprintf( esc_html__( 'Unable to retrieve action %d.', 'action-scheduler' ), $action_id ) ); + } + + $only_logs = ! empty( $this->assoc_args['field'] ) && 'log_entries' === $this->assoc_args['field']; + $only_logs = $only_logs || ( ! empty( $this->assoc_args['fields'] ) && 'log_entries' === $this->assoc_args['fields'] ); + $log_entries = array(); + + foreach ( $logger->get_logs( $action_id ) as $log_entry ) { + $log_entries[] = array( + 'date' => $log_entry->get_date()->format( static::DATE_FORMAT ), + 'message' => $log_entry->get_message(), + ); + } + + if ( $only_logs ) { + $args = array( + 'format' => \WP_CLI\Utils\get_flag_value( $this->assoc_args, 'format', 'table' ), + ); + + $formatter = new \WP_CLI\Formatter( $args, array( 'date', 'message' ) ); + $formatter->display_items( $log_entries ); + + return; + } + + try { + $status = $store->get_status( $action_id ); + } catch ( \Exception $e ) { + \WP_CLI::error( $e->getMessage() ); + } + + $action_arr = array( + 'id' => $this->args[0], + 'hook' => $action->get_hook(), + 'status' => $status, + 'args' => $action->get_args(), + 'group' => $action->get_group(), + 'recurring' => $action->get_schedule()->is_recurring() ? 'yes' : 'no', + 'scheduled_date' => $this->get_schedule_display_string( $action->get_schedule() ), + 'log_entries' => $log_entries, + ); + + $fields = array_keys( $action_arr ); + + if ( ! empty( $this->assoc_args['fields'] ) ) { + $fields = explode( ',', $this->assoc_args['fields'] ); + } + + $formatter = new \WP_CLI\Formatter( $this->assoc_args, $fields ); + $formatter->display_item( $action_arr ); + } + +} diff --git a/lib/action-scheduler/classes/WP_CLI/Action/List_Command.php b/lib/action-scheduler/classes/WP_CLI/Action/List_Command.php new file mode 100644 index 00000000..4a3e0835 --- /dev/null +++ b/lib/action-scheduler/classes/WP_CLI/Action/List_Command.php @@ -0,0 +1,133 @@ +process_csv_arguments_to_arrays(); + + if ( ! empty( $this->assoc_args['fields'] ) ) { + $fields = $this->assoc_args['fields']; + } + + $formatter = new \WP_CLI\Formatter( $this->assoc_args, $fields ); + $query_args = $this->assoc_args; + + /** + * The `claimed` parameter expects a boolean or integer: + * check for string 'false', and set explicitly to `false` boolean. + */ + if ( array_key_exists( 'claimed', $query_args ) && 'false' === strtolower( $query_args['claimed'] ) ) { + $query_args['claimed'] = false; + } + + $return_format = 'OBJECT'; + + if ( in_array( $formatter->format, array( 'ids', 'count' ), true ) ) { + $return_format = '\'ids\''; + } + + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export + $params = var_export( $query_args, true ); + + if ( empty( $query_args ) ) { + $params = 'array()'; + } + + \WP_CLI::debug( + sprintf( + 'as_get_scheduled_actions( %s, %s )', + $params, + $return_format + ) + ); + + if ( ! empty( $query_args['args'] ) ) { + $query_args['args'] = json_decode( $query_args['args'], true ); + } + + switch ( $formatter->format ) { + + case 'ids': + $actions = as_get_scheduled_actions( $query_args, 'ids' ); + echo implode( ' ', $actions ); + break; + + case 'count': + $actions = as_get_scheduled_actions( $query_args, 'ids' ); + $formatter->display_items( $actions ); + break; + + default: + $actions = as_get_scheduled_actions( $query_args, OBJECT ); + + $actions_arr = array(); + + foreach ( $actions as $action_id => $action ) { + $action_arr = array( + 'id' => $action_id, + 'hook' => $action->get_hook(), + 'status' => $store->get_status( $action_id ), + 'args' => $action->get_args(), + 'group' => $action->get_group(), + 'recurring' => $action->get_schedule()->is_recurring() ? 'yes' : 'no', + 'scheduled_date' => $this->get_schedule_display_string( $action->get_schedule() ), + 'log_entries' => array(), + ); + + foreach ( $logger->get_logs( $action_id ) as $log_entry ) { + $action_arr['log_entries'][] = array( + 'date' => $log_entry->get_date()->format( static::DATE_FORMAT ), + 'message' => $log_entry->get_message(), + ); + } + + $actions_arr[] = $action_arr; + } + + $formatter->display_items( $actions_arr ); + break; + + } + } + +} diff --git a/lib/action-scheduler/classes/WP_CLI/Action/Next_Command.php b/lib/action-scheduler/classes/WP_CLI/Action/Next_Command.php new file mode 100644 index 00000000..b7174459 --- /dev/null +++ b/lib/action-scheduler/classes/WP_CLI/Action/Next_Command.php @@ -0,0 +1,71 @@ +args[0]; + $group = get_flag_value( $this->assoc_args, 'group', '' ); + $callback_args = get_flag_value( $this->assoc_args, 'args', null ); + $raw = (bool) get_flag_value( $this->assoc_args, 'raw', false ); + + if ( ! empty( $callback_args ) ) { + $callback_args = json_decode( $callback_args, true ); + } + + if ( $raw ) { + \WP_CLI::line( as_next_scheduled_action( $hook, $callback_args, $group ) ); + return; + } + + $params = array( + 'hook' => $hook, + 'orderby' => 'date', + 'order' => 'ASC', + 'group' => $group, + ); + + if ( is_array( $callback_args ) ) { + $params['args'] = $callback_args; + } + + $params['status'] = \ActionScheduler_Store::STATUS_RUNNING; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export + \WP_CLI::debug( 'ActionScheduler()::store()->query_action( ' . var_export( $params, true ) . ' )' ); + + $store = \ActionScheduler::store(); + $action_id = $store->query_action( $params ); + + if ( $action_id ) { + echo $action_id; + return; + } + + $params['status'] = \ActionScheduler_Store::STATUS_PENDING; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export + \WP_CLI::debug( 'ActionScheduler()::store()->query_action( ' . var_export( $params, true ) . ' )' ); + + $action_id = $store->query_action( $params ); + + if ( $action_id ) { + echo $action_id; + return; + } + + \WP_CLI::warning( 'No matching next action.' ); + } + +} diff --git a/lib/action-scheduler/classes/WP_CLI/Action/Run_Command.php b/lib/action-scheduler/classes/WP_CLI/Action/Run_Command.php new file mode 100644 index 00000000..efff37dd --- /dev/null +++ b/lib/action-scheduler/classes/WP_CLI/Action/Run_Command.php @@ -0,0 +1,194 @@ + + */ + protected $action_counts = array( + 'executed' => 0, + 'failed' => 0, + 'ignored' => 0, + 'invalid' => 0, + 'total' => 0, + ); + + /** + * Construct. + * + * @param string[] $args Positional arguments. + * @param array $assoc_args Keyed arguments. + */ + public function __construct( array $args, array $assoc_args ) { + parent::__construct( $args, $assoc_args ); + + $this->action_ids = array_map( 'absint', $args ); + $this->action_counts['total'] = count( $this->action_ids ); + + add_action( 'action_scheduler_execution_ignored', array( $this, 'on_action_ignored' ) ); + add_action( 'action_scheduler_after_execute', array( $this, 'on_action_executed' ) ); + add_action( 'action_scheduler_failed_execution', array( $this, 'on_action_failed' ), 10, 2 ); + add_action( 'action_scheduler_failed_validation', array( $this, 'on_action_invalid' ), 10, 2 ); + } + + /** + * Execute. + * + * @return void + */ + public function execute() { + $runner = \ActionScheduler::runner(); + + $progress_bar = \WP_CLI\Utils\make_progress_bar( + sprintf( + /* translators: %d: number of actions */ + _n( 'Executing %d action', 'Executing %d actions', $this->action_counts['total'], 'action-scheduler' ), + number_format_i18n( $this->action_counts['total'] ) + ), + $this->action_counts['total'] + ); + + foreach ( $this->action_ids as $action_id ) { + $runner->process_action( $action_id, 'Action Scheduler CLI' ); + $progress_bar->tick(); + } + + $progress_bar->finish(); + + foreach ( array( + 'ignored', + 'invalid', + 'failed', + ) as $type ) { + $count = $this->action_counts[ $type ]; + + if ( empty( $count ) ) { + continue; + } + + /* + * translators: + * %1$d: count of actions evaluated. + * %2$s: type of action evaluated. + */ + $format = _n( '%1$d action %2$s.', '%1$d actions %2$s.', $count, 'action-scheduler' ); + + \WP_CLI::warning( + sprintf( + $format, + number_format_i18n( $count ), + $type + ) + ); + } + + \WP_CLI::success( + sprintf( + /* translators: %d: number of executed actions */ + _n( 'Executed %d action.', 'Executed %d actions.', $this->action_counts['executed'], 'action-scheduler' ), + number_format_i18n( $this->action_counts['executed'] ) + ) + ); + } + + /** + * Action: action_scheduler_execution_ignored + * + * @param int $action_id Action ID. + * @return void + */ + public function on_action_ignored( $action_id ) { + if ( 'action_scheduler_execution_ignored' !== current_action() ) { + return; + } + + $action_id = absint( $action_id ); + + if ( ! in_array( $action_id, $this->action_ids, true ) ) { + return; + } + + $this->action_counts['ignored']++; + \WP_CLI::debug( sprintf( 'Action %d was ignored.', $action_id ) ); + } + + /** + * Action: action_scheduler_after_execute + * + * @param int $action_id Action ID. + * @return void + */ + public function on_action_executed( $action_id ) { + if ( 'action_scheduler_after_execute' !== current_action() ) { + return; + } + + $action_id = absint( $action_id ); + + if ( ! in_array( $action_id, $this->action_ids, true ) ) { + return; + } + + $this->action_counts['executed']++; + \WP_CLI::debug( sprintf( 'Action %d was executed.', $action_id ) ); + } + + /** + * Action: action_scheduler_failed_execution + * + * @param int $action_id Action ID. + * @param \Exception $e Exception. + * @return void + */ + public function on_action_failed( $action_id, \Exception $e ) { + if ( 'action_scheduler_failed_execution' !== current_action() ) { + return; + } + + $action_id = absint( $action_id ); + + if ( ! in_array( $action_id, $this->action_ids, true ) ) { + return; + } + + $this->action_counts['failed']++; + \WP_CLI::debug( sprintf( 'Action %d failed execution: %s', $action_id, $e->getMessage() ) ); + } + + /** + * Action: action_scheduler_failed_validation + * + * @param int $action_id Action ID. + * @param \Exception $e Exception. + * @return void + */ + public function on_action_invalid( $action_id, \Exception $e ) { + if ( 'action_scheduler_failed_validation' !== current_action() ) { + return; + } + + $action_id = absint( $action_id ); + + if ( ! in_array( $action_id, $this->action_ids, true ) ) { + return; + } + + $this->action_counts['invalid']++; + \WP_CLI::debug( sprintf( 'Action %d failed validation: %s', $action_id, $e->getMessage() ) ); + } + +} diff --git a/lib/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Clean_Command.php b/lib/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Clean_Command.php index ff6e57aa..b0ac4e30 100644 --- a/lib/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Clean_Command.php +++ b/lib/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Clean_Command.php @@ -41,7 +41,7 @@ public function clean( $args, $assoc_args ) { $batches_completed = 0; $actions_deleted = 0; - $unlimited = $batches === 0; + $unlimited = 0 === $batches; try { $lifespan = as_get_datetime_object( $before ); } catch ( Exception $e ) { @@ -58,7 +58,7 @@ public function clean( $args, $assoc_args ) { sleep( $sleep ); } - $deleted = count( $cleaner->clean_actions( $status, $lifespan, null,'CLI' ) ); + $deleted = count( $cleaner->clean_actions( $status, $lifespan, null, 'CLI' ) ); if ( $deleted <= 0 ) { break; } @@ -79,7 +79,7 @@ public function clean( $args, $assoc_args ) { /** * Print WP CLI message about how many batches of actions were processed. * - * @param int $batches_processed + * @param int $batches_processed Number of batches processed. */ protected function print_total_batches( int $batches_processed ) { WP_CLI::log( @@ -95,8 +95,6 @@ protected function print_total_batches( int $batches_processed ) { * Convert an exception into a WP CLI error. * * @param Exception $e The error object. - * - * @throws \WP_CLI\ExitException */ protected function print_error( Exception $e ) { WP_CLI::error( @@ -111,7 +109,7 @@ protected function print_error( Exception $e ) { /** * Print a success message with the number of completed actions. * - * @param int $actions_deleted + * @param int $actions_deleted Number of deleted actions. */ protected function print_success( int $actions_deleted ) { WP_CLI::success( diff --git a/lib/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php b/lib/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php index 4681daa4..08cb0cd5 100644 --- a/lib/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php +++ b/lib/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php @@ -9,25 +9,37 @@ */ class ActionScheduler_WPCLI_QueueRunner extends ActionScheduler_Abstract_QueueRunner { - /** @var array */ + /** + * Claimed actions. + * + * @var array + */ protected $actions; - /** @var ActionScheduler_ActionClaim */ + /** + * ActionScheduler_ActionClaim instance. + * + * @var ActionScheduler_ActionClaim + */ protected $claim; - /** @var \cli\progress\Bar */ + /** + * Progress bar instance. + * + * @var \cli\progress\Bar + */ protected $progress_bar; /** * ActionScheduler_WPCLI_QueueRunner constructor. * - * @param ActionScheduler_Store $store - * @param ActionScheduler_FatalErrorMonitor $monitor - * @param ActionScheduler_QueueCleaner $cleaner + * @param ActionScheduler_Store|null $store Store object. + * @param ActionScheduler_FatalErrorMonitor|null $monitor Monitor object. + * @param ActionScheduler_QueueCleaner|null $cleaner Cleaner object. * - * @throws Exception When this is not run within WP CLI + * @throws Exception When this is not run within WP CLI. */ - public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null ) { + public function __construct( ?ActionScheduler_Store $store = null, ?ActionScheduler_FatalErrorMonitor $monitor = null, ?ActionScheduler_QueueCleaner $cleaner = null ) { if ( ! ( defined( 'WP_CLI' ) && WP_CLI ) ) { /* translators: %s php class name */ throw new Exception( sprintf( __( 'The %s class can only be run within WP CLI.', 'action-scheduler' ), __CLASS__ ) ); @@ -39,8 +51,6 @@ public function __construct( ActionScheduler_Store $store = null, ActionSchedule /** * Set up the Queue before processing. * - * @author Jeremy Pry - * * @param int $batch_size The batch size to process. * @param array $hooks The hooks being used to filter the actions claimed in this batch. * @param string $group The group of actions to claim with this batch. @@ -72,8 +82,6 @@ public function setup( $batch_size, $hooks = array(), $group = '', $force = fals /** * Add our hooks to the appropriate actions. - * - * @author Jeremy Pry */ protected function add_hooks() { add_action( 'action_scheduler_before_execute', array( $this, 'before_execute' ) ); @@ -83,8 +91,6 @@ protected function add_hooks() { /** * Set up the WP CLI progress bar. - * - * @author Jeremy Pry */ protected function setup_progress_bar() { $count = count( $this->actions ); @@ -98,8 +104,6 @@ protected function setup_progress_bar() { /** * Process actions in the queue. * - * @author Jeremy Pry - * * @param string $context Optional runner context. Default 'WP CLI'. * * @return int The number of actions processed. @@ -109,7 +113,7 @@ public function run( $context = 'WP CLI' ) { $this->setup_progress_bar(); foreach ( $this->actions as $action_id ) { // Error if we lost the claim. - if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $this->claim->get_id() ) ) ) { + if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $this->claim->get_id() ), true ) ) { WP_CLI::warning( __( 'The claim has been lost. Aborting current batch.', 'action-scheduler' ) ); break; } @@ -129,9 +133,7 @@ public function run( $context = 'WP CLI' ) { /** * Handle WP CLI message when the action is starting. * - * @author Jeremy Pry - * - * @param $action_id + * @param int $action_id Action ID. */ public function before_execute( $action_id ) { /* translators: %s refers to the action ID */ @@ -141,13 +143,11 @@ public function before_execute( $action_id ) { /** * Handle WP CLI message when the action has completed. * - * @author Jeremy Pry - * - * @param int $action_id + * @param int $action_id ActionID. * @param null|ActionScheduler_Action $action The instance of the action. Default to null for backward compatibility. */ public function after_execute( $action_id, $action = null ) { - // backward compatibility + // backward compatibility. if ( null === $action ) { $action = $this->store->fetch_action( $action_id ); } @@ -158,10 +158,8 @@ public function after_execute( $action_id, $action = null ) { /** * Handle WP CLI message when the action has failed. * - * @author Jeremy Pry - * - * @param int $action_id - * @param Exception $exception + * @param int $action_id Action ID. + * @param Exception $exception Exception. * @throws \WP_CLI\ExitException With failure message. */ public function action_failed( $action_id, $exception ) { @@ -175,7 +173,7 @@ public function action_failed( $action_id, $exception ) { /** * Sleep and help avoid hitting memory limit * - * @param int $sleep_time Amount of seconds to sleep + * @param int $sleep_time Amount of seconds to sleep. * @deprecated 3.0.0 */ protected function stop_the_insanity( $sleep_time = 0 ) { diff --git a/lib/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Scheduler_command.php b/lib/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Scheduler_command.php index 2c68a386..d252680f 100644 --- a/lib/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Scheduler_command.php +++ b/lib/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Scheduler_command.php @@ -91,12 +91,12 @@ public function run( $args, $assoc_args ) { $batches_completed = 0; $actions_completed = 0; - $unlimited = $batches === 0; - if ( is_callable( [ ActionScheduler::store(), 'set_claim_filter' ] ) ) { + $unlimited = 0 === $batches; + if ( is_callable( array( ActionScheduler::store(), 'set_claim_filter' ) ) ) { $exclude_groups = $this->parse_comma_separated_string( $exclude_groups ); if ( ! empty( $exclude_groups ) ) { - ActionScheduler::store()->set_claim_filter('exclude-groups', $exclude_groups ); + ActionScheduler::store()->set_claim_filter( 'exclude-groups', $exclude_groups ); } } @@ -104,7 +104,7 @@ public function run( $args, $assoc_args ) { // Custom queue cleaner instance. $cleaner = new ActionScheduler_QueueCleaner( null, $clean ); - // Get the queue runner instance + // Get the queue runner instance. $runner = new ActionScheduler_WPCLI_QueueRunner( null, null, $cleaner ); // Determine how many tasks will be run in the first batch. @@ -141,9 +141,7 @@ private function parse_comma_separated_string( $string ): array { /** * Print WP CLI message about how many actions are about to be processed. * - * @author Jeremy Pry - * - * @param int $total + * @param int $total Number of actions found. */ protected function print_total_actions( $total ) { WP_CLI::log( @@ -158,9 +156,7 @@ protected function print_total_actions( $total ) { /** * Print WP CLI message about how many batches of actions were processed. * - * @author Jeremy Pry - * - * @param int $batches_completed + * @param int $batches_completed Number of completed batches. */ protected function print_total_batches( $batches_completed ) { WP_CLI::log( @@ -175,11 +171,9 @@ protected function print_total_batches( $batches_completed ) { /** * Convert an exception into a WP CLI error. * - * @author Jeremy Pry - * * @param Exception $e The error object. * - * @throws \WP_CLI\ExitException + * @throws \WP_CLI\ExitException Under some conditions WP CLI may throw an exception. */ protected function print_error( Exception $e ) { WP_CLI::error( @@ -194,9 +188,7 @@ protected function print_error( Exception $e ) { /** * Print a success message with the number of completed actions. * - * @author Jeremy Pry - * - * @param int $actions_completed + * @param int $actions_completed Number of completed actions. */ protected function print_success( $actions_completed ) { WP_CLI::success( diff --git a/lib/action-scheduler/classes/WP_CLI/Action_Command.php b/lib/action-scheduler/classes/WP_CLI/Action_Command.php new file mode 100644 index 00000000..b32eea31 --- /dev/null +++ b/lib/action-scheduler/classes/WP_CLI/Action_Command.php @@ -0,0 +1,353 @@ +] + * : Name of the action hook. + * + * [--group=] + * : The group the job is assigned to. + * + * [--args=] + * : JSON object of arguments assigned to the job. + * --- + * default: [] + * --- + * + * [--all] + * : Cancel all occurrences of a scheduled action. + * + * @param array $args Positional arguments. + * @param array $assoc_args Keyed arguments. + * @return void + */ + public function cancel( array $args, array $assoc_args ) { + require_once 'Action/Cancel_Command.php'; + $command = new Action\Cancel_Command( $args, $assoc_args ); + $command->execute(); + } + + /** + * Creates a new scheduled action. + * + * ## OPTIONS + * + * + * : Name of the action hook. + * + * + * : A unix timestamp representing the date you want the action to start. Also 'async' or 'now' to enqueue an async action. + * + * [--args=] + * : JSON object of arguments to pass to callbacks when the hook triggers. + * --- + * default: [] + * --- + * + * [--cron=] + * : A cron-like schedule string (https://crontab.guru/). + * --- + * default: '' + * --- + * + * [--group=] + * : The group to assign this job to. + * --- + * default: '' + * --- + * + * [--interval=] + * : Number of seconds to wait between runs. + * --- + * default: 0 + * --- + * + * ## EXAMPLES + * + * wp action-scheduler action create hook_async async + * wp action-scheduler action create hook_single 1627147598 + * wp action-scheduler action create hook_recurring 1627148188 --interval=5 + * wp action-scheduler action create hook_cron 1627147655 --cron='5 4 * * *' + * + * @param array $args Positional arguments. + * @param array $assoc_args Keyed arguments. + * @return void + */ + public function create( array $args, array $assoc_args ) { + require_once 'Action/Create_Command.php'; + $command = new Action\Create_Command( $args, $assoc_args ); + $command->execute(); + } + + /** + * Delete existing scheduled action(s). + * + * ## OPTIONS + * + * ... + * : One or more IDs of actions to delete. + * --- + * default: 0 + * --- + * + * ## EXAMPLES + * + * # Delete the action with id 100 + * $ wp action-scheduler action delete 100 + * + * # Delete the actions with ids 100 and 200 + * $ wp action-scheduler action delete 100 200 + * + * # Delete the first five pending actions in 'action-scheduler' group + * $ wp action-scheduler action delete $( wp action-scheduler action list --status=pending --group=action-scheduler --format=ids ) + * + * @param array $args Positional arguments. + * @param array $assoc_args Keyed arguments. + * @return void + */ + public function delete( array $args, array $assoc_args ) { + require_once 'Action/Delete_Command.php'; + $command = new Action\Delete_Command( $args, $assoc_args ); + $command->execute(); + } + + /** + * Generates some scheduled actions. + * + * ## OPTIONS + * + * + * : Name of the action hook. + * + * + * : The Unix timestamp representing the date you want the action to start. + * + * [--count=] + * : Number of actions to create. + * --- + * default: 1 + * --- + * + * [--interval=] + * : Number of seconds to wait between runs. + * --- + * default: 0 + * --- + * + * [--args=] + * : JSON object of arguments to pass to callbacks when the hook triggers. + * --- + * default: [] + * --- + * + * [--group=] + * : The group to assign this job to. + * --- + * default: '' + * --- + * + * ## EXAMPLES + * + * wp action-scheduler action generate test_multiple 1627147598 --count=5 --interval=5 + * + * @param array $args Positional arguments. + * @param array $assoc_args Keyed arguments. + * @return void + */ + public function generate( array $args, array $assoc_args ) { + require_once 'Action/Generate_Command.php'; + $command = new Action\Generate_Command( $args, $assoc_args ); + $command->execute(); + } + + /** + * Get details about a scheduled action. + * + * ## OPTIONS + * + * + * : The ID of the action to get. + * --- + * default: 0 + * --- + * + * [--field=] + * : Instead of returning the whole action, returns the value of a single field. + * + * [--fields=] + * : Limit the output to specific fields (comma-separated). Defaults to all fields. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: table + * options: + * - table + * - csv + * - json + * - yaml + * --- + * + * @param array $args Positional arguments. + * @param array $assoc_args Keyed arguments. + * @return void + */ + public function get( array $args, array $assoc_args ) { + require_once 'Action/Get_Command.php'; + $command = new Action\Get_Command( $args, $assoc_args ); + $command->execute(); + } + + /** + * Get a list of scheduled actions. + * + * Display actions based on all arguments supported by + * [as_get_scheduled_actions()](https://actionscheduler.org/api/#function-reference--as_get_scheduled_actions). + * + * ## OPTIONS + * + * [--=] + * : One or more arguments to pass to as_get_scheduled_actions(). + * + * [--field=] + * : Prints the value of a single property for each action. + * + * [--fields=] + * : Limit the output to specific object properties. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: table + * options: + * - table + * - csv + * - ids + * - json + * - count + * - yaml + * --- + * + * ## AVAILABLE FIELDS + * + * These fields will be displayed by default for each action: + * + * * id + * * hook + * * status + * * group + * * recurring + * * scheduled_date + * + * These fields are optionally available: + * + * * args + * * log_entries + * + * @param array $args Positional arguments. + * @param array $assoc_args Keyed arguments. + * @return void + * + * @subcommand list + */ + public function subcommand_list( array $args, array $assoc_args ) { + require_once 'Action/List_Command.php'; + $command = new Action\List_Command( $args, $assoc_args ); + $command->execute(); + } + + /** + * Get logs for a scheduled action. + * + * ## OPTIONS + * + * + * : The ID of the action to get. + * --- + * default: 0 + * --- + * + * @param array $args Positional arguments. + * @return void + */ + public function logs( array $args ) { + $command = sprintf( 'action-scheduler action get %d --field=log_entries', $args[0] ); + WP_CLI::runcommand( $command ); + } + + /** + * Get the ID or timestamp of the next scheduled action. + * + * ## OPTIONS + * + * + * : The hook of the next scheduled action. + * + * [--args=] + * : JSON object of arguments to search for next scheduled action. + * --- + * default: [] + * --- + * + * [--group=] + * : The group to which the next scheduled action is assigned. + * --- + * default: '' + * --- + * + * [--raw] + * : Display the raw output of as_next_scheduled_action() (timestamp or boolean). + * + * @param array $args Positional arguments. + * @param array $assoc_args Keyed arguments. + * @return void + */ + public function next( array $args, array $assoc_args ) { + require_once 'Action/Next_Command.php'; + $command = new Action\Next_Command( $args, $assoc_args ); + $command->execute(); + } + + /** + * Run existing scheduled action(s). + * + * ## OPTIONS + * + * ... + * : One or more IDs of actions to run. + * --- + * default: 0 + * --- + * + * ## EXAMPLES + * + * # Run the action with id 100 + * $ wp action-scheduler action run 100 + * + * # Run the actions with ids 100 and 200 + * $ wp action-scheduler action run 100 200 + * + * # Run the first five pending actions in 'action-scheduler' group + * $ wp action-scheduler action run $( wp action-scheduler action list --status=pending --group=action-scheduler --format=ids ) + * + * @param array $args Positional arguments. + * @param array $assoc_args Keyed arguments. + * @return void + */ + public function run( array $args, array $assoc_args ) { + require_once 'Action/Run_Command.php'; + $command = new Action\Run_Command( $args, $assoc_args ); + $command->execute(); + } + +} diff --git a/lib/action-scheduler/classes/WP_CLI/Migration_Command.php b/lib/action-scheduler/classes/WP_CLI/Migration_Command.php index 066697e4..86f750fe 100644 --- a/lib/action-scheduler/classes/WP_CLI/Migration_Command.php +++ b/lib/action-scheduler/classes/WP_CLI/Migration_Command.php @@ -21,7 +21,11 @@ */ class Migration_Command extends WP_CLI_Command { - /** @var int */ + /** + * Number of actions migrated. + * + * @var int + */ private $total_processed = 0; /** @@ -32,38 +36,42 @@ public function register() { return; } - WP_CLI::add_command( 'action-scheduler migrate', [ $this, 'migrate' ], [ - 'shortdesc' => 'Migrates actions to the DB tables store', - 'synopsis' => [ - [ - 'type' => 'assoc', - 'name' => 'batch-size', - 'optional' => true, - 'default' => 100, - 'description' => 'The number of actions to process in each batch', - ], - [ - 'type' => 'assoc', - 'name' => 'free-memory-on', - 'optional' => true, - 'default' => 50, - 'description' => 'The number of actions to process between freeing memory. 0 disables freeing memory', - ], - [ - 'type' => 'assoc', - 'name' => 'pause', - 'optional' => true, - 'default' => 0, - 'description' => 'The number of seconds to pause when freeing memory', - ], - [ - 'type' => 'flag', - 'name' => 'dry-run', - 'optional' => true, - 'description' => 'Reports on the actions that would have been migrated, but does not change any data', - ], - ], - ] ); + WP_CLI::add_command( + 'action-scheduler migrate', + array( $this, 'migrate' ), + array( + 'shortdesc' => 'Migrates actions to the DB tables store', + 'synopsis' => array( + array( + 'type' => 'assoc', + 'name' => 'batch-size', + 'optional' => true, + 'default' => 100, + 'description' => 'The number of actions to process in each batch', + ), + array( + 'type' => 'assoc', + 'name' => 'free-memory-on', + 'optional' => true, + 'default' => 50, + 'description' => 'The number of actions to process between freeing memory. 0 disables freeing memory', + ), + array( + 'type' => 'assoc', + 'name' => 'pause', + 'optional' => true, + 'default' => 0, + 'description' => 'The number of seconds to pause when freeing memory', + ), + array( + 'type' => 'flag', + 'name' => 'dry-run', + 'optional' => true, + 'description' => 'Reports on the actions that would have been migrated, but does not change any data', + ), + ), + ) + ); } /** @@ -81,19 +89,19 @@ public function migrate( $positional_args, $assoc_args ) { $runner = new Runner( $config ); $runner->init_destination(); - $batch_size = isset( $assoc_args[ 'batch-size' ] ) ? (int) $assoc_args[ 'batch-size' ] : 100; - $free_on = isset( $assoc_args[ 'free-memory-on' ] ) ? (int) $assoc_args[ 'free-memory-on' ] : 50; - $sleep = isset( $assoc_args[ 'pause' ] ) ? (int) $assoc_args[ 'pause' ] : 0; + $batch_size = isset( $assoc_args['batch-size'] ) ? (int) $assoc_args['batch-size'] : 100; + $free_on = isset( $assoc_args['free-memory-on'] ) ? (int) $assoc_args['free-memory-on'] : 50; + $sleep = isset( $assoc_args['pause'] ) ? (int) $assoc_args['pause'] : 0; \ActionScheduler_DataController::set_free_ticks( $free_on ); \ActionScheduler_DataController::set_sleep_time( $sleep ); do { - $actions_processed = $runner->run( $batch_size ); + $actions_processed = $runner->run( $batch_size ); $this->total_processed += $actions_processed; } while ( $actions_processed > 0 ); if ( ! $config->get_dry_run() ) { - // let the scheduler know that there's nothing left to do + // let the scheduler know that there's nothing left to do. $scheduler = new Scheduler(); $scheduler->mark_complete(); } @@ -109,12 +117,15 @@ public function migrate( $positional_args, $assoc_args ) { * @return ActionScheduler\Migration\Config */ private function get_migration_config( $args ) { - $args = wp_parse_args( $args, [ - 'dry-run' => false, - ] ); + $args = wp_parse_args( + $args, + array( + 'dry-run' => false, + ) + ); $config = Controller::instance()->get_migration_config_object(); - $config->set_dry_run( ! empty( $args[ 'dry-run' ] ) ); + $config->set_dry_run( ! empty( $args['dry-run'] ) ); return $config; } @@ -123,26 +134,57 @@ private function get_migration_config( $args ) { * Hook command line logging into migration actions. */ private function init_logging() { - add_action( 'action_scheduler/migrate_action_dry_run', function ( $action_id ) { - WP_CLI::debug( sprintf( 'Dry-run: migrated action %d', $action_id ) ); - }, 10, 1 ); - add_action( 'action_scheduler/no_action_to_migrate', function ( $action_id ) { - WP_CLI::debug( sprintf( 'No action found to migrate for ID %d', $action_id ) ); - }, 10, 1 ); - add_action( 'action_scheduler/migrate_action_failed', function ( $action_id ) { - WP_CLI::warning( sprintf( 'Failed migrating action with ID %d', $action_id ) ); - }, 10, 1 ); - add_action( 'action_scheduler/migrate_action_incomplete', function ( $source_id, $destination_id ) { - WP_CLI::warning( sprintf( 'Unable to remove source action with ID %d after migrating to new ID %d', $source_id, $destination_id ) ); - }, 10, 2 ); - add_action( 'action_scheduler/migrated_action', function ( $source_id, $destination_id ) { - WP_CLI::debug( sprintf( 'Migrated source action with ID %d to new store with ID %d', $source_id, $destination_id ) ); - }, 10, 2 ); - add_action( 'action_scheduler/migration_batch_starting', function ( $batch ) { - WP_CLI::debug( 'Beginning migration of batch: ' . print_r( $batch, true ) ); - }, 10, 1 ); - add_action( 'action_scheduler/migration_batch_complete', function ( $batch ) { - WP_CLI::log( sprintf( 'Completed migration of %d actions', count( $batch ) ) ); - }, 10, 1 ); + add_action( + 'action_scheduler/migrate_action_dry_run', + function ( $action_id ) { + WP_CLI::debug( sprintf( 'Dry-run: migrated action %d', $action_id ) ); + } + ); + + add_action( + 'action_scheduler/no_action_to_migrate', + function ( $action_id ) { + WP_CLI::debug( sprintf( 'No action found to migrate for ID %d', $action_id ) ); + } + ); + + add_action( + 'action_scheduler/migrate_action_failed', + function ( $action_id ) { + WP_CLI::warning( sprintf( 'Failed migrating action with ID %d', $action_id ) ); + } + ); + + add_action( + 'action_scheduler/migrate_action_incomplete', + function ( $source_id, $destination_id ) { + WP_CLI::warning( sprintf( 'Unable to remove source action with ID %d after migrating to new ID %d', $source_id, $destination_id ) ); + }, + 10, + 2 + ); + + add_action( + 'action_scheduler/migrated_action', + function ( $source_id, $destination_id ) { + WP_CLI::debug( sprintf( 'Migrated source action with ID %d to new store with ID %d', $source_id, $destination_id ) ); + }, + 10, + 2 + ); + + add_action( + 'action_scheduler/migration_batch_starting', + function ( $batch ) { + WP_CLI::debug( 'Beginning migration of batch: ' . print_r( $batch, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r + } + ); + + add_action( + 'action_scheduler/migration_batch_complete', + function ( $batch ) { + WP_CLI::log( sprintf( 'Completed migration of %d actions', count( $batch ) ) ); + } + ); } } diff --git a/lib/action-scheduler/classes/WP_CLI/ProgressBar.php b/lib/action-scheduler/classes/WP_CLI/ProgressBar.php index c86c74e8..fb2ae8f5 100644 --- a/lib/action-scheduler/classes/WP_CLI/ProgressBar.php +++ b/lib/action-scheduler/classes/WP_CLI/ProgressBar.php @@ -17,19 +17,39 @@ */ class ProgressBar { - /** @var integer */ + /** + * Current number of ticks. + * + * @var integer + */ protected $total_ticks; - /** @var integer */ + /** + * Total number of ticks. + * + * @var integer + */ protected $count; - /** @var integer */ + /** + * Progress bar update interval. + * + * @var integer + */ protected $interval; - /** @var string */ + /** + * Progress bar message. + * + * @var string + */ protected $message; - /** @var \cli\progress\Bar */ + /** + * Instance. + * + * @var \cli\progress\Bar + */ protected $progress_bar; /** @@ -38,8 +58,8 @@ class ProgressBar { * @param string $message Text to display before the progress bar. * @param integer $count Total number of ticks to be performed. * @param integer $interval Optional. The interval in milliseconds between updates. Default 100. - * - * @throws Exception When this is not run within WP CLI + * + * @throws \Exception When this is not run within WP CLI. */ public function __construct( $message, $count, $interval = 100 ) { if ( ! ( defined( 'WP_CLI' ) && WP_CLI ) ) { @@ -64,7 +84,7 @@ public function tick() { $this->progress_bar->tick(); $this->total_ticks++; - do_action( 'action_scheduler/progress_tick', $this->total_ticks ); + do_action( 'action_scheduler/progress_tick', $this->total_ticks ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores } /** diff --git a/lib/action-scheduler/classes/WP_CLI/System_Command.php b/lib/action-scheduler/classes/WP_CLI/System_Command.php new file mode 100644 index 00000000..c7c3adaf --- /dev/null +++ b/lib/action-scheduler/classes/WP_CLI/System_Command.php @@ -0,0 +1,282 @@ +store = \ActionScheduler::store(); + } + + /** + * Print in-use data store class. + * + * @param array $args Positional args. + * @param array $assoc_args Keyed args. + * @return void + * + * @subcommand data-store + */ + public function datastore( array $args, array $assoc_args ) { + echo $this->get_current_datastore(); + } + + /** + * Print in-use runner class. + * + * @param array $args Positional args. + * @param array $assoc_args Keyed args. + * @return void + */ + public function runner( array $args, array $assoc_args ) { + echo $this->get_current_runner(); + } + + /** + * Get system status. + * + * @param array $args Positional args. + * @param array $assoc_args Keyed args. + * @return void + */ + public function status( array $args, array $assoc_args ) { + /** + * Get runner status. + * + * @link https://github.com/woocommerce/action-scheduler-disable-default-runner + */ + $runner_enabled = has_action( 'action_scheduler_run_queue', array( \ActionScheduler::runner(), 'run' ) ); + + \WP_CLI::line( sprintf( 'Data store: %s', $this->get_current_datastore() ) ); + \WP_CLI::line( sprintf( 'Runner: %s%s', $this->get_current_runner(), ( $runner_enabled ? '' : ' (disabled)' ) ) ); + \WP_CLI::line( sprintf( 'Version: %s', $this->get_latest_version() ) ); + + $rows = array(); + $action_counts = $this->store->action_counts(); + $oldest_and_newest = $this->get_oldest_and_newest( array_keys( $action_counts ) ); + + foreach ( $action_counts as $status => $count ) { + $rows[] = array( + 'status' => $status, + 'count' => $count, + 'oldest' => $oldest_and_newest[ $status ]['oldest'], + 'newest' => $oldest_and_newest[ $status ]['newest'], + ); + } + + $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'status', 'count', 'oldest', 'newest' ) ); + $formatter->display_items( $rows ); + } + + /** + * Display the active version, or all registered versions. + * + * ## OPTIONS + * + * [--all] + * : List all registered versions. + * + * @param array $args Positional args. + * @param array $assoc_args Keyed args. + * @return void + */ + public function version( array $args, array $assoc_args ) { + $all = (bool) get_flag_value( $assoc_args, 'all' ); + $latest = $this->get_latest_version(); + + if ( ! $all ) { + echo $latest; + \WP_CLI::halt( 0 ); + } + + $instance = \ActionScheduler_Versions::instance(); + $versions = $instance->get_versions(); + $rows = array(); + + foreach ( $versions as $version => $callback ) { + $active = $version === $latest; + + $rows[ $version ] = array( + 'version' => $version, + 'callback' => $callback, + 'active' => $active ? 'yes' : 'no', + ); + } + + uksort( $rows, 'version_compare' ); + + $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'version', 'callback', 'active' ) ); + $formatter->display_items( $rows ); + } + + /** + * Display the current source, or all registered sources. + * + * ## OPTIONS + * + * [--all] + * : List all registered sources. + * + * [--fullpath] + * : List full path of source(s). + * + * @param array $args Positional args. + * @param array $assoc_args Keyed args. + * @uses ActionScheduler_SystemInformation::active_source_path() + * @uses \WP_CLI\Formatter::display_items() + * @uses $this->get_latest_version() + * @return void + */ + public function source( array $args, array $assoc_args ) { + $all = (bool) get_flag_value( $assoc_args, 'all' ); + $fullpath = (bool) get_flag_value( $assoc_args, 'fullpath' ); + $source = ActionScheduler_SystemInformation::active_source_path(); + $path = $source; + + if ( ! $fullpath ) { + $path = str_replace( ABSPATH, '', $path ); + } + + if ( ! $all ) { + echo $path; + \WP_CLI::halt( 0 ); + } + + $sources = ActionScheduler_SystemInformation::get_sources(); + + if ( empty( $sources ) ) { + WP_CLI::log( __( 'Detailed information about registered sources is not currently available.', 'action-scheduler' ) ); + return; + } + + $rows = array(); + + foreach ( $sources as $check_source => $version ) { + $active = dirname( $check_source ) === $source; + $path = $check_source; + + if ( ! $fullpath ) { + $path = str_replace( ABSPATH, '', $path ); + } + + $rows[ $check_source ] = array( + 'source' => $path, + 'version' => $version, + 'active' => $active ? 'yes' : 'no', + ); + } + + ksort( $rows ); + + \WP_CLI::log( PHP_EOL . 'Please note there can only be one unique registered instance of Action Scheduler per ' . PHP_EOL . 'version number, so this list may not include all the currently present copies of ' . PHP_EOL . 'Action Scheduler.' . PHP_EOL ); + + $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'source', 'version', 'active' ) ); + $formatter->display_items( $rows ); + } + + /** + * Get current data store. + * + * @return string + */ + protected function get_current_datastore() { + return get_class( $this->store ); + } + + /** + * Get latest version. + * + * @param null|\ActionScheduler_Versions $instance Versions. + * @return string + */ + protected function get_latest_version( $instance = null ) { + if ( is_null( $instance ) ) { + $instance = \ActionScheduler_Versions::instance(); + } + + return $instance->latest_version(); + } + + /** + * Get current runner. + * + * @return string + */ + protected function get_current_runner() { + return get_class( \ActionScheduler::runner() ); + } + + /** + * Get oldest and newest scheduled dates for a given set of statuses. + * + * @param array $status_keys Set of statuses to find oldest & newest action for. + * @return array + */ + protected function get_oldest_and_newest( $status_keys ) { + $oldest_and_newest = array(); + + foreach ( $status_keys as $status ) { + $oldest_and_newest[ $status ] = array( + 'oldest' => '–', + 'newest' => '–', + ); + + if ( 'in-progress' === $status ) { + continue; + } + + $oldest_and_newest[ $status ]['oldest'] = $this->get_action_status_date( $status, 'oldest' ); + $oldest_and_newest[ $status ]['newest'] = $this->get_action_status_date( $status, 'newest' ); + } + + return $oldest_and_newest; + } + + /** + * Get oldest or newest scheduled date for a given status. + * + * @param string $status Action status label/name string. + * @param string $date_type Oldest or Newest. + * @return string + */ + protected function get_action_status_date( $status, $date_type = 'oldest' ) { + $order = 'oldest' === $date_type ? 'ASC' : 'DESC'; + + $args = array( + 'status' => $status, + 'per_page' => 1, + 'order' => $order, + ); + + $action = $this->store->query_actions( $args ); + + if ( ! empty( $action ) ) { + $date_object = $this->store->get_date( $action[0] ); + $action_date = $date_object->format( 'Y-m-d H:i:s O' ); + } else { + $action_date = '–'; + } + + return $action_date; + } + +} diff --git a/lib/action-scheduler/classes/abstracts/ActionScheduler.php b/lib/action-scheduler/classes/abstracts/ActionScheduler.php index 0163f707..03922d8a 100644 --- a/lib/action-scheduler/classes/abstracts/ActionScheduler.php +++ b/lib/action-scheduler/classes/abstracts/ActionScheduler.php @@ -5,67 +5,109 @@ /** * Class ActionScheduler + * * @codeCoverageIgnore */ abstract class ActionScheduler { + + /** + * Plugin file path. + * + * @var string + */ private static $plugin_file = ''; - /** @var ActionScheduler_ActionFactory */ - private static $factory = NULL; - /** @var bool */ + + /** + * ActionScheduler_ActionFactory instance. + * + * @var ActionScheduler_ActionFactory + */ + private static $factory = null; + + /** + * Data store is initialized. + * + * @var bool + */ private static $data_store_initialized = false; + /** + * Factory. + */ public static function factory() { - if ( !isset(self::$factory) ) { + if ( ! isset( self::$factory ) ) { self::$factory = new ActionScheduler_ActionFactory(); } return self::$factory; } + /** + * Get Store instance. + */ public static function store() { return ActionScheduler_Store::instance(); } + /** + * Get Lock instance. + */ public static function lock() { return ActionScheduler_Lock::instance(); } + /** + * Get Logger instance. + */ public static function logger() { return ActionScheduler_Logger::instance(); } + /** + * Get QueueRunner instance. + */ public static function runner() { return ActionScheduler_QueueRunner::instance(); } + /** + * Get AdminView instance. + */ public static function admin_view() { return ActionScheduler_AdminView::instance(); } /** * Get the absolute system path to the plugin directory, or a file therein + * * @static - * @param string $path + * @param string $path Path relative to plugin directory. * @return string */ public static function plugin_path( $path ) { - $base = dirname(self::$plugin_file); + $base = dirname( self::$plugin_file ); if ( $path ) { - return trailingslashit($base).$path; + return trailingslashit( $base ) . $path; } else { - return untrailingslashit($base); + return untrailingslashit( $base ); } } /** * Get the absolute URL to the plugin directory, or a file therein + * * @static - * @param string $path + * @param string $path Path relative to plugin directory. * @return string */ public static function plugin_url( $path ) { - return plugins_url($path, self::$plugin_file); + return plugins_url( $path, self::$plugin_file ); } + /** + * Autoload. + * + * @param string $class Class name. + */ public static function autoload( $class ) { $d = DIRECTORY_SEPARATOR; $classes_dir = self::plugin_path( 'classes' . $d ); @@ -91,7 +133,7 @@ public static function autoload( $class ) { $dir = $classes_dir . 'schema' . $d; } elseif ( strpos( $class, 'ActionScheduler' ) === 0 ) { $segments = explode( '_', $class ); - $type = isset( $segments[ 1 ] ) ? $segments[ 1 ] : ''; + $type = isset( $segments[1] ) ? $segments[1] : ''; switch ( $type ) { case 'WPCLI': @@ -119,7 +161,7 @@ public static function autoload( $class ) { } if ( file_exists( $dir . "{$class}.php" ) ) { - include( $dir . "{$class}.php" ); + include $dir . "{$class}.php"; return; } } @@ -128,7 +170,7 @@ public static function autoload( $class ) { * Initialize the plugin * * @static - * @param string $plugin_file + * @param string $plugin_file Plugin file path. */ public static function init( $plugin_file ) { self::$plugin_file = $plugin_file; @@ -139,20 +181,23 @@ public static function init( $plugin_file ) { */ do_action( 'action_scheduler_pre_init' ); - require_once( self::plugin_path( 'functions.php' ) ); + require_once self::plugin_path( 'functions.php' ); ActionScheduler_DataController::init(); - $store = self::store(); - $logger = self::logger(); - $runner = self::runner(); - $admin_view = self::admin_view(); + $store = self::store(); + $logger = self::logger(); + $runner = self::runner(); + $admin_view = self::admin_view(); + $recurring_action_scheduler = new ActionScheduler_RecurringActionScheduler(); // Ensure initialization on plugin activation. if ( ! did_action( 'init' ) ) { - add_action( 'init', array( $admin_view, 'init' ), 0, 0 ); // run before $store::init() + // phpcs:ignore Squiz.PHP.CommentedOutCode + add_action( 'init', array( $admin_view, 'init' ), 0, 0 ); // run before $store::init(). add_action( 'init', array( $store, 'init' ), 1, 0 ); add_action( 'init', array( $logger, 'init' ), 1, 0 ); add_action( 'init', array( $runner, 'init' ), 1, 0 ); + add_action( 'init', array( $recurring_action_scheduler, 'init' ), 1, 0 ); add_action( 'init', @@ -180,6 +225,7 @@ function () { $store->init(); $logger->init(); $runner->init(); + $recurring_action_scheduler->init(); self::$data_store_initialized = true; /** @@ -191,12 +237,14 @@ function () { } if ( apply_filters( 'action_scheduler_load_deprecated_functions', true ) ) { - require_once( self::plugin_path( 'deprecated/functions.php' ) ); + require_once self::plugin_path( 'deprecated/functions.php' ); } if ( defined( 'WP_CLI' ) && WP_CLI ) { WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Scheduler_command' ); WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Clean_Command' ); + WP_CLI::add_command( 'action-scheduler action', '\Action_Scheduler\WP_CLI\Action_Command' ); + WP_CLI::add_command( 'action-scheduler', '\Action_Scheduler\WP_CLI\System_Command' ); if ( ! ActionScheduler_DataController::is_migration_complete() && Controller::instance()->allow_migration() ) { $command = new Migration_Command(); $command->register(); @@ -226,7 +274,7 @@ public static function is_initialized( $function_name = null ) { __( '%s() was called before the Action Scheduler data store was initialized', 'action-scheduler' ), esc_attr( $function_name ) ); - _doing_it_wrong( $function_name, $message, '3.1.6' ); + _doing_it_wrong( esc_html( $function_name ), esc_html( $message ), '3.1.6' ); } return self::$data_store_initialized; @@ -253,6 +301,7 @@ protected static function is_class_abstract( $class ) { 'ActionScheduler_Abstract_Schema' => true, 'ActionScheduler_Store' => true, 'ActionScheduler_TimezoneHelper' => true, + 'ActionScheduler_WPCLI_Command' => true, ); return isset( $abstracts[ $class ] ) && $abstracts[ $class ]; @@ -281,7 +330,7 @@ protected static function is_class_migration( $class ) { ); $segments = explode( '_', $class ); - $segment = isset( $segments[ 1 ] ) ? $segments[ 1 ] : $class; + $segment = isset( $segments[1] ) ? $segments[1] : $class; return isset( $migration_segments[ $segment ] ) && $migration_segments[ $segment ]; } @@ -297,29 +346,46 @@ protected static function is_class_migration( $class ) { */ protected static function is_class_cli( $class ) { static $cli_segments = array( - 'QueueRunner' => true, - 'Command' => true, - 'ProgressBar' => true, + 'QueueRunner' => true, + 'Command' => true, + 'ProgressBar' => true, + '\Action_Scheduler\WP_CLI\Action_Command' => true, + '\Action_Scheduler\WP_CLI\System_Command' => true, ); $segments = explode( '_', $class ); - $segment = isset( $segments[ 1 ] ) ? $segments[ 1 ] : $class; + $segment = isset( $segments[1] ) ? $segments[1] : $class; return isset( $cli_segments[ $segment ] ) && $cli_segments[ $segment ]; } + /** + * Clone. + */ final public function __clone() { - trigger_error("Singleton. No cloning allowed!", E_USER_ERROR); + trigger_error( 'Singleton. No cloning allowed!', E_USER_ERROR ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error } + /** + * Wakeup. + */ final public function __wakeup() { - trigger_error("Singleton. No serialization allowed!", E_USER_ERROR); + trigger_error( 'Singleton. No serialization allowed!', E_USER_ERROR ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error } + /** + * Construct. + */ final private function __construct() {} /** Deprecated **/ + /** + * Get DateTime object. + * + * @param null|string $when Date/time string. + * @param string $timezone Timezone string. + */ public static function get_datetime_object( $when = null, $timezone = 'UTC' ) { _deprecated_function( __METHOD__, '2.0', 'wcs_add_months()' ); return as_get_datetime_object( $when, $timezone ); diff --git a/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php b/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php index 371917e3..d5b4bf49 100644 --- a/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php +++ b/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php @@ -5,13 +5,25 @@ */ abstract class ActionScheduler_Abstract_QueueRunner extends ActionScheduler_Abstract_QueueRunner_Deprecated { - /** @var ActionScheduler_QueueCleaner */ + /** + * ActionScheduler_QueueCleaner instance. + * + * @var ActionScheduler_QueueCleaner + */ protected $cleaner; - /** @var ActionScheduler_FatalErrorMonitor */ + /** + * ActionScheduler_FatalErrorMonitor instance. + * + * @var ActionScheduler_FatalErrorMonitor + */ protected $monitor; - /** @var ActionScheduler_Store */ + /** + * ActionScheduler_Store instance. + * + * @var ActionScheduler_Store + */ protected $store; /** @@ -27,11 +39,11 @@ abstract class ActionScheduler_Abstract_QueueRunner extends ActionScheduler_Abst /** * ActionScheduler_Abstract_QueueRunner constructor. * - * @param ActionScheduler_Store $store - * @param ActionScheduler_FatalErrorMonitor $monitor - * @param ActionScheduler_QueueCleaner $cleaner + * @param ActionScheduler_Store|null $store Store object. + * @param ActionScheduler_FatalErrorMonitor|null $monitor Monitor object. + * @param ActionScheduler_QueueCleaner|null $cleaner Cleaner object. */ - public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null ) { + public function __construct( ?ActionScheduler_Store $store = null, ?ActionScheduler_FatalErrorMonitor $monitor = null, ?ActionScheduler_QueueCleaner $cleaner = null ) { $this->created_time = microtime( true ); @@ -43,12 +55,14 @@ public function __construct( ActionScheduler_Store $store = null, ActionSchedule /** * Process an individual action. * - * @param int $action_id The action ID to process. + * @param int $action_id The action ID to process. * @param string $context Optional identifier for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron' - * Generally, this should be capitalised and not localised as it's a proper noun. + * Generally, this should be capitalised and not localised as it's a proper noun. + * @throws \Exception When error running action. */ public function process_action( $action_id, $context = '' ) { // Temporarily override the error handler while we process the current action. + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler set_error_handler( /** * Temporary error handler which can catch errors and convert them into exceptions. This facilitates more @@ -72,15 +86,16 @@ function ( $type, $message ) { */ try { try { - $valid_action = false; + $valid_action = true; + do_action( 'action_scheduler_before_execute', $action_id, $context ); if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) { + $valid_action = false; do_action( 'action_scheduler_execution_ignored', $action_id, $context ); return; } - $valid_action = true; do_action( 'action_scheduler_begin_execute', $action_id, $context ); $action = $this->store->fetch_action( $action_id ); @@ -141,8 +156,8 @@ private function handle_action_error( $action_id, $e, $context, $valid_action ) /** * Schedule the next instance of the action if necessary. * - * @param ActionScheduler_Action $action - * @param int $action_id + * @param ActionScheduler_Action $action Action. + * @param int $action_id Action ID. */ protected function schedule_next_instance( ActionScheduler_Action $action, $action_id ) { // If a recurring action has been consistently failing, we may wish to stop rescheduling it. @@ -192,7 +207,7 @@ private function recurring_action_is_consistently_failing( ActionScheduler_Actio 'date' => date_create( 'now', timezone_open( 'UTC' ) )->format( 'Y-m-d H:i:s' ), 'date_compare' => '<', 'per_page' => 1, - 'offset' => $consistent_failure_threshold - 1 + 'offset' => $consistent_failure_threshold - 1, ); $first_failing_action_id = $this->store->query_actions( $query_args ); @@ -222,8 +237,6 @@ private function recurring_action_is_consistently_failing( ActionScheduler_Actio /** * Run the queue cleaner. - * - * @author Jeremy Pry */ protected function run_cleanup() { $this->cleaner->clean( 10 * $this->get_time_limit() ); @@ -256,7 +269,7 @@ protected function get_time_limit() { $time_limit = 30; - // Apply deprecated filter from deprecated get_maximum_execution_time() method + // Apply deprecated filter from deprecated get_maximum_execution_time() method. if ( has_filter( 'action_scheduler_maximum_execution_time' ) ) { _deprecated_function( 'action_scheduler_maximum_execution_time', '2.1.1', 'action_scheduler_queue_runner_time_limit' ); $time_limit = apply_filters( 'action_scheduler_maximum_execution_time', $time_limit ); @@ -288,7 +301,7 @@ protected function get_execution_time() { /** * Check if the host's max execution time is (likely) to be exceeded if processing more actions. * - * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action + * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action. * @return bool */ protected function time_likely_to_be_exceeded( $processed_actions ) { @@ -318,7 +331,7 @@ protected function get_memory_limit() { if ( function_exists( 'ini_get' ) ) { $memory_limit = ini_get( 'memory_limit' ); } else { - $memory_limit = '128M'; // Sensible default, and minimum required by WooCommerce + $memory_limit = '128M'; // Sensible default, and minimum required by WooCommerce. } if ( ! $memory_limit || -1 === $memory_limit || '-1' === $memory_limit ) { @@ -353,7 +366,7 @@ protected function memory_exceeded() { * * Based on WC_Background_Process::batch_limits_exceeded() * - * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action + * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action. * @return bool */ protected function batch_limits_exceeded( $processed_actions ) { @@ -363,7 +376,6 @@ protected function batch_limits_exceeded( $processed_actions ) { /** * Process actions in the queue. * - * @author Jeremy Pry * @param string $context Optional identifier for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron' * Generally, this should be capitalised and not localised as it's a proper noun. * @return int The number of actions processed. diff --git a/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_RecurringSchedule.php b/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_RecurringSchedule.php index 0ed8bc8e..60d09e91 100644 --- a/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_RecurringSchedule.php +++ b/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_RecurringSchedule.php @@ -15,14 +15,14 @@ abstract class ActionScheduler_Abstract_RecurringSchedule extends ActionSchedule * * @var DateTime */ - private $first_date = NULL; + private $first_date = null; /** * Timestamp equivalent of @see $this->first_date * * @var int */ - protected $first_timestamp = NULL; + protected $first_timestamp = null; /** * The recurrence between each time an action is run using this schedule. @@ -35,17 +35,21 @@ abstract class ActionScheduler_Abstract_RecurringSchedule extends ActionSchedule protected $recurrence; /** - * @param DateTime $date The date & time to run the action. - * @param mixed $recurrence The data used to determine the schedule's recurrence. + * Construct. + * + * @param DateTime $date The date & time to run the action. + * @param mixed $recurrence The data used to determine the schedule's recurrence. * @param DateTime|null $first (Optional) The date & time the first instance of this interval schedule ran. Default null, meaning this is the first instance. */ - public function __construct( DateTime $date, $recurrence, DateTime $first = null ) { + public function __construct( DateTime $date, $recurrence, ?DateTime $first = null ) { parent::__construct( $date ); $this->first_date = empty( $first ) ? $date : $first; $this->recurrence = $recurrence; } /** + * Schedule is recurring. + * * @return bool */ public function is_recurring() { @@ -62,6 +66,8 @@ public function get_first_date() { } /** + * Get the schedule's recurrence. + * * @return string */ public function get_recurrence() { @@ -70,15 +76,19 @@ public function get_recurrence() { /** * For PHP 5.2 compat, since DateTime objects can't be serialized + * * @return array */ public function __sleep() { - $sleep_params = parent::__sleep(); + $sleep_params = parent::__sleep(); $this->first_timestamp = $this->first_date->getTimestamp(); - return array_merge( $sleep_params, array( - 'first_timestamp', - 'recurrence' - ) ); + return array_merge( + $sleep_params, + array( + 'first_timestamp', + 'recurrence', + ) + ); } /** diff --git a/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schedule.php b/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schedule.php index 2631ef55..b7826b41 100644 --- a/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schedule.php +++ b/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schedule.php @@ -10,16 +10,18 @@ abstract class ActionScheduler_Abstract_Schedule extends ActionScheduler_Schedul * * @var DateTime */ - private $scheduled_date = NULL; + private $scheduled_date = null; /** * Timestamp equivalent of @see $this->scheduled_date * * @var int */ - protected $scheduled_timestamp = NULL; + protected $scheduled_timestamp = null; /** + * Construct. + * * @param DateTime $date The date & time to run the action. */ public function __construct( DateTime $date ) { @@ -36,7 +38,7 @@ abstract public function is_recurring(); /** * Calculate when the next instance of this schedule would run based on a given date & time. * - * @param DateTime $after + * @param DateTime $after Start timestamp. * @return DateTime */ abstract protected function calculate_next( DateTime $after ); @@ -44,7 +46,7 @@ abstract protected function calculate_next( DateTime $after ); /** * Get the next date & time when this schedule should run after a given date & time. * - * @param DateTime $after + * @param DateTime $after Start timestamp. * @return DateTime|null */ public function get_next( DateTime $after ) { @@ -66,7 +68,8 @@ public function get_date() { } /** - * For PHP 5.2 compat, since DateTime objects can't be serialized + * For PHP 5.2 compat, because DateTime objects can't be serialized + * * @return array */ public function __sleep() { @@ -76,6 +79,9 @@ public function __sleep() { ); } + /** + * Wakeup. + */ public function __wakeup() { $this->scheduled_date = as_get_datetime_object( $this->scheduled_timestamp ); unset( $this->scheduled_timestamp ); diff --git a/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php b/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php index 3fd259ea..69817fe7 100644 --- a/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php +++ b/lib/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php @@ -13,17 +13,23 @@ abstract class ActionScheduler_Abstract_Schema { /** - * @var int Increment this value in derived class to trigger a schema update. + * Increment this value in derived class to trigger a schema update. + * + * @var int */ protected $schema_version = 1; /** - * @var string Schema version stored in database. + * Schema version stored in database. + * + * @var string */ protected $db_version; /** - * @var array Names of tables that will be registered by this class. + * Names of tables that will be registered by this class. + * + * @var array */ protected $tables = array(); @@ -43,14 +49,14 @@ public function init() {} public function register_tables( $force_update = false ) { global $wpdb; - // make WP aware of our tables + // make WP aware of our tables. foreach ( $this->tables as $table ) { $wpdb->tables[] = $table; $name = $this->get_full_table_name( $table ); $wpdb->$table = $name; } - // create the tables + // create the tables. if ( $this->schema_update_required() || $force_update ) { foreach ( $this->tables as $table ) { /** @@ -67,7 +73,9 @@ public function register_tables( $force_update = false ) { } /** - * @param string $table The name of the table + * Get table definition. + * + * @param string $table The name of the table. * * @return string The CREATE TABLE statement, suitable for passing to dbDelta */ @@ -84,7 +92,7 @@ private function schema_update_required() { $option_name = 'schema-' . static::class; $this->db_version = get_option( $option_name, 0 ); - // Check for schema option stored by the Action Scheduler Custom Tables plugin in case site has migrated from that plugin with an older schema + // Check for schema option stored by the Action Scheduler Custom Tables plugin in case site has migrated from that plugin with an older schema. if ( 0 === $this->db_version ) { $plugin_option_name = 'schema-'; @@ -115,7 +123,7 @@ private function schema_update_required() { private function mark_schema_update_complete() { $option_name = 'schema-' . static::class; - // work around race conditions and ensure that our option updates + // work around race conditions and ensure that our option updates. $value_to_save = (string) $this->schema_version . '.0.' . time(); update_option( $option_name, $value_to_save ); @@ -124,7 +132,7 @@ private function mark_schema_update_complete() { /** * Update the schema for the given table * - * @param string $table The name of the table to update + * @param string $table The name of the table to update. * * @return void */ @@ -135,14 +143,16 @@ private function update_table( $table ) { $updated = dbDelta( $definition ); foreach ( $updated as $updated_table => $update_description ) { if ( strpos( $update_description, 'Created table' ) === 0 ) { - do_action( 'action_scheduler/created_table', $updated_table, $table ); + do_action( 'action_scheduler/created_table', $updated_table, $table ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores } } } } /** - * @param string $table + * Get full table name. + * + * @param string $table Table name. * * @return string The full name of the table, including the * table prefix for the current blog diff --git a/lib/action-scheduler/classes/abstracts/ActionScheduler_Lock.php b/lib/action-scheduler/classes/abstracts/ActionScheduler_Lock.php index e388a58f..72802077 100644 --- a/lib/action-scheduler/classes/abstracts/ActionScheduler_Lock.php +++ b/lib/action-scheduler/classes/abstracts/ActionScheduler_Lock.php @@ -7,10 +7,18 @@ */ abstract class ActionScheduler_Lock { - /** @var ActionScheduler_Lock */ - private static $locker = NULL; + /** + * Instance. + * + * @var ActionScheduler_Lock + */ + private static $locker = null; - /** @var int */ + /** + * Duration of lock. + * + * @var int + */ protected static $lock_duration = MINUTE_IN_SECONDS; /** @@ -52,11 +60,13 @@ protected function get_duration( $lock_type ) { } /** + * Get instance. + * * @return ActionScheduler_Lock */ public static function instance() { if ( empty( self::$locker ) ) { - $class = apply_filters( 'action_scheduler_lock_class', 'ActionScheduler_OptionLock' ); + $class = apply_filters( 'action_scheduler_lock_class', 'ActionScheduler_OptionLock' ); self::$locker = new $class(); } return self::$locker; diff --git a/lib/action-scheduler/classes/abstracts/ActionScheduler_Logger.php b/lib/action-scheduler/classes/abstracts/ActionScheduler_Logger.php index 0627251c..94ee2a93 100644 --- a/lib/action-scheduler/classes/abstracts/ActionScheduler_Logger.php +++ b/lib/action-scheduler/classes/abstracts/ActionScheduler_Logger.php @@ -2,40 +2,55 @@ /** * Class ActionScheduler_Logger + * * @codeCoverageIgnore */ abstract class ActionScheduler_Logger { - private static $logger = NULL; /** + * Instance. + * + * @var null|self + */ + private static $logger = null; + + /** + * Get instance. + * * @return ActionScheduler_Logger */ public static function instance() { - if ( empty(self::$logger) ) { - $class = apply_filters('action_scheduler_logger_class', 'ActionScheduler_wpCommentLogger'); + if ( empty( self::$logger ) ) { + $class = apply_filters( 'action_scheduler_logger_class', 'ActionScheduler_wpCommentLogger' ); self::$logger = new $class(); } return self::$logger; } /** - * @param string $action_id - * @param string $message - * @param DateTime $date + * Create log entry. + * + * @param string $action_id Action ID. + * @param string $message Log message. + * @param DateTime|null $date Log date. * * @return string The log entry ID */ - abstract public function log( $action_id, $message, DateTime $date = NULL ); + abstract public function log( $action_id, $message, ?DateTime $date = null ); /** - * @param string $entry_id + * Get action's log entry. + * + * @param string $entry_id Entry ID. * * @return ActionScheduler_LogEntry */ abstract public function get_entry( $entry_id ); /** - * @param string $action_id + * Get action's logs. + * + * @param string $action_id Action ID. * * @return ActionScheduler_LogEntry[] */ @@ -43,6 +58,8 @@ abstract public function get_logs( $action_id ); /** + * Initialize. + * * @codeCoverageIgnore */ public function init() { @@ -60,22 +77,44 @@ public function init() { add_action( 'action_scheduler_bulk_cancel_actions', array( $this, 'bulk_log_cancel_actions' ), 10, 1 ); } + /** + * Register callback for storing action. + */ public function hook_stored_action() { add_action( 'action_scheduler_stored_action', array( $this, 'log_stored_action' ) ); } + /** + * Unhook callback for storing action. + */ public function unhook_stored_action() { remove_action( 'action_scheduler_stored_action', array( $this, 'log_stored_action' ) ); } + /** + * Log action stored. + * + * @param int $action_id Action ID. + */ public function log_stored_action( $action_id ) { $this->log( $action_id, __( 'action created', 'action-scheduler' ) ); } + /** + * Log action cancellation. + * + * @param int $action_id Action ID. + */ public function log_canceled_action( $action_id ) { $this->log( $action_id, __( 'action canceled', 'action-scheduler' ) ); } + /** + * Log action start. + * + * @param int $action_id Action ID. + * @param string $context Action execution context. + */ public function log_started_action( $action_id, $context = '' ) { if ( ! empty( $context ) ) { /* translators: %s: context */ @@ -86,7 +125,14 @@ public function log_started_action( $action_id, $context = '' ) { $this->log( $action_id, $message ); } - public function log_completed_action( $action_id, $action = NULL, $context = '' ) { + /** + * Log action completion. + * + * @param int $action_id Action ID. + * @param null|ActionScheduler_Action $action Action. + * @param string $context Action execution context. + */ + public function log_completed_action( $action_id, $action = null, $context = '' ) { if ( ! empty( $context ) ) { /* translators: %s: context */ $message = sprintf( __( 'action complete via %s', 'action-scheduler' ), $context ); @@ -96,6 +142,13 @@ public function log_completed_action( $action_id, $action = NULL, $context = '' $this->log( $action_id, $message ); } + /** + * Log action failure. + * + * @param int $action_id Action ID. + * @param Exception $exception Exception. + * @param string $context Action execution context. + */ public function log_failed_action( $action_id, Exception $exception, $context = '' ) { if ( ! empty( $context ) ) { /* translators: 1: context 2: exception message */ @@ -107,11 +160,23 @@ public function log_failed_action( $action_id, Exception $exception, $context = $this->log( $action_id, $message ); } + /** + * Log action timeout. + * + * @param int $action_id Action ID. + * @param string $timeout Timeout. + */ public function log_timed_out_action( $action_id, $timeout ) { /* translators: %s: amount of time */ $this->log( $action_id, sprintf( __( 'action marked as failed after %s seconds. Unknown error occurred. Check server, PHP and database error logs to diagnose cause.', 'action-scheduler' ), $timeout ) ); } + /** + * Log unexpected shutdown. + * + * @param int $action_id Action ID. + * @param mixed[] $error Error. + */ public function log_unexpected_shutdown( $action_id, $error ) { if ( ! empty( $error ) ) { /* translators: 1: error message 2: filename 3: line */ @@ -119,10 +184,21 @@ public function log_unexpected_shutdown( $action_id, $error ) { } } + /** + * Log action reset. + * + * @param int $action_id Action ID. + */ public function log_reset_action( $action_id ) { $this->log( $action_id, __( 'action reset', 'action-scheduler' ) ); } + /** + * Log ignored action. + * + * @param int $action_id Action ID. + * @param string $context Action execution context. + */ public function log_ignored_action( $action_id, $context = '' ) { if ( ! empty( $context ) ) { /* translators: %s: context */ @@ -134,12 +210,12 @@ public function log_ignored_action( $action_id, $context = '' ) { } /** - * @param string $action_id - * @param Exception|NULL $exception The exception which occurred when fetching the action. NULL by default for backward compatibility. + * Log the failure of fetching the action. * - * @return ActionScheduler_LogEntry[] + * @param string $action_id Action ID. + * @param null|Exception $exception The exception which occurred when fetching the action. NULL by default for backward compatibility. */ - public function log_failed_fetch_action( $action_id, Exception $exception = NULL ) { + public function log_failed_fetch_action( $action_id, ?Exception $exception = null ) { if ( ! is_null( $exception ) ) { /* translators: %s: exception message */ @@ -151,6 +227,12 @@ public function log_failed_fetch_action( $action_id, Exception $exception = NULL $this->log( $action_id, $log_message ); } + /** + * Log the failure of scheduling the action's next instance. + * + * @param int $action_id Action ID. + * @param Exception $exception Exception object. + */ public function log_failed_schedule_next_instance( $action_id, Exception $exception ) { /* translators: %s: exception message */ $this->log( $action_id, sprintf( __( 'There was a failure scheduling the next instance of this action: %s', 'action-scheduler' ), $exception->getMessage() ) ); diff --git a/lib/action-scheduler/classes/abstracts/ActionScheduler_Store.php b/lib/action-scheduler/classes/abstracts/ActionScheduler_Store.php index 421f5a67..bf6bc429 100644 --- a/lib/action-scheduler/classes/abstracts/ActionScheduler_Store.php +++ b/lib/action-scheduler/classes/abstracts/ActionScheduler_Store.php @@ -2,6 +2,7 @@ /** * Class ActionScheduler_Store + * * @codeCoverageIgnore */ abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated { @@ -12,24 +13,36 @@ abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated { const STATUS_CANCELED = 'canceled'; const DEFAULT_CLASS = 'ActionScheduler_wpPostStore'; - /** @var ActionScheduler_Store */ - private static $store = NULL; + /** + * ActionScheduler_Store instance. + * + * @var ActionScheduler_Store + */ + private static $store = null; - /** @var int */ + /** + * Maximum length of args. + * + * @var int + */ protected static $max_args_length = 191; /** - * @param ActionScheduler_Action $action - * @param DateTime $scheduled_date Optional Date of the first instance - * to store. Otherwise uses the first date of the action's - * schedule. + * Save action. + * + * @param ActionScheduler_Action $action Action to save. + * @param null|DateTime $scheduled_date Optional Date of the first instance + * to store. Otherwise uses the first date of the action's + * schedule. * * @return int The action ID */ - abstract public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ); + abstract public function save_action( ActionScheduler_Action $action, ?DateTime $scheduled_date = null ); /** - * @param string $action_id + * Get action. + * + * @param string $action_id Action ID. * * @return ActionScheduler_Action */ @@ -141,10 +154,13 @@ abstract public function action_counts(); public function extra_action_counts() { $extra_actions = array(); - $pastdue_action_counts = ( int ) $this->query_actions( array( - 'status' => self::STATUS_PENDING, - 'date' => as_get_datetime_object(), - ), 'count' ); + $pastdue_action_counts = (int) $this->query_actions( + array( + 'status' => self::STATUS_PENDING, + 'date' => as_get_datetime_object(), + ), + 'count' + ); if ( $pastdue_action_counts ) { $extra_actions['past-due'] = $pastdue_action_counts; @@ -160,17 +176,23 @@ public function extra_action_counts() { } /** - * @param string $action_id + * Cancel action. + * + * @param string $action_id Action ID. */ abstract public function cancel_action( $action_id ); /** - * @param string $action_id + * Delete action. + * + * @param string $action_id Action ID. */ abstract public function delete_action( $action_id ); /** - * @param string $action_id + * Get action's schedule or run timestamp. + * + * @param string $action_id Action ID. * * @return DateTime The date the action is schedule to run, or the date that it ran. */ @@ -178,87 +200,111 @@ abstract public function get_date( $action_id ); /** - * @param int $max_actions - * @param DateTime $before_date Claim only actions schedule before the given date. Defaults to now. - * @param array $hooks Claim only actions with a hook or hooks. - * @param string $group Claim only actions in the given group. + * Make a claim. + * + * @param int $max_actions Maximum number of actions to claim. + * @param DateTime|null $before_date Claim only actions schedule before the given date. Defaults to now. + * @param array $hooks Claim only actions with a hook or hooks. + * @param string $group Claim only actions in the given group. * * @return ActionScheduler_ActionClaim */ - abstract public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ); + abstract public function stake_claim( $max_actions = 10, ?DateTime $before_date = null, $hooks = array(), $group = '' ); /** + * Get claim count. + * * @return int */ abstract public function get_claim_count(); /** - * @param ActionScheduler_ActionClaim $claim + * Release the claim. + * + * @param ActionScheduler_ActionClaim $claim Claim object. */ abstract public function release_claim( ActionScheduler_ActionClaim $claim ); /** - * @param string $action_id + * Un-claim the action. + * + * @param string $action_id Action ID. */ abstract public function unclaim_action( $action_id ); /** - * @param string $action_id + * Mark action as failed. + * + * @param string $action_id Action ID. */ abstract public function mark_failure( $action_id ); /** - * @param string $action_id + * Log action's execution. + * + * @param string $action_id Actoin ID. */ abstract public function log_execution( $action_id ); /** - * @param string $action_id + * Mark action as complete. + * + * @param string $action_id Action ID. */ abstract public function mark_complete( $action_id ); /** - * @param string $action_id + * Get action's status. * + * @param string $action_id Action ID. * @return string */ abstract public function get_status( $action_id ); /** - * @param string $action_id + * Get action's claim ID. + * + * @param string $action_id Action ID. * @return mixed */ abstract public function get_claim_id( $action_id ); /** - * @param string $claim_id + * Find actions by claim ID. + * + * @param string $claim_id Claim ID. * @return array */ abstract public function find_actions_by_claim_id( $claim_id ); /** - * @param string $comparison_operator + * Validate SQL operator. + * + * @param string $comparison_operator Operator. * @return string */ protected function validate_sql_comparator( $comparison_operator ) { - if ( in_array( $comparison_operator, array('!=', '>', '>=', '<', '<=', '=') ) ) { + if ( in_array( $comparison_operator, array( '!=', '>', '>=', '<', '<=', '=' ), true ) ) { return $comparison_operator; } + return '='; } /** * Get the time MySQL formatted date/time string for an action's (next) scheduled date. * - * @param ActionScheduler_Action $action - * @param DateTime $scheduled_date (optional) + * @param ActionScheduler_Action $action Action. + * @param null|DateTime $scheduled_date Action's schedule date (optional). * @return string */ - protected function get_scheduled_date_string( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) { - $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date; + protected function get_scheduled_date_string( ActionScheduler_Action $action, ?DateTime $scheduled_date = null ) { + $next = is_null( $scheduled_date ) ? $action->get_schedule()->get_date() : $scheduled_date; + if ( ! $next ) { $next = date_create(); } + $next->setTimezone( new DateTimeZone( 'UTC' ) ); return $next->format( 'Y-m-d H:i:s' ); @@ -267,12 +313,13 @@ protected function get_scheduled_date_string( ActionScheduler_Action $action, Da /** * Get the time MySQL formatted date/time string for an action's (next) scheduled date. * - * @param ActionScheduler_Action $action - * @param DateTime $scheduled_date (optional) + * @param ActionScheduler_Action|null $action Action. + * @param null|DateTime $scheduled_date Action's scheduled date (optional). * @return string */ - protected function get_scheduled_date_string_local( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) { - $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date; + protected function get_scheduled_date_string_local( ActionScheduler_Action $action, ?DateTime $scheduled_date = null ) { + $next = is_null( $scheduled_date ) ? $action->get_schedule()->get_date() : $scheduled_date; + if ( ! $next ) { $next = date_create(); } @@ -386,7 +433,7 @@ public function cancel_actions_by_group( $group ) { * * @since 3.0.0 * - * @param array $action_ids List of action IDs. + * @param int[] $action_ids List of action IDs. * * @return void */ @@ -399,7 +446,9 @@ private function bulk_cancel_actions( $action_ids ) { } /** - * @return array + * Get status labels. + * + * @return array */ public function get_status_labels() { return array( @@ -414,16 +463,18 @@ public function get_status_labels() { /** * Check if there are any pending scheduled actions due to run. * - * @param ActionScheduler_Action $action - * @param DateTime $scheduled_date (optional) * @return string */ public function has_pending_actions_due() { - $pending_actions = $this->query_actions( array( - 'date' => as_get_datetime_object(), - 'status' => ActionScheduler_Store::STATUS_PENDING, - 'orderby' => 'none', - ) ); + $pending_actions = $this->query_actions( + array( + 'per_page' => 1, + 'date' => as_get_datetime_object(), + 'status' => self::STATUS_PENDING, + 'orderby' => 'none', + ), + 'count' + ); return ! empty( $pending_actions ); } @@ -435,15 +486,19 @@ public function init() {} /** * Callable function to mark an action as migrated optionally overridden in derived classes. + * + * @param int $action_id Action ID. */ public function mark_migrated( $action_id ) {} /** + * Get instance. + * * @return ActionScheduler_Store */ public static function instance() { if ( empty( self::$store ) ) { - $class = apply_filters( 'action_scheduler_store_class', self::DEFAULT_CLASS ); + $class = apply_filters( 'action_scheduler_store_class', self::DEFAULT_CLASS ); self::$store = new $class(); } return self::$store; diff --git a/lib/action-scheduler/classes/abstracts/ActionScheduler_TimezoneHelper.php b/lib/action-scheduler/classes/abstracts/ActionScheduler_TimezoneHelper.php index 36529181..63813eba 100644 --- a/lib/action-scheduler/classes/abstracts/ActionScheduler_TimezoneHelper.php +++ b/lib/action-scheduler/classes/abstracts/ActionScheduler_TimezoneHelper.php @@ -4,7 +4,13 @@ * Class ActionScheduler_TimezoneHelper */ abstract class ActionScheduler_TimezoneHelper { - private static $local_timezone = NULL; + + /** + * DateTimeZone object. + * + * @var null|DateTimeZone + */ + private static $local_timezone = null; /** * Set a DateTime's timezone to the WordPress site's timezone, or a UTC offset @@ -12,12 +18,12 @@ abstract class ActionScheduler_TimezoneHelper { * * @since 2.1.0 * - * @param DateTime $date + * @param DateTime $date Timestamp. * @return ActionScheduler_DateTime */ public static function set_local_timezone( DateTime $date ) { - // Accept a DateTime for easier backward compatibility, even though we require methods on ActionScheduler_DateTime + // Accept a DateTime for easier backward compatibility, even though we require methods on ActionScheduler_DateTime. if ( ! is_a( $date, 'ActionScheduler_DateTime' ) ) { $date = as_get_datetime_object( $date->format( 'U' ) ); } @@ -42,6 +48,7 @@ public static function set_local_timezone( DateTime $date ) { * timezone. * * @since 2.1.0 + * @param bool $reset Unused. * @return string PHP timezone string for the site or empty if no timezone string is available. */ protected static function get_local_timezone_string( $reset = false ) { @@ -75,7 +82,7 @@ protected static function get_local_timezone_string( $reset = false ) { } } - // No timezone string + // No timezone string. return ''; } @@ -97,23 +104,26 @@ protected static function get_local_timezone_offset() { } /** + * Get local timezone. + * + * @param bool $reset Toggle to discard stored value. * @deprecated 2.1.0 */ - public static function get_local_timezone( $reset = FALSE ) { + public static function get_local_timezone( $reset = false ) { _deprecated_function( __FUNCTION__, '2.1.0', 'ActionScheduler_TimezoneHelper::set_local_timezone()' ); if ( $reset ) { - self::$local_timezone = NULL; + self::$local_timezone = null; } - if ( !isset(self::$local_timezone) ) { - $tzstring = get_option('timezone_string'); + if ( ! isset( self::$local_timezone ) ) { + $tzstring = get_option( 'timezone_string' ); - if ( empty($tzstring) ) { - $gmt_offset = get_option('gmt_offset'); - if ( $gmt_offset == 0 ) { + if ( empty( $tzstring ) ) { + $gmt_offset = absint( get_option( 'gmt_offset' ) ); + if ( 0 === $gmt_offset ) { $tzstring = 'UTC'; } else { $gmt_offset *= HOUR_IN_SECONDS; - $tzstring = timezone_name_from_abbr( '', $gmt_offset, 1 ); + $tzstring = timezone_name_from_abbr( '', $gmt_offset, 1 ); // If there's no timezone string, try again with no DST. if ( false === $tzstring ) { @@ -125,9 +135,9 @@ public static function get_local_timezone( $reset = FALSE ) { $is_dst = date( 'I' ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- we are actually interested in the runtime timezone. foreach ( timezone_abbreviations_list() as $abbr ) { foreach ( $abbr as $city ) { - if ( $city['dst'] == $is_dst && $city['offset'] == $gmt_offset ) { + if ( $city['dst'] === $is_dst && $city['offset'] === $gmt_offset ) { // If there's no valid timezone ID, keep looking. - if ( null === $city['timezone_id'] ) { + if ( is_null( $city['timezone_id'] ) ) { continue; } @@ -145,7 +155,7 @@ public static function get_local_timezone( $reset = FALSE ) { } } - self::$local_timezone = new DateTimeZone($tzstring); + self::$local_timezone = new DateTimeZone( $tzstring ); } return self::$local_timezone; } diff --git a/lib/action-scheduler/classes/abstracts/ActionScheduler_WPCLI_Command.php b/lib/action-scheduler/classes/abstracts/ActionScheduler_WPCLI_Command.php new file mode 100644 index 00000000..847c109e --- /dev/null +++ b/lib/action-scheduler/classes/abstracts/ActionScheduler_WPCLI_Command.php @@ -0,0 +1,83 @@ + + */ + protected $assoc_args; + + /** + * Construct. + * + * @param string[] $args Positional arguments. + * @param array $assoc_args Keyed arguments. + * @throws \Exception When loading a CLI command file outside of WP CLI context. + */ + public function __construct( array $args, array $assoc_args ) { + if ( ! defined( 'WP_CLI' ) || ! constant( 'WP_CLI' ) ) { + /* translators: %s php class name */ + throw new \Exception( sprintf( __( 'The %s class can only be run within WP CLI.', 'action-scheduler' ), get_class( $this ) ) ); + } + + $this->args = $args; + $this->assoc_args = $assoc_args; + } + + /** + * Execute command. + */ + abstract public function execute(); + + /** + * Get the scheduled date in a human friendly format. + * + * @see ActionScheduler_ListTable::get_schedule_display_string() + * @param ActionScheduler_Schedule $schedule Schedule. + * @return string + */ + protected function get_schedule_display_string( ActionScheduler_Schedule $schedule ) { + + $schedule_display_string = ''; + + if ( ! $schedule->get_date() ) { + return '0000-00-00 00:00:00'; + } + + $next_timestamp = $schedule->get_date()->getTimestamp(); + + $schedule_display_string .= $schedule->get_date()->format( static::DATE_FORMAT ); + + return $schedule_display_string; + } + + /** + * Transforms arguments with '__' from CSV into expected arrays. + * + * @see \WP_CLI\CommandWithDBObject::process_csv_arguments_to_arrays() + * @link https://github.com/wp-cli/entity-command/blob/c270cc9a2367cb8f5845f26a6b5e203397c91392/src/WP_CLI/CommandWithDBObject.php#L99 + * @return void + */ + protected function process_csv_arguments_to_arrays() { + foreach ( $this->assoc_args as $k => $v ) { + if ( false !== strpos( $k, '__' ) ) { + $this->assoc_args[ $k ] = explode( ',', $v ); + } + } + } + +} diff --git a/lib/action-scheduler/classes/actions/ActionScheduler_Action.php b/lib/action-scheduler/classes/actions/ActionScheduler_Action.php index ddf33d5d..1613efa1 100644 --- a/lib/action-scheduler/classes/actions/ActionScheduler_Action.php +++ b/lib/action-scheduler/classes/actions/ActionScheduler_Action.php @@ -4,10 +4,32 @@ * Class ActionScheduler_Action */ class ActionScheduler_Action { + /** + * Action's hook. + * + * @var string + */ protected $hook = ''; + + /** + * Action's args. + * + * @var array + */ protected $args = array(); - /** @var ActionScheduler_Schedule */ - protected $schedule = NULL; + + /** + * Action's schedule. + * + * @var ActionScheduler_Schedule + */ + protected $schedule = null; + + /** + * Action's group. + * + * @var string + */ protected $group = ''; /** @@ -23,12 +45,20 @@ class ActionScheduler_Action { */ protected $priority = 10; - public function __construct( $hook, array $args = array(), ActionScheduler_Schedule $schedule = NULL, $group = '' ) { + /** + * Construct. + * + * @param string $hook Action's hook. + * @param mixed[] $args Action's arguments. + * @param null|ActionScheduler_Schedule $schedule Action's schedule. + * @param string $group Action's group. + */ + public function __construct( $hook, array $args = array(), ?ActionScheduler_Schedule $schedule = null, $group = '' ) { $schedule = empty( $schedule ) ? new ActionScheduler_NullSchedule() : $schedule; - $this->set_hook($hook); - $this->set_schedule($schedule); - $this->set_args($args); - $this->set_group($group); + $this->set_hook( $hook ); + $this->set_schedule( $schedule ); + $this->set_args( $args ); + $this->set_group( $group ); } /** @@ -57,43 +87,67 @@ public function execute() { } /** - * @param string $hook + * Set action's hook. + * + * @param string $hook Action's hook. */ protected function set_hook( $hook ) { $this->hook = $hook; } + /** + * Get action's hook. + */ public function get_hook() { return $this->hook; } + /** + * Set action's schedule. + * + * @param ActionScheduler_Schedule $schedule Action's schedule. + */ protected function set_schedule( ActionScheduler_Schedule $schedule ) { $this->schedule = $schedule; } /** + * Action's schedule. + * * @return ActionScheduler_Schedule */ public function get_schedule() { return $this->schedule; } + /** + * Set action's args. + * + * @param mixed[] $args Action's arguments. + */ protected function set_args( array $args ) { $this->args = $args; } + /** + * Get action's args. + */ public function get_args() { return $this->args; } /** - * @param string $group + * Section action's group. + * + * @param string $group Action's group. */ protected function set_group( $group ) { $this->group = $group; } /** + * Action's group. + * * @return string */ public function get_group() { @@ -101,10 +155,12 @@ public function get_group() { } /** - * @return bool If the action has been finished + * Action has not finished. + * + * @return bool */ public function is_finished() { - return FALSE; + return false; } /** diff --git a/lib/action-scheduler/classes/actions/ActionScheduler_CanceledAction.php b/lib/action-scheduler/classes/actions/ActionScheduler_CanceledAction.php index 8bbc5d18..c6007528 100644 --- a/lib/action-scheduler/classes/actions/ActionScheduler_CanceledAction.php +++ b/lib/action-scheduler/classes/actions/ActionScheduler_CanceledAction.php @@ -9,12 +9,14 @@ class ActionScheduler_CanceledAction extends ActionScheduler_FinishedAction { /** - * @param string $hook - * @param array $args - * @param ActionScheduler_Schedule $schedule - * @param string $group + * Construct. + * + * @param string $hook Action's hook. + * @param array $args Action's arguments. + * @param null|ActionScheduler_Schedule $schedule Action's schedule. + * @param string $group Action's group. */ - public function __construct( $hook, array $args = array(), ActionScheduler_Schedule $schedule = null, $group = '' ) { + public function __construct( $hook, array $args = array(), ?ActionScheduler_Schedule $schedule = null, $group = '' ) { parent::__construct( $hook, $args, $schedule, $group ); if ( is_null( $schedule ) ) { $this->set_schedule( new ActionScheduler_NullSchedule() ); diff --git a/lib/action-scheduler/classes/actions/ActionScheduler_FinishedAction.php b/lib/action-scheduler/classes/actions/ActionScheduler_FinishedAction.php index b23a56c6..d4e89421 100644 --- a/lib/action-scheduler/classes/actions/ActionScheduler_FinishedAction.php +++ b/lib/action-scheduler/classes/actions/ActionScheduler_FinishedAction.php @@ -5,12 +5,17 @@ */ class ActionScheduler_FinishedAction extends ActionScheduler_Action { + /** + * Execute action. + */ public function execute() { - // don't execute + // don't execute. } + /** + * Get finished state. + */ public function is_finished() { - return TRUE; + return true; } } - \ No newline at end of file diff --git a/lib/action-scheduler/classes/actions/ActionScheduler_NullAction.php b/lib/action-scheduler/classes/actions/ActionScheduler_NullAction.php index cd5dc3b0..2cfaea61 100644 --- a/lib/action-scheduler/classes/actions/ActionScheduler_NullAction.php +++ b/lib/action-scheduler/classes/actions/ActionScheduler_NullAction.php @@ -5,12 +5,21 @@ */ class ActionScheduler_NullAction extends ActionScheduler_Action { - public function __construct( $hook = '', array $args = array(), ActionScheduler_Schedule $schedule = NULL ) { + /** + * Construct. + * + * @param string $hook Action hook. + * @param mixed[] $args Action arguments. + * @param null|ActionScheduler_Schedule $schedule Action schedule. + */ + public function __construct( $hook = '', array $args = array(), ?ActionScheduler_Schedule $schedule = null ) { $this->set_schedule( new ActionScheduler_NullSchedule() ); } + /** + * Execute action. + */ public function execute() { - // don't execute + // don't execute. } } - \ No newline at end of file diff --git a/lib/action-scheduler/classes/data-stores/ActionScheduler_DBLogger.php b/lib/action-scheduler/classes/data-stores/ActionScheduler_DBLogger.php index d285c8d2..0976b066 100644 --- a/lib/action-scheduler/classes/data-stores/ActionScheduler_DBLogger.php +++ b/lib/action-scheduler/classes/data-stores/ActionScheduler_DBLogger.php @@ -12,13 +12,13 @@ class ActionScheduler_DBLogger extends ActionScheduler_Logger { /** * Add a record to an action log. * - * @param int $action_id Action ID. - * @param string $message Message to be saved in the log entry. - * @param DateTime $date Timestamp of the log entry. + * @param int $action_id Action ID. + * @param string $message Message to be saved in the log entry. + * @param DateTime|null $date Timestamp of the log entry. * * @return int The log entry ID. */ - public function log( $action_id, $message, DateTime $date = null ) { + public function log( $action_id, $message, ?DateTime $date = null ) { if ( empty( $date ) ) { $date = as_get_datetime_object(); } else { diff --git a/lib/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php b/lib/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php index e19711eb..7840fe25 100644 --- a/lib/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php +++ b/lib/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php @@ -19,18 +19,30 @@ class ActionScheduler_DBStore extends ActionScheduler_Store { */ private $claim_before_date = null; - /** @var int */ + /** + * Maximum length of args. + * + * @var int + */ protected static $max_args_length = 8000; - /** @var int */ + /** + * Maximum length of index. + * + * @var int + */ protected static $max_index_length = 191; - /** @var array List of claim filters. */ - protected $claim_filters = [ + /** + * List of claim filters. + * + * @var array + */ + protected $claim_filters = array( 'group' => '', 'hooks' => '', 'exclude-groups' => '', - ]; + ); /** * Initialize the data store @@ -47,12 +59,12 @@ public function init() { * Save an action, checks if this is a unique action before actually saving. * * @param ActionScheduler_Action $action Action object. - * @param \DateTime $scheduled_date Optional schedule date. Default null. + * @param DateTime|null $scheduled_date Optional schedule date. Default null. * * @return int Action ID. * @throws RuntimeException Throws exception when saving the action fails. */ - public function save_unique_action( ActionScheduler_Action $action, \DateTime $scheduled_date = null ) { + public function save_unique_action( ActionScheduler_Action $action, ?DateTime $scheduled_date = null ) { return $this->save_action_to_db( $action, $scheduled_date, true ); } @@ -60,12 +72,12 @@ public function save_unique_action( ActionScheduler_Action $action, \DateTime $s * Save an action. Can save duplicate action as well, prefer using `save_unique_action` instead. * * @param ActionScheduler_Action $action Action object. - * @param \DateTime $scheduled_date Optional schedule date. Default null. + * @param DateTime|null $scheduled_date Optional schedule date. Default null. * * @return int Action ID. * @throws RuntimeException Throws exception when saving the action fails. */ - public function save_action( ActionScheduler_Action $action, \DateTime $scheduled_date = null ) { + public function save_action( ActionScheduler_Action $action, ?DateTime $scheduled_date = null ) { return $this->save_action_to_db( $action, $scheduled_date, false ); } @@ -77,9 +89,9 @@ public function save_action( ActionScheduler_Action $action, \DateTime $schedule * @param bool $unique Whether the action should be unique. * * @return int Action ID. - * @throws RuntimeException Throws exception when saving the action fails. + * @throws \RuntimeException Throws exception when saving the action fails. */ - private function save_action_to_db( ActionScheduler_Action $action, DateTime $date = null, $unique = false ) { + private function save_action_to_db( ActionScheduler_Action $action, ?DateTime $date = null, $unique = false ) { global $wpdb; try { @@ -137,6 +149,7 @@ private function save_action_to_db( ActionScheduler_Action $action, DateTime $da */ private function build_insert_sql( array $data, $unique ) { global $wpdb; + $columns = array_keys( $data ); $values = array_values( $data ); $placeholders = array_map( array( $this, 'get_placeholder_for_column' ), $columns ); @@ -146,8 +159,9 @@ private function build_insert_sql( array $data, $unique ) { $column_sql = '`' . implode( '`, `', $columns ) . '`'; $placeholder_sql = implode( ', ', $placeholders ); $where_clause = $this->build_where_clause_for_insert( $data, $table_name, $unique ); + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $column_sql and $where_clause are already prepared. $placeholder_sql is hardcoded. - $insert_query = $wpdb->prepare( + $insert_query = $wpdb->prepare( " INSERT INTO $table_name ( $column_sql ) SELECT $placeholder_sql FROM DUAL @@ -222,7 +236,7 @@ private function get_placeholder_for_column( $column_name ) { 'extended_args', ); - return in_array( $column_name, $string_columns ) ? '%s' : '%d'; + return in_array( $column_name, $string_columns, true ) ? '%s' : '%d'; } /** @@ -264,7 +278,11 @@ protected function get_group_ids( $slugs, $create_if_not_exists = true ) { return array(); } - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; foreach ( $slugs as $slug ) { @@ -290,8 +308,13 @@ protected function get_group_ids( $slugs, $create_if_not_exists = true ) { * @return int Group ID. */ protected function create_group( $slug ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; + $wpdb->insert( $wpdb->actionscheduler_groups, array( 'slug' => $slug ) ); return (int) $wpdb->insert_id; @@ -305,8 +328,13 @@ protected function create_group( $slug ) { * @return ActionScheduler_Action */ public function fetch_action( $action_id ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; + $data = $wpdb->get_row( $wpdb->prepare( "SELECT a.*, g.slug AS `group` FROM {$wpdb->actionscheduler_actions} a LEFT JOIN {$wpdb->actionscheduler_groups} g ON a.group_id=g.group_id WHERE a.action_id=%d", @@ -388,7 +416,8 @@ protected function make_action_from_db_record( $data ) { * @param string $select_or_count Whether the SQL should select and return the IDs or just the row count. * * @return string SQL statement already properly escaped. - * @throws InvalidArgumentException If the query is invalid. + * @throws \InvalidArgumentException If the query is invalid. + * @throws \RuntimeException When "unknown partial args matching value". */ protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) { @@ -396,24 +425,31 @@ protected function get_query_actions_sql( array $query, $select_or_count = 'sele throw new InvalidArgumentException( __( 'Invalid value for select or count parameter. Cannot query actions.', 'action-scheduler' ) ); } - $query = wp_parse_args( $query, array( - 'hook' => '', - 'args' => null, - 'partial_args_matching' => 'off', // can be 'like' or 'json' - 'date' => null, - 'date_compare' => '<=', - 'modified' => null, - 'modified_compare' => '<=', - 'group' => '', - 'status' => '', - 'claimed' => null, - 'per_page' => 5, - 'offset' => 0, - 'orderby' => 'date', - 'order' => 'ASC', - ) ); - - /** @var \wpdb $wpdb */ + $query = wp_parse_args( + $query, + array( + 'hook' => '', + 'args' => null, + 'partial_args_matching' => 'off', // can be 'like' or 'json'. + 'date' => null, + 'date_compare' => '<=', + 'modified' => null, + 'modified_compare' => '<=', + 'group' => '', + 'status' => '', + 'claimed' => null, + 'per_page' => 5, + 'offset' => 0, + 'orderby' => 'date', + 'order' => 'ASC', + ) + ); + + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; $db_server_info = is_callable( array( $wpdb, 'db_server_info' ) ) ? $wpdb->db_server_info() : $wpdb->db_version(); @@ -428,22 +464,22 @@ protected function get_query_actions_sql( array $query, $select_or_count = 'sele } $sql = ( 'count' === $select_or_count ) ? 'SELECT count(a.action_id)' : 'SELECT a.action_id'; - $sql .= " FROM {$wpdb->actionscheduler_actions} a"; + $sql .= " FROM {$wpdb->actionscheduler_actions} a"; $sql_params = array(); if ( ! empty( $query['group'] ) || 'group' === $query['orderby'] ) { $sql .= " LEFT JOIN {$wpdb->actionscheduler_groups} g ON g.group_id=a.group_id"; } - $sql .= " WHERE 1=1"; + $sql .= ' WHERE 1=1'; if ( ! empty( $query['group'] ) ) { - $sql .= " AND g.slug=%s"; + $sql .= ' AND g.slug=%s'; $sql_params[] = $query['group']; } if ( ! empty( $query['hook'] ) ) { - $sql .= " AND a.hook=%s"; + $sql .= ' AND a.hook=%s'; $sql_params[] = $query['hook']; } @@ -466,26 +502,28 @@ protected function get_query_actions_sql( array $query, $select_or_count = 'sele } $placeholder = isset( $supported_types[ $value_type ] ) ? $supported_types[ $value_type ] : false; if ( ! $placeholder ) { - throw new \RuntimeException( sprintf( - /* translators: %s: provided value type */ - __( 'The value type for the JSON partial matching is not supported. Must be either integer, boolean, double or string. %s type provided.', 'action-scheduler' ), - $value_type - ) ); + throw new \RuntimeException( + sprintf( + /* translators: %s: provided value type */ + __( 'The value type for the JSON partial matching is not supported. Must be either integer, boolean, double or string. %s type provided.', 'action-scheduler' ), + $value_type + ) + ); } - $sql .= ' AND JSON_EXTRACT(a.args, %s)='.$placeholder; - $sql_params[] = '$.'.$key; + $sql .= ' AND JSON_EXTRACT(a.args, %s)=' . $placeholder; + $sql_params[] = '$.' . $key; $sql_params[] = $value; } break; case 'like': foreach ( $query['args'] as $key => $value ) { - $sql .= ' AND a.args LIKE %s'; + $sql .= ' AND a.args LIKE %s'; $json_partial = $wpdb->esc_like( trim( wp_json_encode( array( $key => $value ) ), '{}' ) ); $sql_params[] = "%{$json_partial}%"; } break; case 'off': - $sql .= " AND a.args=%s"; + $sql .= ' AND a.args=%s'; $sql_params[] = $this->get_args_for_query( $query['args'] ); break; default: @@ -596,7 +634,11 @@ protected function get_query_actions_sql( array $query, $select_or_count = 'sele * @return string|array|null The IDs of actions matching the query. Null on failure. */ public function query_actions( $query = array(), $query_type = 'select' ) { - /** @var wpdb $wpdb */ + /** + * Global. + * + * @var wpdb $wpdb + */ global $wpdb; $sql = $this->get_query_actions_sql( $query, $query_type ); @@ -638,7 +680,11 @@ public function action_counts() { * @throws \InvalidArgumentException If the action update failed. */ public function cancel_action( $action_id ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; $updated = $wpdb->update( @@ -687,7 +733,11 @@ public function cancel_actions_by_group( $group ) { * @param array $query_args Query parameters. */ protected function bulk_cancel_actions( $query_args ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; if ( ! is_array( $query_args ) ) { @@ -738,8 +788,13 @@ protected function bulk_cancel_actions( $query_args ) { * @throws \InvalidArgumentException If the action deletion failed. */ public function delete_action( $action_id ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; + $deleted = $wpdb->delete( $wpdb->actionscheduler_actions, array( 'action_id' => $action_id ), array( '%d' ) ); if ( empty( $deleted ) ) { /* translators: %s is the action ID */ @@ -770,8 +825,13 @@ public function get_date( $action_id ) { * @return \DateTime The GMT date the action is scheduled to run, or the date that it ran. */ protected function get_date_gmt( $action_id ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; + $record = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d", $action_id ) ); if ( empty( $record ) ) { /* translators: %s is the action ID */ @@ -787,14 +847,14 @@ protected function get_date_gmt( $action_id ) { /** * Stake a claim on actions. * - * @param int $max_actions Maximum number of action to include in claim. - * @param \DateTime $before_date Jobs must be schedule before this date. Defaults to now. - * @param array $hooks Hooks to filter for. - * @param string $group Group to filter for. + * @param int $max_actions Maximum number of action to include in claim. + * @param DateTime|null $before_date Jobs must be schedule before this date. Defaults to now. + * @param array $hooks Hooks to filter for. + * @param string $group Group to filter for. * * @return ActionScheduler_ActionClaim */ - public function stake_claim( $max_actions = 10, \DateTime $before_date = null, $hooks = array(), $group = '' ) { + public function stake_claim( $max_actions = 10, ?DateTime $before_date = null, $hooks = array(), $group = '' ) { $claim_id = $this->generate_claim_id(); $this->claim_before_date = $before_date; @@ -811,8 +871,13 @@ public function stake_claim( $max_actions = 10, \DateTime $before_date = null, $ * @return int Claim ID. */ protected function generate_claim_id() { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; + $now = as_get_datetime_object(); $wpdb->insert( $wpdb->actionscheduler_claims, array( 'date_created_gmt' => $now->format( 'Y-m-d H:i:s' ) ) ); @@ -823,7 +888,7 @@ protected function generate_claim_id() { * Set a claim filter. * * @param string $filter_name Claim filter name. - * @param mixed $filter_values Values to filter. + * @param mixed $filter_values Values to filter. * @return void */ public function set_claim_filter( $filter_name, $filter_values ) { @@ -849,29 +914,25 @@ public function get_claim_filter( $filter_name ) { /** * Mark actions claimed. * - * @param string $claim_id Claim Id. - * @param int $limit Number of action to include in claim. - * @param \DateTime $before_date Should use UTC timezone. - * @param array $hooks Hooks to filter for. - * @param string $group Group to filter for. + * @param string $claim_id Claim Id. + * @param int $limit Number of action to include in claim. + * @param DateTime|null $before_date Should use UTC timezone. + * @param array $hooks Hooks to filter for. + * @param string $group Group to filter for. * * @return int The number of actions that were claimed. * @throws \InvalidArgumentException Throws InvalidArgumentException if group doesn't exist. * @throws \RuntimeException Throws RuntimeException if unable to claim action. */ - protected function claim_actions( $claim_id, $limit, \DateTime $before_date = null, $hooks = array(), $group = '' ) { - /** @var \wpdb $wpdb */ + protected function claim_actions( $claim_id, $limit, ?DateTime $before_date = null, $hooks = array(), $group = '' ) { + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; - - $now = as_get_datetime_object(); - $date = is_null( $before_date ) ? $now : clone $before_date; - // can't use $wpdb->update() because of the <= condition. - $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s"; - $params = array( - $claim_id, - $now->format( 'Y-m-d H:i:s' ), - current_time( 'mysql' ), - ); + $now = as_get_datetime_object(); + $date = is_null( $before_date ) ? $now : clone $before_date; // Set claim filters. if ( ! empty( $hooks ) ) { @@ -885,19 +946,21 @@ protected function claim_actions( $claim_id, $limit, \DateTime $before_date = nu $group = $this->get_claim_filter( 'group' ); } - $where = 'WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s'; - $params[] = $date->format( 'Y-m-d H:i:s' ); - $params[] = self::STATUS_PENDING; + $where = 'WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s'; + $where_params = array( + $date->format( 'Y-m-d H:i:s' ), + self::STATUS_PENDING, + ); if ( ! empty( $hooks ) ) { $placeholders = array_fill( 0, count( $hooks ), '%s' ); $where .= ' AND hook IN (' . join( ', ', $placeholders ) . ')'; - $params = array_merge( $params, array_values( $hooks ) ); + $where_params = array_merge( $where_params, array_values( $hooks ) ); } $group_operator = 'IN'; if ( empty( $group ) ) { - $group = $this->get_claim_filter( 'exclude-groups' ); + $group = $this->get_claim_filter( 'exclude-groups' ); $group_operator = 'NOT IN'; } @@ -921,26 +984,38 @@ protected function claim_actions( $claim_id, $limit, \DateTime $before_date = nu } $id_list = implode( ',', array_map( 'intval', $group_ids ) ); - $where .= " AND group_id {$group_operator} ( $id_list )"; + $where .= " AND group_id {$group_operator} ( $id_list )"; } /** * Sets the order-by clause used in the action claim query. * - * @since 3.4.0 - * * @param string $order_by_sql + * @param string $claim_id Claim Id. + * @param array $hooks Hooks to filter for. + * + * @since 3.8.3 Made $claim_id and $hooks available. + * @since 3.4.0 */ - $order = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY priority ASC, attempts ASC, scheduled_date_gmt ASC, action_id ASC' ); - $params[] = $limit; + $order = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY priority ASC, attempts ASC, scheduled_date_gmt ASC, action_id ASC', $claim_id, $hooks ); + $skip_locked = $this->db_supports_skip_locked() ? ' SKIP LOCKED' : ''; + + // Selecting the action_ids that we plan to claim, while skipping any locked rows to avoid deadlocking. + $select_sql = $wpdb->prepare( "SELECT action_id from {$wpdb->actionscheduler_actions} {$where} {$order} LIMIT %d FOR UPDATE{$skip_locked}", array_merge( $where_params, array( $limit ) ) ); - $sql = $wpdb->prepare( "{$update} {$where} {$order} LIMIT %d", $params ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders - $rows_affected = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + // Now place it into an UPDATE statement by joining the result sets, allowing for the SKIP LOCKED behavior to take effect. + $update_sql = "UPDATE {$wpdb->actionscheduler_actions} t1 JOIN ( $select_sql ) t2 ON t1.action_id = t2.action_id SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s"; + $update_params = array( + $claim_id, + $now->format( 'Y-m-d H:i:s' ), + current_time( 'mysql' ), + ); + + $rows_affected = $wpdb->query( $wpdb->prepare( $update_sql, $update_params ) ); if ( false === $rows_affected ) { $error = empty( $wpdb->last_error ) ? _x( 'unknown', 'database error', 'action-scheduler' ) : $wpdb->last_error; - throw new \RuntimeException( sprintf( /* translators: %s database error. */ @@ -953,6 +1028,43 @@ protected function claim_actions( $claim_id, $limit, \DateTime $before_date = nu return (int) $rows_affected; } + /** + * Determines whether the database supports using SKIP LOCKED. This logic mimicks the $wpdb::has_cap() logic. + * + * SKIP_LOCKED support was added to MariaDB in 10.6.0 and to MySQL in 8.0.1 + * + * @return bool + */ + private function db_supports_skip_locked() { + global $wpdb; + $db_version = $wpdb->db_version(); + $db_server_info = $wpdb->db_server_info(); + $is_mariadb = ( false !== strpos( $db_server_info, 'MariaDB' ) ); + + if ( $is_mariadb && + '5.5.5' === $db_version && + PHP_VERSION_ID < 80016 // PHP 8.0.15 or older. + ) { + /* + * Account for MariaDB version being prefixed with '5.5.5-' on older PHP versions. + */ + $db_server_info = preg_replace( '/^5\.5\.5-(.*)/', '$1', $db_server_info ); + $db_version = preg_replace( '/[^0-9.].*/', '', $db_server_info ); + } + + $is_supported = ( $is_mariadb && version_compare( $db_version, '10.6.0', '>=' ) ) || + ( ! $is_mariadb && version_compare( $db_version, '8.0.1', '>=' ) ); + + /** + * Filter whether the database supports the SKIP LOCKED modifier for queries. + * + * @param bool $is_supported Whether SKIP LOCKED is supported. + * + * @since 3.9.3 + */ + return apply_filters( 'action_scheduler_db_supports_skip_locked', $is_supported ); + } + /** * Get the number of active claims. * @@ -974,7 +1086,11 @@ public function get_claim_count() { * @return mixed */ public function get_claim_id( $action_id ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; $sql = "SELECT claim_id FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d"; @@ -990,7 +1106,11 @@ public function get_claim_id( $action_id ) { * @return int[] */ public function find_actions_by_claim_id( $claim_id ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; $action_ids = array(); @@ -1014,21 +1134,34 @@ public function find_actions_by_claim_id( $claim_id ) { } /** - * Release actions from a claim and delete the claim. + * Release pending actions from a claim and delete the claim. * * @param ActionScheduler_ActionClaim $claim Claim object. + * @throws \RuntimeException When unable to release actions from claim. */ public function release_claim( ActionScheduler_ActionClaim $claim ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; + + if ( 0 === intval( $claim->get_id() ) ) { + // Verify that the claim_id is valid before attempting to release it. + return; + } + /** * Deadlock warning: This function modifies actions to release them from claims that have been processed. Earlier, we used to it in a atomic query, i.e. we would update all actions belonging to a particular claim_id with claim_id = 0. * While this was functionally correct, it would cause deadlock, since this update query will hold a lock on the claim_id_.. index on the action table. * This allowed the possibility of a race condition, where the claimer query is also running at the same time, then the claimer query will also try to acquire a lock on the claim_id_.. index, and in this case if claim release query has already progressed to the point of acquiring the lock, but have not updated yet, it would cause a deadlock. * * We resolve this by getting all the actions_id that we want to release claim from in a separate query, and then releasing the claim on each of them. This way, our lock is acquired on the action_id index instead of the claim_id index. Note that the lock on claim_id will still be acquired, but it will only when we actually make the update, rather than when we select the actions. + * + * We only release pending actions in order for them to be claimed by another process. */ - $action_ids = $wpdb->get_col( $wpdb->prepare( "SELECT action_id FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d", $claim->get_id() ) ); + $action_ids = $wpdb->get_col( $wpdb->prepare( "SELECT action_id FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d AND status = %s", $claim->get_id(), self::STATUS_PENDING ) ); $row_updates = 0; if ( count( $action_ids ) > 0 ) { @@ -1057,8 +1190,13 @@ public function release_claim( ActionScheduler_ActionClaim $claim ) { * @return void */ public function unclaim_action( $action_id ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; + $wpdb->update( $wpdb->actionscheduler_actions, array( 'claim_id' => 0 ), @@ -1075,8 +1213,13 @@ public function unclaim_action( $action_id ) { * @throws \InvalidArgumentException Throw an exception if action was not updated. */ public function mark_failure( $action_id ) { - /** @var \wpdb $wpdb */ + /** + * Global. + + * @var \wpdb $wpdb + */ global $wpdb; + $updated = $wpdb->update( $wpdb->actionscheduler_actions, array( 'status' => self::STATUS_FAILED ), @@ -1100,7 +1243,11 @@ public function mark_failure( $action_id ) { * @return void */ public function log_execution( $action_id ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; $sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d"; @@ -1130,8 +1277,13 @@ public function log_execution( $action_id ) { * @throws \InvalidArgumentException Throw an exception if action was not updated. */ public function mark_complete( $action_id ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; + $updated = $wpdb->update( $wpdb->actionscheduler_actions, array( @@ -1168,13 +1320,18 @@ public function mark_complete( $action_id ) { * @throws \RuntimeException Throw an exception if action status could not be retrieved. */ public function get_status( $action_id ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; + $sql = "SELECT status FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d"; $sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $status = $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - if ( null === $status ) { + if ( is_null( $status ) ) { throw new \InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) ); } elseif ( empty( $status ) ) { throw new \RuntimeException( __( 'Unknown status found for action.', 'action-scheduler' ) ); diff --git a/lib/action-scheduler/classes/data-stores/ActionScheduler_HybridStore.php b/lib/action-scheduler/classes/data-stores/ActionScheduler_HybridStore.php index 22d61a60..40520cc8 100644 --- a/lib/action-scheduler/classes/data-stores/ActionScheduler_HybridStore.php +++ b/lib/action-scheduler/classes/data-stores/ActionScheduler_HybridStore.php @@ -15,13 +15,32 @@ class ActionScheduler_HybridStore extends Store { const DEMARKATION_OPTION = 'action_scheduler_hybrid_store_demarkation'; + /** + * Primary store instance. + * + * @var ActionScheduler_Store + */ private $primary_store; + + /** + * Secondary store instance. + * + * @var ActionScheduler_Store + */ private $secondary_store; + + /** + * Runner instance. + * + * @var Action_Scheduler\Migration\Runner + */ private $migration_runner; /** - * @var int The dividing line between IDs of actions created - * by the primary and secondary stores. + * The dividing line between IDs of actions created + * by the primary and secondary stores. + * + * @var int * * Methods that accept an action ID will compare the ID against * this to determine which store will contain that ID. In almost @@ -35,9 +54,9 @@ class ActionScheduler_HybridStore extends Store { /** * ActionScheduler_HybridStore constructor. * - * @param Config $config Migration config object. + * @param Config|null $config Migration config object. */ - public function __construct( Config $config = null ) { + public function __construct( ?Config $config = null ) { $this->demarkation_id = (int) get_option( self::DEMARKATION_OPTION, 0 ); if ( empty( $config ) ) { $config = Controller::instance()->get_migration_config_object(); @@ -53,10 +72,10 @@ public function __construct( Config $config = null ) { * @codeCoverageIgnore */ public function init() { - add_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10, 2 ); + add_action( 'action_scheduler/created_table', array( $this, 'set_autoincrement' ), 10, 2 ); $this->primary_store->init(); $this->secondary_store->init(); - remove_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10 ); + remove_action( 'action_scheduler/created_table', array( $this, 'set_autoincrement' ), 10 ); } /** @@ -64,8 +83,8 @@ public function init() { * value to be one higher than the posts table to ensure that * there are no ID collisions. * - * @param string $table_name - * @param string $table_suffix + * @param string $table_name Table name. + * @param string $table_suffix Suffix of table name. * * @return void * @codeCoverageIgnore @@ -75,10 +94,16 @@ public function set_autoincrement( $table_name, $table_suffix ) { if ( empty( $this->demarkation_id ) ) { $this->demarkation_id = $this->set_demarkation_id(); } - /** @var \wpdb $wpdb */ + + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; + /** - * A default date of '0000-00-00 00:00:00' is invalid in MySQL 5.7 when configured with + * A default date of '0000-00-00 00:00:00' is invalid in MySQL 5.7 when configured with * sql_mode including both STRICT_TRANS_TABLES and NO_ZERO_DATE. */ $default_date = new DateTime( 'tomorrow' ); @@ -88,7 +113,7 @@ public function set_autoincrement( $table_name, $table_suffix ) { $row_count = $wpdb->insert( $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE}, - [ + array( 'action_id' => $this->demarkation_id, 'hook' => '', 'status' => '', @@ -96,12 +121,12 @@ public function set_autoincrement( $table_name, $table_suffix ) { 'scheduled_date_local' => $date_local, 'last_attempt_gmt' => $date_gmt, 'last_attempt_local' => $date_local, - ] + ) ); if ( $row_count > 0 ) { $wpdb->delete( $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE}, - [ 'action_id' => $this->demarkation_id ] + array( 'action_id' => $this->demarkation_id ) ); } } @@ -119,10 +144,15 @@ public function set_autoincrement( $table_name, $table_suffix ) { */ private function set_demarkation_id( $id = null ) { if ( empty( $id ) ) { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; + $id = (int) $wpdb->get_var( "SELECT MAX(ID) FROM $wpdb->posts" ); - $id ++; + $id++; } update_option( self::DEMARKATION_OPTION, $id ); @@ -135,15 +165,15 @@ private function set_demarkation_id( $id = null ) { * After it migrates, the secondary store will logically contain * the next matching action, so return the result thence. * - * @param string $hook - * @param array $params + * @param string $hook Action's hook. + * @param array $params Action's arguments. * * @return string */ - public function find_action( $hook, $params = [] ) { + public function find_action( $hook, $params = array() ) { $found_unmigrated_action = $this->secondary_store->find_action( $hook, $params ); if ( ! empty( $found_unmigrated_action ) ) { - $this->migrate( [ $found_unmigrated_action ] ); + $this->migrate( array( $found_unmigrated_action ) ); } return $this->primary_store->find_action( $hook, $params ); @@ -154,12 +184,12 @@ public function find_action( $hook, $params = [] ) { * If any are found, migrate them immediately. Then the secondary * store will contain the canonical results. * - * @param array $query + * @param array $query Query arguments. * @param string $query_type Whether to select or count the results. Default, select. * * @return int[] */ - public function query_actions( $query = [], $query_type = 'select' ) { + public function query_actions( $query = array(), $query_type = 'select' ) { $found_unmigrated_actions = $this->secondary_store->query_actions( $query, 'select' ); if ( ! empty( $found_unmigrated_actions ) ) { $this->migrate( $found_unmigrated_actions ); @@ -203,12 +233,14 @@ public function action_counts() { * migrate them immediately, then ask the primary store for the * canonical claim. * - * @param int $max_actions - * @param DateTime|null $before_date + * @param int $max_actions Maximum number of actions to claim. + * @param null|DateTime $before_date Latest timestamp of actions to claim. + * @param string[] $hooks Hook of actions to claim. + * @param string $group Group of actions to claim. * * @return ActionScheduler_ActionClaim */ - public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) { + public function stake_claim( $max_actions = 10, ?DateTime $before_date = null, $hooks = array(), $group = '' ) { $claim = $this->secondary_store->stake_claim( $max_actions, $before_date, $hooks, $group ); $claimed_actions = $claim->get_actions(); @@ -234,11 +266,11 @@ private function migrate( $action_ids ) { * Save an action to the primary store. * * @param ActionScheduler_Action $action Action object to be saved. - * @param DateTime $date Optional. Schedule date. Default null. + * @param DateTime|null $date Optional. Schedule date. Default null. * * @return int The action ID */ - public function save_action( ActionScheduler_Action $action, DateTime $date = null ) { + public function save_action( ActionScheduler_Action $action, ?DateTime $date = null ) { return $this->primary_store->save_action( $action, $date ); } @@ -352,19 +384,19 @@ public function get_status( $action_id ) { */ protected function get_store_from_action_id( $action_id, $primary_first = false ) { if ( $primary_first ) { - $stores = [ + $stores = array( $this->primary_store, $this->secondary_store, - ]; + ); } elseif ( $action_id < $this->demarkation_id ) { - $stores = [ + $stores = array( $this->secondary_store, $this->primary_store, - ]; + ); } else { - $stores = [ + $stores = array( $this->primary_store, - ]; + ); } foreach ( $stores as $store ) { @@ -376,10 +408,12 @@ protected function get_store_from_action_id( $action_id, $primary_first = false return null; } - /* * * * * * * * * * * * * * * * * * * * * * * * * * * + /** + * * * * * * * * * * * * * * * * * * * * * * * * * * * * All claim-related functions should operate solely * on the primary store. - * * * * * * * * * * * * * * * * * * * * * * * * * * */ + * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ /** * Get the claim count from the table data store. @@ -398,7 +432,7 @@ public function get_claim_id( $action_id ) { } /** - * Release a claim in the table data store. + * Release a claim in the table data store on any pending actions. * * @param ActionScheduler_ActionClaim $claim Claim object. */ diff --git a/lib/action-scheduler/classes/data-stores/ActionScheduler_wpCommentLogger.php b/lib/action-scheduler/classes/data-stores/ActionScheduler_wpCommentLogger.php index 7215ddd9..a9d84d31 100644 --- a/lib/action-scheduler/classes/data-stores/ActionScheduler_wpCommentLogger.php +++ b/lib/action-scheduler/classes/data-stores/ActionScheduler_wpCommentLogger.php @@ -5,17 +5,19 @@ */ class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger { const AGENT = 'ActionScheduler'; - const TYPE = 'action_log'; + const TYPE = 'action_log'; /** - * @param string $action_id - * @param string $message - * @param DateTime $date + * Create log entry. + * + * @param string $action_id Action ID. + * @param string $message Action log's message. + * @param DateTime|null $date Action log's timestamp. * * @return string The log entry ID */ - public function log( $action_id, $message, DateTime $date = NULL ) { - if ( empty($date) ) { + public function log( $action_id, $message, ?DateTime $date = null ) { + if ( empty( $date ) ) { $date = as_get_datetime_object(); } else { $date = as_get_datetime_object( clone $date ); @@ -24,30 +26,41 @@ public function log( $action_id, $message, DateTime $date = NULL ) { return $comment_id; } + /** + * Create comment. + * + * @param int $action_id Action ID. + * @param string $message Action log's message. + * @param DateTime $date Action log entry's timestamp. + */ protected function create_wp_comment( $action_id, $message, DateTime $date ) { - $comment_date_gmt = $date->format('Y-m-d H:i:s'); + $comment_date_gmt = $date->format( 'Y-m-d H:i:s' ); ActionScheduler_TimezoneHelper::set_local_timezone( $date ); $comment_data = array( - 'comment_post_ID' => $action_id, - 'comment_date' => $date->format('Y-m-d H:i:s'), + 'comment_post_ID' => $action_id, + 'comment_date' => $date->format( 'Y-m-d H:i:s' ), 'comment_date_gmt' => $comment_date_gmt, - 'comment_author' => self::AGENT, - 'comment_content' => $message, - 'comment_agent' => self::AGENT, - 'comment_type' => self::TYPE, + 'comment_author' => self::AGENT, + 'comment_content' => $message, + 'comment_agent' => self::AGENT, + 'comment_type' => self::TYPE, ); - return wp_insert_comment($comment_data); + + return wp_insert_comment( $comment_data ); } /** - * @param string $entry_id + * Get single log entry for action. + * + * @param string $entry_id Entry ID. * * @return ActionScheduler_LogEntry */ public function get_entry( $entry_id ) { $comment = $this->get_comment( $entry_id ); - if ( empty($comment) || $comment->comment_type != self::TYPE ) { + + if ( empty( $comment ) || self::TYPE !== $comment->comment_type ) { return new ActionScheduler_NullLogEntry(); } @@ -57,59 +70,75 @@ public function get_entry( $entry_id ) { } /** - * @param string $action_id + * Get action's logs. + * + * @param string $action_id Action ID. * * @return ActionScheduler_LogEntry[] */ public function get_logs( $action_id ) { $status = 'all'; - if ( get_post_status($action_id) == 'trash' ) { + $logs = array(); + + if ( get_post_status( $action_id ) === 'trash' ) { $status = 'post-trashed'; } - $comments = get_comments(array( - 'post_id' => $action_id, - 'orderby' => 'comment_date_gmt', - 'order' => 'ASC', - 'type' => self::TYPE, - 'status' => $status, - )); - $logs = array(); + + $comments = get_comments( + array( + 'post_id' => $action_id, + 'orderby' => 'comment_date_gmt', + 'order' => 'ASC', + 'type' => self::TYPE, + 'status' => $status, + ) + ); + foreach ( $comments as $c ) { $entry = $this->get_entry( $c ); - if ( !empty($entry) ) { + + if ( ! empty( $entry ) ) { $logs[] = $entry; } } + return $logs; } + /** + * Get comment. + * + * @param int $comment_id Comment ID. + */ protected function get_comment( $comment_id ) { return get_comment( $comment_id ); } - - /** - * @param WP_Comment_Query $query + * Filter comment queries. + * + * @param WP_Comment_Query $query Comment query object. */ public function filter_comment_queries( $query ) { - foreach ( array('ID', 'parent', 'post_author', 'post_name', 'post_parent', 'type', 'post_type', 'post_id', 'post_ID') as $key ) { - if ( !empty($query->query_vars[$key]) ) { - return; // don't slow down queries that wouldn't include action_log comments anyway + foreach ( array( 'ID', 'parent', 'post_author', 'post_name', 'post_parent', 'type', 'post_type', 'post_id', 'post_ID' ) as $key ) { + if ( ! empty( $query->query_vars[ $key ] ) ) { + return; // don't slow down queries that wouldn't include action_log comments anyway. } } - $query->query_vars['action_log_filter'] = TRUE; + $query->query_vars['action_log_filter'] = true; add_filter( 'comments_clauses', array( $this, 'filter_comment_query_clauses' ), 10, 2 ); } /** - * @param array $clauses - * @param WP_Comment_Query $query + * Filter comment queries. + * + * @param array $clauses Query's clauses. + * @param WP_Comment_Query $query Query object. * * @return array */ public function filter_comment_query_clauses( $clauses, $query ) { - if ( !empty($query->query_vars['action_log_filter']) ) { + if ( ! empty( $query->query_vars['action_log_filter'] ) ) { $clauses['where'] .= $this->get_where_clause(); } return $clauses; @@ -119,8 +148,8 @@ public function filter_comment_query_clauses( $clauses, $query ) { * Make sure Action Scheduler logs are excluded from comment feeds, which use WP_Query, not * the WP_Comment_Query class handled by @see self::filter_comment_queries(). * - * @param string $where - * @param WP_Query $query + * @param string $where Query's `where` clause. + * @param WP_Query $query Query object. * * @return string */ @@ -144,8 +173,8 @@ protected function get_where_clause() { /** * Remove action log entries from wp_count_comments() * - * @param array $stats - * @param int $post_id + * @param array $stats Comment count. + * @param int $post_id Post ID. * * @return object */ @@ -170,17 +199,21 @@ protected function get_comment_count() { $stats = get_transient( 'as_comment_count' ); if ( ! $stats ) { - $stats = array(); - - $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type NOT IN('order_note','action_log') GROUP BY comment_approved", ARRAY_A ); - - $total = 0; - $stats = array(); - $approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' ); + $stats = array(); + $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type NOT IN('order_note','action_log') GROUP BY comment_approved", ARRAY_A ); + $total = 0; + $stats = array(); + $approved = array( + '0' => 'moderated', + '1' => 'approved', + 'spam' => 'spam', + 'trash' => 'trash', + 'post-trashed' => 'post-trashed', + ); foreach ( (array) $count as $row ) { - // Don't count post-trashed toward totals - if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] ) { + // Don't count post-trashed toward totals. + if ( 'post-trashed' !== $row['comment_approved'] && 'trash' !== $row['comment_approved'] ) { $total += $row['num_comments']; } if ( isset( $approved[ $row['comment_approved'] ] ) ) { @@ -213,6 +246,8 @@ public function delete_comment_count_cache() { } /** + * Initialize. + * * @codeCoverageIgnore */ public function init() { @@ -222,19 +257,26 @@ public function init() { parent::init(); add_action( 'pre_get_comments', array( $this, 'filter_comment_queries' ), 10, 1 ); - add_action( 'wp_count_comments', array( $this, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs + add_action( 'wp_count_comments', array( $this, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs. add_action( 'comment_feed_where', array( $this, 'filter_comment_feed' ), 10, 2 ); - // Delete comments count cache whenever there is a new comment or a comment status changes + // Delete comments count cache whenever there is a new comment or a comment status changes. add_action( 'wp_insert_comment', array( $this, 'delete_comment_count_cache' ) ); add_action( 'wp_set_comment_status', array( $this, 'delete_comment_count_cache' ) ); } + /** + * Defer comment counting. + */ public function disable_comment_counting() { - wp_defer_comment_counting(true); + wp_defer_comment_counting( true ); } + + /** + * Enable comment counting. + */ public function enable_comment_counting() { - wp_defer_comment_counting(false); + wp_defer_comment_counting( false ); } } diff --git a/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php b/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php index 3971b339..8f6375a6 100644 --- a/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php +++ b/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php @@ -30,12 +30,12 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store { * Save action. * * @param ActionScheduler_Action $action Scheduled Action. - * @param DateTime $scheduled_date Scheduled Date. + * @param DateTime|null $scheduled_date Scheduled Date. * * @throws RuntimeException Throws an exception if the action could not be saved. * @return int */ - public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = null ) { + public function save_action( ActionScheduler_Action $action, ?DateTime $scheduled_date = null ) { try { $this->validate_action( $action ); $post_array = $this->create_post_array( $action, $scheduled_date ); @@ -54,11 +54,11 @@ public function save_action( ActionScheduler_Action $action, DateTime $scheduled * Create post array. * * @param ActionScheduler_Action $action Scheduled Action. - * @param DateTime $scheduled_date Scheduled Date. + * @param DateTime|null $scheduled_date Scheduled Date. * * @return array Returns an array of post data. */ - protected function create_post_array( ActionScheduler_Action $action, DateTime $scheduled_date = null ) { + protected function create_post_array( ActionScheduler_Action $action, ?DateTime $scheduled_date = null ) { $post = array( 'post_type' => self::POST_TYPE, 'post_title' => $action->get_hook(), @@ -573,16 +573,16 @@ public function get_date_gmt( $action_id ) { /** * Stake claim. * - * @param int $max_actions Maximum number of actions. - * @param DateTime $before_date Jobs must be schedule before this date. Defaults to now. - * @param array $hooks Claim only actions with a hook or hooks. - * @param string $group Claim only actions in the given group. + * @param int $max_actions Maximum number of actions. + * @param DateTime|null $before_date Jobs must be schedule before this date. Defaults to now. + * @param array $hooks Claim only actions with a hook or hooks. + * @param string $group Claim only actions in the given group. * * @return ActionScheduler_ActionClaim * @throws RuntimeException When there is an error staking a claim. * @throws InvalidArgumentException When the given group is not valid. */ - public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) { + public function stake_claim( $max_actions = 10, ?DateTime $before_date = null, $hooks = array(), $group = '' ) { $this->claim_before_date = $before_date; $claim_id = $this->generate_claim_id(); $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group ); @@ -622,16 +622,16 @@ protected function generate_claim_id() { /** * Claim actions. * - * @param string $claim_id Claim ID. - * @param int $limit Limit. - * @param DateTime $before_date Should use UTC timezone. - * @param array $hooks Claim only actions with a hook or hooks. - * @param string $group Claim only actions in the given group. + * @param string $claim_id Claim ID. + * @param int $limit Limit. + * @param DateTime|null $before_date Should use UTC timezone. + * @param array $hooks Claim only actions with a hook or hooks. + * @param string $group Claim only actions in the given group. * * @return int The number of actions that were claimed. * @throws RuntimeException When there is a database error. */ - protected function claim_actions( $claim_id, $limit, DateTime $before_date = null, $hooks = array(), $group = '' ) { + protected function claim_actions( $claim_id, $limit, ?DateTime $before_date = null, $hooks = array(), $group = '' ) { // Set up initial variables. $date = null === $before_date ? as_get_datetime_object() : clone $before_date; $limit_ids = ! empty( $group ); @@ -791,18 +791,13 @@ public function find_actions_by_claim_id( $claim_id ) { } /** - * Release claim. + * Release pending actions from a claim. * * @param ActionScheduler_ActionClaim $claim Claim object to release. * @return void * @throws RuntimeException When the claim is not unlocked. */ public function release_claim( ActionScheduler_ActionClaim $claim ) { - $action_ids = $this->find_actions_by_claim_id( $claim->get_id() ); - if ( empty( $action_ids ) ) { - return; // nothing to do. - } - $action_id_string = implode( ',', array_map( 'intval', $action_ids ) ); /** * Global wpdb object. * @@ -810,6 +805,28 @@ public function release_claim( ActionScheduler_ActionClaim $claim ) { */ global $wpdb; + $claim_id = $claim->get_id(); + if ( trim( $claim_id ) === '' ) { + // Verify that the claim_id is valid before attempting to release it. + return; + } + + // Only attempt to release pending actions to be claimed again. Running and complete actions are no longer relevant outside of admin/analytics. + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $action_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT ID, post_date_gmt FROM {$wpdb->posts} WHERE post_type = %s AND post_password = %s AND post_status = %s", + self::POST_TYPE, + $claim_id, + self::STATUS_PENDING + ) + ); + + if ( empty( $action_ids ) ) { + return; // nothing to do. + } + $action_id_string = implode( ',', array_map( 'intval', $action_ids ) ); + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $result = $wpdb->query( $wpdb->prepare( diff --git a/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostStatusRegistrar.php b/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostStatusRegistrar.php index 246bc347..5f1f1fc0 100644 --- a/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostStatusRegistrar.php +++ b/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostStatusRegistrar.php @@ -2,9 +2,14 @@ /** * Class ActionScheduler_wpPostStore_PostStatusRegistrar + * * @codeCoverageIgnore */ class ActionScheduler_wpPostStore_PostStatusRegistrar { + + /** + * Registrar. + */ public function register() { register_post_status( ActionScheduler_Store::STATUS_RUNNING, array_merge( $this->post_status_args(), $this->post_status_running_labels() ) ); register_post_status( ActionScheduler_Store::STATUS_FAILED, array_merge( $this->post_status_args(), $this->post_status_failed_labels() ) ); diff --git a/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostTypeRegistrar.php b/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostTypeRegistrar.php index e1bcf6be..52522945 100644 --- a/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostTypeRegistrar.php +++ b/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostTypeRegistrar.php @@ -2,9 +2,13 @@ /** * Class ActionScheduler_wpPostStore_PostTypeRegistrar + * * @codeCoverageIgnore */ class ActionScheduler_wpPostStore_PostTypeRegistrar { + /** + * Registrar. + */ public function register() { register_post_type( ActionScheduler_wpPostStore::POST_TYPE, $this->post_type_args() ); } @@ -16,35 +20,34 @@ public function register() { */ protected function post_type_args() { $args = array( - 'label' => __( 'Scheduled Actions', 'action-scheduler' ), - 'description' => __( 'Scheduled actions are hooks triggered on a certain date and time.', 'action-scheduler' ), - 'public' => false, + 'label' => __( 'Scheduled Actions', 'action-scheduler' ), + 'description' => __( 'Scheduled actions are hooks triggered on a certain date and time.', 'action-scheduler' ), + 'public' => false, 'map_meta_cap' => true, 'hierarchical' => false, - 'supports' => array('title', 'editor','comments'), - 'rewrite' => false, - 'query_var' => false, - 'can_export' => true, - 'ep_mask' => EP_NONE, - 'labels' => array( - 'name' => __( 'Scheduled Actions', 'action-scheduler' ), - 'singular_name' => __( 'Scheduled Action', 'action-scheduler' ), - 'menu_name' => _x( 'Scheduled Actions', 'Admin menu name', 'action-scheduler' ), - 'add_new' => __( 'Add', 'action-scheduler' ), - 'add_new_item' => __( 'Add New Scheduled Action', 'action-scheduler' ), - 'edit' => __( 'Edit', 'action-scheduler' ), - 'edit_item' => __( 'Edit Scheduled Action', 'action-scheduler' ), - 'new_item' => __( 'New Scheduled Action', 'action-scheduler' ), - 'view' => __( 'View Action', 'action-scheduler' ), - 'view_item' => __( 'View Action', 'action-scheduler' ), - 'search_items' => __( 'Search Scheduled Actions', 'action-scheduler' ), - 'not_found' => __( 'No actions found', 'action-scheduler' ), + 'supports' => array( 'title', 'editor', 'comments' ), + 'rewrite' => false, + 'query_var' => false, + 'can_export' => true, + 'ep_mask' => EP_NONE, + 'labels' => array( + 'name' => __( 'Scheduled Actions', 'action-scheduler' ), + 'singular_name' => __( 'Scheduled Action', 'action-scheduler' ), + 'menu_name' => _x( 'Scheduled Actions', 'Admin menu name', 'action-scheduler' ), + 'add_new' => __( 'Add', 'action-scheduler' ), + 'add_new_item' => __( 'Add New Scheduled Action', 'action-scheduler' ), + 'edit' => __( 'Edit', 'action-scheduler' ), + 'edit_item' => __( 'Edit Scheduled Action', 'action-scheduler' ), + 'new_item' => __( 'New Scheduled Action', 'action-scheduler' ), + 'view' => __( 'View Action', 'action-scheduler' ), + 'view_item' => __( 'View Action', 'action-scheduler' ), + 'search_items' => __( 'Search Scheduled Actions', 'action-scheduler' ), + 'not_found' => __( 'No actions found', 'action-scheduler' ), 'not_found_in_trash' => __( 'No actions found in trash', 'action-scheduler' ), ), ); - $args = apply_filters('action_scheduler_post_type_args', $args); + $args = apply_filters( 'action_scheduler_post_type_args', $args ); return $args; } } - \ No newline at end of file diff --git a/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_TaxonomyRegistrar.php b/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_TaxonomyRegistrar.php index 367401f7..e100fc9b 100644 --- a/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_TaxonomyRegistrar.php +++ b/lib/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_TaxonomyRegistrar.php @@ -2,25 +2,32 @@ /** * Class ActionScheduler_wpPostStore_TaxonomyRegistrar + * * @codeCoverageIgnore */ class ActionScheduler_wpPostStore_TaxonomyRegistrar { + + /** + * Registrar. + */ public function register() { register_taxonomy( ActionScheduler_wpPostStore::GROUP_TAXONOMY, ActionScheduler_wpPostStore::POST_TYPE, $this->taxonomy_args() ); } + /** + * Get taxonomy arguments. + */ protected function taxonomy_args() { $args = array( - 'label' => __( 'Action Group', 'action-scheduler' ), - 'public' => false, - 'hierarchical' => false, + 'label' => __( 'Action Group', 'action-scheduler' ), + 'public' => false, + 'hierarchical' => false, 'show_admin_column' => true, - 'query_var' => false, - 'rewrite' => false, + 'query_var' => false, + 'rewrite' => false, ); $args = apply_filters( 'action_scheduler_taxonomy_args', $args ); return $args; } } - \ No newline at end of file diff --git a/lib/action-scheduler/classes/migration/ActionMigrator.php b/lib/action-scheduler/classes/migration/ActionMigrator.php index 1469de02..f0e04fe9 100644 --- a/lib/action-scheduler/classes/migration/ActionMigrator.php +++ b/lib/action-scheduler/classes/migration/ActionMigrator.php @@ -13,21 +13,33 @@ * @codeCoverageIgnore */ class ActionMigrator { - /** var ActionScheduler_Store */ + /** + * Source store instance. + * + * @var ActionScheduler_Store + */ private $source; - /** var ActionScheduler_Store */ + /** + * Destination store instance. + * + * @var ActionScheduler_Store + */ private $destination; - /** var LogMigrator */ + /** + * LogMigrator instance. + * + * @var LogMigrator + */ private $log_migrator; /** * ActionMigrator constructor. * - * @param ActionScheduler_Store $source_store Source store object. - * @param ActionScheduler_Store $destination_store Destination store object. - * @param LogMigrator $log_migrator Log migrator object. + * @param \ActionScheduler_Store $source_store Source store object. + * @param \ActionScheduler_Store $destination_store Destination store object. + * @param LogMigrator $log_migrator Log migrator object. */ public function __construct( \ActionScheduler_Store $source_store, \ActionScheduler_Store $destination_store, LogMigrator $log_migrator ) { $this->source = $source_store; @@ -41,6 +53,7 @@ public function __construct( \ActionScheduler_Store $source_store, \ActionSchedu * @param int $source_action_id Action ID. * * @return int 0|new action ID + * @throws \RuntimeException When unable to delete action from the source store. */ public function migrate( $source_action_id ) { try { @@ -52,37 +65,37 @@ public function migrate( $source_action_id ) { } if ( is_null( $action ) || empty( $status ) || ! $action->get_schedule()->get_date() ) { - // null action or empty status means the fetch operation failed or the action didn't exist - // null schedule means it's missing vital data - // delete it and move on + // null action or empty status means the fetch operation failed or the action didn't exist. + // null schedule means it's missing vital data. + // delete it and move on. try { $this->source->delete_action( $source_action_id ); - } catch ( \Exception $e ) { - // nothing to do, it didn't exist in the first place + } catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // nothing to do, it didn't exist in the first place. } - do_action( 'action_scheduler/no_action_to_migrate', $source_action_id, $this->source, $this->destination ); + do_action( 'action_scheduler/no_action_to_migrate', $source_action_id, $this->source, $this->destination ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores return 0; } try { - // Make sure the last attempt date is set correctly for completed and failed actions - $last_attempt_date = ( $status !== \ActionScheduler_Store::STATUS_PENDING ) ? $this->source->get_date( $source_action_id ) : null; + // Make sure the last attempt date is set correctly for completed and failed actions. + $last_attempt_date = ( \ActionScheduler_Store::STATUS_PENDING !== $status ) ? $this->source->get_date( $source_action_id ) : null; $destination_action_id = $this->destination->save_action( $action, null, $last_attempt_date ); } catch ( \Exception $e ) { - do_action( 'action_scheduler/migrate_action_failed', $source_action_id, $this->source, $this->destination ); + do_action( 'action_scheduler/migrate_action_failed', $source_action_id, $this->source, $this->destination ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores - return 0; // could not save the action in the new store + return 0; // could not save the action in the new store. } try { switch ( $status ) { - case \ActionScheduler_Store::STATUS_FAILED : + case \ActionScheduler_Store::STATUS_FAILED: $this->destination->mark_failure( $destination_action_id ); break; - case \ActionScheduler_Store::STATUS_CANCELED : + case \ActionScheduler_Store::STATUS_CANCELED: $this->destination->cancel_action( $destination_action_id ); break; } @@ -95,14 +108,17 @@ public function migrate( $source_action_id ) { // translators: %s is an action ID. throw new \RuntimeException( sprintf( __( 'Unable to remove source migrated action %s', 'action-scheduler' ), $source_action_id ) ); } - do_action( 'action_scheduler/migrated_action', $source_action_id, $destination_action_id, $this->source, $this->destination ); + do_action( 'action_scheduler/migrated_action', $source_action_id, $destination_action_id, $this->source, $this->destination ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores return $destination_action_id; } catch ( \Exception $e ) { - // could not delete from the old store + // could not delete from the old store. $this->source->mark_migrated( $source_action_id ); + + // phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores do_action( 'action_scheduler/migrate_action_incomplete', $source_action_id, $destination_action_id, $this->source, $this->destination ); do_action( 'action_scheduler/migrated_action', $source_action_id, $destination_action_id, $this->source, $this->destination ); + // phpcs:enable return $destination_action_id; } diff --git a/lib/action-scheduler/classes/migration/ActionScheduler_DBStoreMigrator.php b/lib/action-scheduler/classes/migration/ActionScheduler_DBStoreMigrator.php index ae0785d4..66ab9970 100644 --- a/lib/action-scheduler/classes/migration/ActionScheduler_DBStoreMigrator.php +++ b/lib/action-scheduler/classes/migration/ActionScheduler_DBStoreMigrator.php @@ -3,7 +3,7 @@ /** * Class ActionScheduler_DBStoreMigrator * - * A class for direct saving of actions to the table data store during migration. + * A class for direct saving of actions to the table data store during migration. * * @since 3.0.0 */ @@ -16,25 +16,29 @@ class ActionScheduler_DBStoreMigrator extends ActionScheduler_DBStore { * it can't have been attempted yet, but migrated completed actions will have an attempted date, so we need to save * that when first saving the action. * - * @param ActionScheduler_Action $action - * @param \DateTime $scheduled_date Optional date of the first instance to store. - * @param \DateTime $last_attempt_date Optional date the action was last attempted. + * @param ActionScheduler_Action $action Action to migrate. + * @param null|DateTime $scheduled_date Optional date of the first instance to store. + * @param null|DateTime $last_attempt_date Optional date the action was last attempted. * * @return string The action ID * @throws \RuntimeException When the action is not saved. */ - public function save_action( ActionScheduler_Action $action, \DateTime $scheduled_date = null, \DateTime $last_attempt_date = null ){ + public function save_action( ActionScheduler_Action $action, ?DateTime $scheduled_date = null, ?DateTime $last_attempt_date = null ) { try { - /** @var \wpdb $wpdb */ + /** + * Global. + * + * @var \wpdb $wpdb + */ global $wpdb; $action_id = parent::save_action( $action, $scheduled_date ); if ( null !== $last_attempt_date ) { - $data = [ + $data = array( 'last_attempt_gmt' => $this->get_scheduled_date_string( $action, $last_attempt_date ), 'last_attempt_local' => $this->get_scheduled_date_string_local( $action, $last_attempt_date ), - ]; + ); $wpdb->update( $wpdb->actionscheduler_actions, $data, array( 'action_id' => $action_id ), array( '%s', '%s' ), array( '%d' ) ); } diff --git a/lib/action-scheduler/classes/migration/BatchFetcher.php b/lib/action-scheduler/classes/migration/BatchFetcher.php index 48728010..20aa3d03 100644 --- a/lib/action-scheduler/classes/migration/BatchFetcher.php +++ b/lib/action-scheduler/classes/migration/BatchFetcher.php @@ -1,9 +1,7 @@ $now, 'per_page' => $count, 'offset' => 0, 'orderby' => 'date', 'order' => 'ASC', - ]; + ); - $priorities = [ + $priorities = array( Store::STATUS_PENDING, Store::STATUS_FAILED, Store::STATUS_CANCELED, Store::STATUS_COMPLETE, Store::STATUS_RUNNING, - '', // any other unanticipated status - ]; + '', // any other unanticipated status. + ); foreach ( $priorities as $status ) { - yield wp_parse_args( [ - 'status' => $status, - 'date_compare' => '<=', - ], $args ); - yield wp_parse_args( [ - 'status' => $status, - 'date_compare' => '>=', - ], $args ); + yield wp_parse_args( + array( + 'status' => $status, + 'date_compare' => '<=', + ), + $args + ); + + yield wp_parse_args( + array( + 'status' => $status, + 'date_compare' => '>=', + ), + $args + ); } } -} \ No newline at end of file +} diff --git a/lib/action-scheduler/classes/migration/Config.php b/lib/action-scheduler/classes/migration/Config.php index cf91a5e2..f16b4c19 100644 --- a/lib/action-scheduler/classes/migration/Config.php +++ b/lib/action-scheduler/classes/migration/Config.php @@ -17,22 +17,46 @@ * A config builder for the ActionScheduler\Migration\Runner class */ class Config { - /** @var ActionScheduler_Store */ + /** + * Source store instance. + * + * @var ActionScheduler_Store + */ private $source_store; - /** @var ActionScheduler_Logger */ + /** + * Source logger instance. + * + * @var ActionScheduler_Logger + */ private $source_logger; - /** @var ActionScheduler_Store */ + /** + * Destination store instance. + * + * @var ActionScheduler_Store + */ private $destination_store; - /** @var ActionScheduler_Logger */ + /** + * Destination logger instance. + * + * @var ActionScheduler_Logger + */ private $destination_logger; - /** @var Progress bar */ + /** + * Progress bar object. + * + * @var Action_Scheduler\WP_CLI\ProgressBar + */ private $progress_bar; - /** @var bool */ + /** + * Flag indicating a dryrun. + * + * @var bool + */ private $dry_run = false; /** @@ -46,6 +70,7 @@ public function __construct() { * Get the configured source store. * * @return ActionScheduler_Store + * @throws \RuntimeException When source store is not configured. */ public function get_source_store() { if ( empty( $this->source_store ) ) { @@ -68,6 +93,7 @@ public function set_source_store( Store $store ) { * Get the configured source logger. * * @return ActionScheduler_Logger + * @throws \RuntimeException When source logger is not configured. */ public function get_source_logger() { if ( empty( $this->source_logger ) ) { @@ -80,7 +106,7 @@ public function get_source_logger() { /** * Set the configured source logger. * - * @param ActionScheduler_Logger $logger + * @param ActionScheduler_Logger $logger Logger object. */ public function set_source_logger( Logger $logger ) { $this->source_logger = $logger; @@ -90,6 +116,7 @@ public function set_source_logger( Logger $logger ) { * Get the configured destination store. * * @return ActionScheduler_Store + * @throws \RuntimeException When destination store is not configured. */ public function get_destination_store() { if ( empty( $this->destination_store ) ) { @@ -102,7 +129,7 @@ public function get_destination_store() { /** * Set the configured destination store. * - * @param ActionScheduler_Store $store + * @param ActionScheduler_Store $store Action store object. */ public function set_destination_store( Store $store ) { $this->destination_store = $store; @@ -112,6 +139,7 @@ public function set_destination_store( Store $store ) { * Get the configured destination logger. * * @return ActionScheduler_Logger + * @throws \RuntimeException When destination logger is not configured. */ public function get_destination_logger() { if ( empty( $this->destination_logger ) ) { @@ -124,7 +152,7 @@ public function get_destination_logger() { /** * Set the configured destination logger. * - * @param ActionScheduler_Logger $logger + * @param ActionScheduler_Logger $logger Logger object. */ public function set_destination_logger( Logger $logger ) { $this->destination_logger = $logger; @@ -142,7 +170,7 @@ public function get_dry_run() { /** * Set flag indicating whether it's a dry run. * - * @param bool $dry_run + * @param bool $dry_run Dry run toggle. */ public function set_dry_run( $dry_run ) { $this->dry_run = (bool) $dry_run; @@ -160,7 +188,7 @@ public function get_progress_bar() { /** * Set progress bar object. * - * @param ActionScheduler\WPCLI\ProgressBar $progress_bar + * @param ActionScheduler\WPCLI\ProgressBar $progress_bar Progress bar object. */ public function set_progress_bar( ProgressBar $progress_bar ) { $this->progress_bar = $progress_bar; diff --git a/lib/action-scheduler/classes/migration/Controller.php b/lib/action-scheduler/classes/migration/Controller.php index b2b618d8..7c3df607 100644 --- a/lib/action-scheduler/classes/migration/Controller.php +++ b/lib/action-scheduler/classes/migration/Controller.php @@ -19,18 +19,39 @@ * @codeCoverageIgnore */ class Controller { + /** + * Instance. + * + * @var self + */ private static $instance; - /** @var Action_Scheduler\Migration\Scheduler */ + /** + * Scheduler instance. + * + * @var Action_Scheduler\Migration\Scheduler + */ private $migration_scheduler; - /** @var string */ + /** + * Class name of the store object. + * + * @var string + */ private $store_classname; - /** @var string */ + /** + * Class name of the logger object. + * + * @var string + */ private $logger_classname; - /** @var bool */ + /** + * Flag to indicate migrating custom store. + * + * @var bool + */ private $migrate_custom_store; /** @@ -142,7 +163,7 @@ public function get_migration_config_object() { } } - return apply_filters( 'action_scheduler/migration_config', $config ); + return apply_filters( 'action_scheduler/migration_config', $config ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores } /** @@ -171,15 +192,13 @@ private function hook() { add_action( 'init', array( $this, 'maybe_hook_migration' ) ); add_action( 'wp_loaded', array( $this, 'schedule_migration' ) ); - // Action Scheduler may be displayed as a Tools screen or WooCommerce > Status administration screen + // Action Scheduler may be displayed as a Tools screen or WooCommerce > Status administration screen. add_action( 'load-tools_page_action-scheduler', array( $this, 'hook_admin_notices' ), 10, 0 ); add_action( 'load-woocommerce_page_wc-status', array( $this, 'hook_admin_notices' ), 10, 0 ); } /** * Possibly hook the migration scheduler action. - * - * @author Jeremy Pry */ public function maybe_hook_migration() { if ( ! $this->allow_migration() || \ActionScheduler_DataController::is_migration_complete() ) { diff --git a/lib/action-scheduler/classes/migration/DryRun_ActionMigrator.php b/lib/action-scheduler/classes/migration/DryRun_ActionMigrator.php index ffc21c28..ab7d670e 100644 --- a/lib/action-scheduler/classes/migration/DryRun_ActionMigrator.php +++ b/lib/action-scheduler/classes/migration/DryRun_ActionMigrator.php @@ -21,7 +21,7 @@ class DryRun_ActionMigrator extends ActionMigrator { * @return int */ public function migrate( $source_action_id ) { - do_action( 'action_scheduler/migrate_action_dry_run', $source_action_id ); + do_action( 'action_scheduler/migrate_action_dry_run', $source_action_id ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores return 0; } diff --git a/lib/action-scheduler/classes/migration/DryRun_LogMigrator.php b/lib/action-scheduler/classes/migration/DryRun_LogMigrator.php index fc9e4d3d..90bd637a 100644 --- a/lib/action-scheduler/classes/migration/DryRun_LogMigrator.php +++ b/lib/action-scheduler/classes/migration/DryRun_LogMigrator.php @@ -18,6 +18,6 @@ class DryRun_LogMigrator extends LogMigrator { * @param int $destination_action_id Destination logger object. */ public function migrate( $source_action_id, $destination_action_id ) { - // no-op + // no-op. } -} \ No newline at end of file +} diff --git a/lib/action-scheduler/classes/migration/LogMigrator.php b/lib/action-scheduler/classes/migration/LogMigrator.php index b85da855..1bc41ace 100644 --- a/lib/action-scheduler/classes/migration/LogMigrator.php +++ b/lib/action-scheduler/classes/migration/LogMigrator.php @@ -15,21 +15,29 @@ * @codeCoverageIgnore */ class LogMigrator { - /** @var ActionScheduler_Logger */ + /** + * Source logger instance. + * + * @var ActionScheduler_Logger + */ private $source; - /** @var ActionScheduler_Logger */ + /** + * Destination logger instance. + * + * @var ActionScheduler_Logger + */ private $destination; /** * ActionMigrator constructor. * * @param ActionScheduler_Logger $source_logger Source logger object. - * @param ActionScheduler_Logger $destination_Logger Destination logger object. + * @param ActionScheduler_Logger $destination_logger Destination logger object. */ - public function __construct( ActionScheduler_Logger $source_logger, ActionScheduler_Logger $destination_Logger ) { + public function __construct( ActionScheduler_Logger $source_logger, ActionScheduler_Logger $destination_logger ) { $this->source = $source_logger; - $this->destination = $destination_Logger; + $this->destination = $destination_logger; } /** @@ -40,8 +48,9 @@ public function __construct( ActionScheduler_Logger $source_logger, ActionSchedu */ public function migrate( $source_action_id, $destination_action_id ) { $logs = $this->source->get_logs( $source_action_id ); + foreach ( $logs as $log ) { - if ( $log->get_action_id() == $source_action_id ) { + if ( absint( $log->get_action_id() ) === absint( $source_action_id ) ) { $this->destination->log( $destination_action_id, $log->get_message(), $log->get_date() ); } } diff --git a/lib/action-scheduler/classes/migration/Runner.php b/lib/action-scheduler/classes/migration/Runner.php index 2304a79a..85c7e044 100644 --- a/lib/action-scheduler/classes/migration/Runner.php +++ b/lib/action-scheduler/classes/migration/Runner.php @@ -13,28 +13,60 @@ * @codeCoverageIgnore */ class Runner { - /** @var ActionScheduler_Store */ + /** + * Source store instance. + * + * @var ActionScheduler_Store + */ private $source_store; - /** @var ActionScheduler_Store */ + /** + * Destination store instance. + * + * @var ActionScheduler_Store + */ private $destination_store; - /** @var ActionScheduler_Logger */ + /** + * Source logger instance. + * + * @var ActionScheduler_Logger + */ private $source_logger; - /** @var ActionScheduler_Logger */ + /** + * Destination logger instance. + * + * @var ActionScheduler_Logger + */ private $destination_logger; - /** @var BatchFetcher */ + /** + * Batch fetcher instance. + * + * @var BatchFetcher + */ private $batch_fetcher; - /** @var ActionMigrator */ + /** + * Action migrator instance. + * + * @var ActionMigrator + */ private $action_migrator; - /** @var LogMigrator */ + /** + * Log migrator instance. + * + * @var LogMigrator + */ private $log_migrator; - /** @var ProgressBar */ + /** + * Progress bar instance. + * + * @var ProgressBar + */ private $progress_bar; /** @@ -70,7 +102,7 @@ public function __construct( Config $config ) { * @return int Size of batch processed. */ public function run( $batch_size = 10 ) { - $batch = $this->batch_fetcher->fetch( $batch_size ); + $batch = $this->batch_fetcher->fetch( $batch_size ); $batch_size = count( $batch ); if ( ! $batch_size ) { @@ -94,7 +126,7 @@ public function run( $batch_size = 10 ) { * @param array $action_ids List of action IDs to migrate. */ public function migrate_actions( array $action_ids ) { - do_action( 'action_scheduler/migration_batch_starting', $action_ids ); + do_action( 'action_scheduler/migration_batch_starting', $action_ids ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores \ActionScheduler::logger()->unhook_stored_action(); $this->destination_logger->unhook_stored_action(); @@ -102,14 +134,17 @@ public function migrate_actions( array $action_ids ) { foreach ( $action_ids as $source_action_id ) { $destination_action_id = $this->action_migrator->migrate( $source_action_id ); if ( $destination_action_id ) { - $this->destination_logger->log( $destination_action_id, sprintf( - /* translators: 1: source action ID 2: source store class 3: destination action ID 4: destination store class */ - __( 'Migrated action with ID %1$d in %2$s to ID %3$d in %4$s', 'action-scheduler' ), - $source_action_id, - get_class( $this->source_store ), + $this->destination_logger->log( $destination_action_id, - get_class( $this->destination_store ) - ) ); + sprintf( + /* translators: 1: source action ID 2: source store class 3: destination action ID 4: destination store class */ + __( 'Migrated action with ID %1$d in %2$s to ID %3$d in %4$s', 'action-scheduler' ), + $source_action_id, + get_class( $this->source_store ), + $destination_action_id, + get_class( $this->destination_store ) + ) + ); } if ( $this->progress_bar ) { @@ -123,7 +158,7 @@ public function migrate_actions( array $action_ids ) { \ActionScheduler::logger()->hook_stored_action(); - do_action( 'action_scheduler/migration_batch_complete', $action_ids ); + do_action( 'action_scheduler/migration_batch_complete', $action_ids ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores } /** diff --git a/lib/action-scheduler/classes/migration/Scheduler.php b/lib/action-scheduler/classes/migration/Scheduler.php index dcbe2db5..d6c320d9 100644 --- a/lib/action-scheduler/classes/migration/Scheduler.php +++ b/lib/action-scheduler/classes/migration/Scheduler.php @@ -14,10 +14,10 @@ */ class Scheduler { /** Migration action hook. */ - const HOOK = 'action_scheduler/migration_hook'; + const HOOK = 'action_scheduler/migration_hook'; /** Migration action group. */ - const GROUP = 'action-scheduler-migration'; + const GROUP = 'action-scheduler-migration'; /** * Set up the callback for the scheduled job. @@ -40,7 +40,7 @@ public function run_migration() { $migration_runner = $this->get_migration_runner(); $count = $migration_runner->run( $this->get_batch_size() ); - if ( $count === 0 ) { + if ( 0 === $count ) { $this->mark_complete(); } else { $this->schedule_migration( time() + $this->get_schedule_interval() ); @@ -54,7 +54,7 @@ public function mark_complete() { $this->unschedule_migration(); \ActionScheduler_DataController::mark_migration_complete(); - do_action( 'action_scheduler/migration_complete' ); + do_action( 'action_scheduler/migration_complete' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores } /** @@ -102,7 +102,7 @@ public function unschedule_migration() { * @return int Seconds between migration runs. Defaults to 0 seconds to allow chaining migration via Async Runners. */ private function get_schedule_interval() { - return (int) apply_filters( 'action_scheduler/migration_interval', 0 ); + return (int) apply_filters( 'action_scheduler/migration_interval', 0 ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores } /** @@ -111,7 +111,7 @@ private function get_schedule_interval() { * @return int Number of actions to migrate in each batch. Defaults to 250. */ private function get_batch_size() { - return (int) apply_filters( 'action_scheduler/migration_batch_size', 250 ); + return (int) apply_filters( 'action_scheduler/migration_batch_size', 250 ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores } /** diff --git a/lib/action-scheduler/classes/schedules/ActionScheduler_CanceledSchedule.php b/lib/action-scheduler/classes/schedules/ActionScheduler_CanceledSchedule.php index 840e482c..4f90c833 100644 --- a/lib/action-scheduler/classes/schedules/ActionScheduler_CanceledSchedule.php +++ b/lib/action-scheduler/classes/schedules/ActionScheduler_CanceledSchedule.php @@ -7,11 +7,15 @@ class ActionScheduler_CanceledSchedule extends ActionScheduler_SimpleSchedule { /** * Deprecated property @see $this->__wakeup() for details. - **/ - private $timestamp = NULL; + * + * @var null + */ + private $timestamp = null; /** - * @param DateTime $after + * Calculate when the next instance of this schedule would run based on a given date & time. + * + * @param DateTime $after Timestamp. * * @return DateTime|null */ @@ -23,7 +27,7 @@ public function calculate_next( DateTime $after ) { * Cancelled actions should never have a next schedule, even if get_next() * is called with $after < $this->scheduled_date. * - * @param DateTime $after + * @param DateTime $after Timestamp. * @return DateTime|null */ public function get_next( DateTime $after ) { @@ -31,6 +35,8 @@ public function get_next( DateTime $after ) { } /** + * Action is not recurring. + * * @return bool */ public function is_recurring() { diff --git a/lib/action-scheduler/classes/schedules/ActionScheduler_CronSchedule.php b/lib/action-scheduler/classes/schedules/ActionScheduler_CronSchedule.php index bc70c0f2..6ac43c94 100644 --- a/lib/action-scheduler/classes/schedules/ActionScheduler_CronSchedule.php +++ b/lib/action-scheduler/classes/schedules/ActionScheduler_CronSchedule.php @@ -7,23 +7,27 @@ class ActionScheduler_CronSchedule extends ActionScheduler_Abstract_RecurringSch /** * Deprecated property @see $this->__wakeup() for details. - **/ - private $start_timestamp = NULL; + * + * @var null + */ + private $start_timestamp = null; /** * Deprecated property @see $this->__wakeup() for details. - **/ - private $cron = NULL; + * + * @var null + */ + private $cron = null; /** * Wrapper for parent constructor to accept a cron expression string and map it to a CronExpression for this * objects $recurrence property. * - * @param DateTime $start The date & time to run the action at or after. If $start aligns with the CronSchedule passed via $recurrence, it will be used. If it does not align, the first matching date after it will be used. + * @param DateTime $start The date & time to run the action at or after. If $start aligns with the CronSchedule passed via $recurrence, it will be used. If it does not align, the first matching date after it will be used. * @param CronExpression|string $recurrence The CronExpression used to calculate the schedule's next instance. - * @param DateTime|null $first (Optional) The date & time the first instance of this interval schedule ran. Default null, meaning this is the first instance. + * @param DateTime|null $first (Optional) The date & time the first instance of this interval schedule ran. Default null, meaning this is the first instance. */ - public function __construct( DateTime $start, $recurrence, DateTime $first = null ) { + public function __construct( DateTime $start, $recurrence, ?DateTime $first = null ) { if ( ! is_a( $recurrence, 'CronExpression' ) ) { $recurrence = CronExpression::factory( $recurrence ); } @@ -41,7 +45,7 @@ public function __construct( DateTime $start, $recurrence, DateTime $first = nul * Calculate when an instance of this schedule would start based on a given * date & time using its the CronExpression. * - * @param DateTime $after + * @param DateTime $after Timestamp. * @return DateTime */ protected function calculate_next( DateTime $after ) { @@ -49,6 +53,8 @@ protected function calculate_next( DateTime $after ) { } /** + * Get the schedule's recurrence. + * * @return string */ public function get_recurrence() { @@ -75,10 +81,13 @@ public function __sleep() { $this->start_timestamp = $this->scheduled_timestamp; $this->cron = $this->recurrence; - return array_merge( $sleep_params, array( - 'start_timestamp', - 'cron' - ) ); + return array_merge( + $sleep_params, + array( + 'start_timestamp', + 'cron', + ) + ); } /** diff --git a/lib/action-scheduler/classes/schedules/ActionScheduler_IntervalSchedule.php b/lib/action-scheduler/classes/schedules/ActionScheduler_IntervalSchedule.php index ba4d508d..228ef4c2 100644 --- a/lib/action-scheduler/classes/schedules/ActionScheduler_IntervalSchedule.php +++ b/lib/action-scheduler/classes/schedules/ActionScheduler_IntervalSchedule.php @@ -7,19 +7,23 @@ class ActionScheduler_IntervalSchedule extends ActionScheduler_Abstract_Recurrin /** * Deprecated property @see $this->__wakeup() for details. - **/ - private $start_timestamp = NULL; + * + * @var null + */ + private $start_timestamp = null; /** * Deprecated property @see $this->__wakeup() for details. - **/ - private $interval_in_seconds = NULL; + * + * @var null + */ + private $interval_in_seconds = null; /** * Calculate when this schedule should start after a given date & time using * the number of seconds between recurrences. * - * @param DateTime $after + * @param DateTime $after Timestamp. * @return DateTime */ protected function calculate_next( DateTime $after ) { @@ -28,6 +32,8 @@ protected function calculate_next( DateTime $after ) { } /** + * Schedule interval in seconds. + * * @return int */ public function interval_in_seconds() { @@ -55,10 +61,13 @@ public function __sleep() { $this->start_timestamp = $this->scheduled_timestamp; $this->interval_in_seconds = $this->recurrence; - return array_merge( $sleep_params, array( - 'start_timestamp', - 'interval_in_seconds' - ) ); + return array_merge( + $sleep_params, + array( + 'start_timestamp', + 'interval_in_seconds', + ) + ); } /** diff --git a/lib/action-scheduler/classes/schedules/ActionScheduler_NullSchedule.php b/lib/action-scheduler/classes/schedules/ActionScheduler_NullSchedule.php index 1b1afec0..77c7c4ed 100644 --- a/lib/action-scheduler/classes/schedules/ActionScheduler_NullSchedule.php +++ b/lib/action-scheduler/classes/schedules/ActionScheduler_NullSchedule.php @@ -5,26 +5,34 @@ */ class ActionScheduler_NullSchedule extends ActionScheduler_SimpleSchedule { - /** @var DateTime|null */ + /** + * DateTime instance. + * + * @var DateTime|null + */ protected $scheduled_date; /** * Make the $date param optional and default to null. * - * @param null $date The date & time to run the action. + * @param null|DateTime $date The date & time to run the action. */ - public function __construct( DateTime $date = null ) { + public function __construct( ?DateTime $date = null ) { $this->scheduled_date = null; } /** - * This schedule has no scheduled DateTime, so we need to override the parent __sleep() + * This schedule has no scheduled DateTime, so we need to override the parent __sleep(). + * * @return array */ public function __sleep() { return array(); } + /** + * Wakeup. + */ public function __wakeup() { $this->scheduled_date = null; } diff --git a/lib/action-scheduler/classes/schedules/ActionScheduler_Schedule.php b/lib/action-scheduler/classes/schedules/ActionScheduler_Schedule.php index d61a9f7c..e3803e18 100644 --- a/lib/action-scheduler/classes/schedules/ActionScheduler_Schedule.php +++ b/lib/action-scheduler/classes/schedules/ActionScheduler_Schedule.php @@ -5,14 +5,18 @@ */ interface ActionScheduler_Schedule { /** - * @param DateTime $after + * Get the date & time this schedule was created to run, or calculate when it should be run + * after a given date & time. + * + * @param null|DateTime $after Timestamp. * @return DateTime|null */ - public function next( DateTime $after = NULL ); + public function next( ?DateTime $after = null ); /** + * Identify the schedule as (not) recurring. + * * @return bool */ public function is_recurring(); } - \ No newline at end of file diff --git a/lib/action-scheduler/classes/schedules/ActionScheduler_SimpleSchedule.php b/lib/action-scheduler/classes/schedules/ActionScheduler_SimpleSchedule.php index 454174c2..f1ce2e01 100644 --- a/lib/action-scheduler/classes/schedules/ActionScheduler_SimpleSchedule.php +++ b/lib/action-scheduler/classes/schedules/ActionScheduler_SimpleSchedule.php @@ -7,11 +7,16 @@ class ActionScheduler_SimpleSchedule extends ActionScheduler_Abstract_Schedule { /** * Deprecated property @see $this->__wakeup() for details. - **/ - private $timestamp = NULL; + * + * @var null|DateTime + */ + private $timestamp = null; /** - * @param DateTime $after + * Calculate when this schedule should start after a given date & time using + * the number of seconds between recurrences. + * + * @param DateTime $after Timestamp. * * @return DateTime|null */ @@ -20,6 +25,8 @@ public function calculate_next( DateTime $after ) { } /** + * Schedule is not recurring. + * * @return bool */ public function is_recurring() { @@ -45,9 +52,12 @@ public function __sleep() { $this->timestamp = $this->scheduled_timestamp; - return array_merge( $sleep_params, array( - 'timestamp', - ) ); + return array_merge( + $sleep_params, + array( + 'timestamp', + ) + ); } /** diff --git a/lib/action-scheduler/classes/schema/ActionScheduler_LoggerSchema.php b/lib/action-scheduler/classes/schema/ActionScheduler_LoggerSchema.php index c52d37ce..27e1c7ff 100644 --- a/lib/action-scheduler/classes/schema/ActionScheduler_LoggerSchema.php +++ b/lib/action-scheduler/classes/schema/ActionScheduler_LoggerSchema.php @@ -11,14 +11,21 @@ class ActionScheduler_LoggerSchema extends ActionScheduler_Abstract_Schema { const LOG_TABLE = 'actionscheduler_logs'; /** - * @var int Increment this value to trigger a schema update. + * Schema version. + * + * Increment this value to trigger a schema update. + * + * @var int */ protected $schema_version = 3; + /** + * Construct. + */ public function __construct() { - $this->tables = [ + $this->tables = array( self::LOG_TABLE, - ]; + ); } /** @@ -28,14 +35,18 @@ public function init() { add_action( 'action_scheduler_before_schema_update', array( $this, 'update_schema_3_0' ), 10, 2 ); } + /** + * Get table definition. + * + * @param string $table Table name. + */ protected function get_table_definition( $table ) { global $wpdb; - $table_name = $wpdb->$table; - $charset_collate = $wpdb->get_charset_collate(); + $table_name = $wpdb->$table; + $charset_collate = $wpdb->get_charset_collate(); switch ( $table ) { case self::LOG_TABLE: - $default_date = ActionScheduler_StoreSchema::DEFAULT_DATE; return "CREATE TABLE $table_name ( log_id bigint(20) unsigned NOT NULL auto_increment, diff --git a/lib/action-scheduler/classes/schema/ActionScheduler_StoreSchema.php b/lib/action-scheduler/classes/schema/ActionScheduler_StoreSchema.php index a0bd8cb2..e1d073f5 100644 --- a/lib/action-scheduler/classes/schema/ActionScheduler_StoreSchema.php +++ b/lib/action-scheduler/classes/schema/ActionScheduler_StoreSchema.php @@ -14,16 +14,23 @@ class ActionScheduler_StoreSchema extends ActionScheduler_Abstract_Schema { const DEFAULT_DATE = '0000-00-00 00:00:00'; /** - * @var int Increment this value to trigger a schema update. + * Schema version. + * + * Increment this value to trigger a schema update. + * + * @var int */ - protected $schema_version = 7; + protected $schema_version = 8; + /** + * Construct. + */ public function __construct() { - $this->tables = [ + $this->tables = array( self::ACTIONS_TABLE, self::CLAIMS_TABLE, self::GROUPS_TABLE, - ]; + ); } /** @@ -33,17 +40,24 @@ public function init() { add_action( 'action_scheduler_before_schema_update', array( $this, 'update_schema_5_0' ), 10, 2 ); } + /** + * Get table definition. + * + * @param string $table Table name. + */ protected function get_table_definition( $table ) { global $wpdb; - $table_name = $wpdb->$table; - $charset_collate = $wpdb->get_charset_collate(); + $table_name = $wpdb->$table; + $charset_collate = $wpdb->get_charset_collate(); + $default_date = self::DEFAULT_DATE; + // phpcs:ignore Squiz.PHP.CommentedOutCode $max_index_length = 191; // @see wp_get_db_schema() + $hook_status_scheduled_date_gmt_max_index_length = $max_index_length - 20 - 8; // - status, - scheduled_date_gmt - $default_date = self::DEFAULT_DATE; + switch ( $table ) { case self::ACTIONS_TABLE: - return "CREATE TABLE {$table_name} ( action_id bigint(20) unsigned NOT NULL auto_increment, hook varchar(191) NOT NULL, @@ -66,11 +80,12 @@ protected function get_table_definition( $table ) { KEY args (args($max_index_length)), KEY group_id (group_id), KEY last_attempt_gmt (last_attempt_gmt), - KEY `claim_id_status_scheduled_date_gmt` (`claim_id`, `status`, `scheduled_date_gmt`) + KEY `claim_id_status_priority_scheduled_date_gmt` (`claim_id`,`status`,`priority`,`scheduled_date_gmt`), + KEY `status_last_attempt_gmt` (`status`,`last_attempt_gmt`), + KEY `status_claim_id` (`status`,`claim_id`) ) $charset_collate"; case self::CLAIMS_TABLE: - return "CREATE TABLE {$table_name} ( claim_id bigint(20) unsigned NOT NULL auto_increment, date_created_gmt datetime NULL default '{$default_date}', @@ -79,7 +94,6 @@ protected function get_table_definition( $table ) { ) $charset_collate"; case self::GROUPS_TABLE: - return "CREATE TABLE {$table_name} ( group_id bigint(20) unsigned NOT NULL auto_increment, slug varchar(255) NOT NULL, diff --git a/lib/action-scheduler/deprecated/ActionScheduler_Abstract_QueueRunner_Deprecated.php b/lib/action-scheduler/deprecated/ActionScheduler_Abstract_QueueRunner_Deprecated.php index dac17aa4..e24ddca1 100644 --- a/lib/action-scheduler/deprecated/ActionScheduler_Abstract_QueueRunner_Deprecated.php +++ b/lib/action-scheduler/deprecated/ActionScheduler_Abstract_QueueRunner_Deprecated.php @@ -16,7 +16,7 @@ protected function get_maximum_execution_time() { $maximum_execution_time = 30; - // Apply deprecated filter + // Apply deprecated filter. if ( has_filter( 'action_scheduler_maximum_execution_time' ) ) { _deprecated_function( 'action_scheduler_maximum_execution_time', '2.1.1', 'action_scheduler_queue_runner_time_limit' ); $maximum_execution_time = apply_filters( 'action_scheduler_maximum_execution_time', $maximum_execution_time ); diff --git a/lib/action-scheduler/deprecated/ActionScheduler_AdminView_Deprecated.php b/lib/action-scheduler/deprecated/ActionScheduler_AdminView_Deprecated.php index 62d964e0..46935ec6 100644 --- a/lib/action-scheduler/deprecated/ActionScheduler_AdminView_Deprecated.php +++ b/lib/action-scheduler/deprecated/ActionScheduler_AdminView_Deprecated.php @@ -10,6 +10,11 @@ */ class ActionScheduler_AdminView_Deprecated { + /** + * Adjust parameters for custom post type. + * + * @param array $args Args. + */ public function action_scheduler_post_type_args( $args ) { _deprecated_function( __METHOD__, '2.0.0' ); return $args; @@ -68,7 +73,7 @@ public static function list_table_sortable_columns( $columns ) { * Print the content for our custom columns. * * @param string $column_name The key for the column for which we should output our content. - * @param int $post_id The ID of the 'scheduled-action' post for which this row relates. + * @param int $post_id The ID of the 'scheduled-action' post for which this row relates. */ public static function list_table_column_content( $column_name, $post_id ) { _deprecated_function( __METHOD__, '2.0.0' ); @@ -79,7 +84,8 @@ public static function list_table_column_content( $column_name, $post_id ) { * * Hooked to the 'post_row_actions' filter. * - * @param array $actions An associative array of actions which can be performed on the 'scheduled-action' post type. + * @param array $actions An associative array of actions which can be performed on the 'scheduled-action' post type. + * @param WP_Post $post The 'scheduled-action' post object. * @return array $actions An associative array of actions which can be performed on the 'scheduled-action' post type. */ public static function row_actions( $actions, $post ) { @@ -105,8 +111,7 @@ public static function maybe_execute_action() { * * Based on Crontrol::interval() function by Edward Dale: https://wordpress.org/plugins/wp-crontrol/ * - * @param int $interval A interval in seconds. - * @return string A human friendly string representation of the interval. + * @return void */ public static function admin_notices() { _deprecated_function( __METHOD__, '2.0.0' ); @@ -115,20 +120,20 @@ public static function admin_notices() { /** * Filter search queries to allow searching by Claim ID (i.e. post_password). * - * @param string $orderby MySQL orderby string. - * @param WP_Query $query Instance of a WP_Query object - * @return string MySQL orderby string. + * @param string $orderby MySQL orderby string. + * @param WP_Query $query Instance of a WP_Query object. + * @return void */ - public function custom_orderby( $orderby, $query ){ + public function custom_orderby( $orderby, $query ) { _deprecated_function( __METHOD__, '2.0.0' ); } /** * Filter search queries to allow searching by Claim ID (i.e. post_password). * - * @param string $search MySQL search string. - * @param WP_Query $query Instance of a WP_Query object - * @return string MySQL search string. + * @param string $search MySQL search string. + * @param WP_Query $query Instance of a WP_Query object. + * @return void */ public function search_post_password( $search, $query ) { _deprecated_function( __METHOD__, '2.0.0' ); @@ -137,11 +142,12 @@ public function search_post_password( $search, $query ) { /** * Change messages when a scheduled action is updated. * - * @param array $messages + * @param array $messages Messages. * @return array */ public function post_updated_messages( $messages ) { _deprecated_function( __METHOD__, '2.0.0' ); return $messages; } -} \ No newline at end of file + +} diff --git a/lib/action-scheduler/deprecated/ActionScheduler_Schedule_Deprecated.php b/lib/action-scheduler/deprecated/ActionScheduler_Schedule_Deprecated.php index 496d67b8..af200bca 100644 --- a/lib/action-scheduler/deprecated/ActionScheduler_Schedule_Deprecated.php +++ b/lib/action-scheduler/deprecated/ActionScheduler_Schedule_Deprecated.php @@ -13,7 +13,7 @@ abstract class ActionScheduler_Schedule_Deprecated implements ActionScheduler_Sc * * @return DateTime|null */ - public function next( DateTime $after = null ) { + public function next( ?DateTime $after = null ) { if ( empty( $after ) ) { $return_value = $this->get_date(); $replacement_method = 'get_date()'; diff --git a/lib/action-scheduler/deprecated/ActionScheduler_Store_Deprecated.php b/lib/action-scheduler/deprecated/ActionScheduler_Store_Deprecated.php index 002dc75b..02dc8b7c 100644 --- a/lib/action-scheduler/deprecated/ActionScheduler_Store_Deprecated.php +++ b/lib/action-scheduler/deprecated/ActionScheduler_Store_Deprecated.php @@ -2,6 +2,7 @@ /** * Class ActionScheduler_Store_Deprecated + * * @codeCoverageIgnore */ abstract class ActionScheduler_Store_Deprecated { diff --git a/lib/action-scheduler/deprecated/functions.php b/lib/action-scheduler/deprecated/functions.php index f782c4b7..76ac0195 100644 --- a/lib/action-scheduler/deprecated/functions.php +++ b/lib/action-scheduler/deprecated/functions.php @@ -1,20 +1,21 @@ '' - the name of the action that will be triggered * 'args' => NULL - the args array that will be passed with the action * 'date' => NULL - the scheduled date of the action. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone. @@ -113,8 +116,8 @@ function wc_next_scheduled_action( $hook, $args = NULL, $group = '' ) { * 'per_page' => 5 - Number of results to return * 'offset' => 0 * 'orderby' => 'date' - accepted values are 'hook', 'group', 'modified', or 'date' - * 'order' => 'ASC' - * @param string $return_format OBJECT, ARRAY_A, or ids + * 'order' => 'ASC'. + * @param string $return_format OBJECT, ARRAY_A, or ids. * * @deprecated 2.1.0 * diff --git a/lib/action-scheduler/functions.php b/lib/action-scheduler/functions.php index 66bc54ff..d78c23ab 100644 --- a/lib/action-scheduler/functions.php +++ b/lib/action-scheduler/functions.php @@ -35,8 +35,9 @@ function as_enqueue_async_action( $hook, $args = array(), $group = '', $unique = * @param array $args Action arguments. * @param string $group Action group. * @param int $priority Action priority. + * @param bool $unique Unique action. */ - $pre = apply_filters( 'pre_as_enqueue_async_action', null, $hook, $args, $group, $priority ); + $pre = apply_filters( 'pre_as_enqueue_async_action', null, $hook, $args, $group, $priority, $unique ); if ( null !== $pre ) { return is_int( $pre ) ? $pre : 0; } @@ -85,8 +86,9 @@ function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = * @param array $args Action arguments. * @param string $group Action group. * @param int $priorities Action priority. + * @param bool $unique Unique action. */ - $pre = apply_filters( 'pre_as_schedule_single_action', null, $timestamp, $hook, $args, $group, $priority ); + $pre = apply_filters( 'pre_as_schedule_single_action', null, $timestamp, $hook, $args, $group, $priority, $unique ); if ( null !== $pre ) { return is_int( $pre ) ? $pre : 0; } @@ -126,6 +128,7 @@ function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, // We expect an integer and allow it to be passed using float and string types, but otherwise // should reject unexpected values. + // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison if ( ! is_numeric( $interval_in_seconds ) || $interval_in_seconds != $interval ) { _doing_it_wrong( __METHOD__, @@ -157,8 +160,9 @@ function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, * @param array $args Action arguments. * @param string $group Action group. * @param int $priority Action priority. + * @param bool $unique Unique action. */ - $pre = apply_filters( 'pre_as_schedule_recurring_action', null, $timestamp, $interval_in_seconds, $hook, $args, $group, $priority ); + $pre = apply_filters( 'pre_as_schedule_recurring_action', null, $timestamp, $interval_in_seconds, $hook, $args, $group, $priority, $unique ); if ( null !== $pre ) { return is_int( $pre ) ? $pre : 0; } @@ -223,8 +227,9 @@ function as_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(), * @param array $args Action arguments. * @param string $group Action group. * @param int $priority Action priority. + * @param bool $unique Unique action. */ - $pre = apply_filters( 'pre_as_schedule_cron_action', null, $timestamp, $schedule, $hook, $args, $group, $priority ); + $pre = apply_filters( 'pre_as_schedule_cron_action', null, $timestamp, $schedule, $hook, $args, $group, $priority, $unique ); if ( null !== $pre ) { return is_int( $pre ) ? $pre : 0; } @@ -455,7 +460,7 @@ function as_get_scheduled_actions( $args = array(), $return_format = OBJECT ) { $actions[ $action_id ] = $store->fetch_action( $action_id ); } - if ( ARRAY_A == $return_format ) { + if ( ARRAY_A === $return_format ) { foreach ( $actions as $action_id => $action_object ) { $actions[ $action_id ] = get_object_vars( $action_object ); } @@ -491,3 +496,18 @@ function as_get_datetime_object( $date_string = null, $timezone = 'UTC' ) { } return $date; } + +/** + * Check if a specific feature is supported by the current version of Action Scheduler. + * + * @since 3.9.3 + * + * @param string $feature The feature to check support for. + * + * @return bool True if the feature is supported, false otherwise. + */ +function as_supports( string $feature ): bool { + $supported_features = array( 'ensure_recurring_actions_hook' ); + + return in_array( $feature, $supported_features, true ); +} diff --git a/lib/action-scheduler/lib/WP_Async_Request.php b/lib/action-scheduler/lib/WP_Async_Request.php index ff5e29b3..a40c80a1 100644 --- a/lib/action-scheduler/lib/WP_Async_Request.php +++ b/lib/action-scheduler/lib/WP_Async_Request.php @@ -4,6 +4,7 @@ * * @package WP-Background-Processing */ + /* Library URI: https://github.com/deliciousbrains/wp-background-processing/blob/fbbc56f2480910d7959972ec9ec0819a13c6150a/classes/wp-async-request.php Author: Delicious Brains Inc. @@ -27,7 +28,6 @@ abstract class WP_Async_Request { * (default value: 'wp') * * @var string - * @access protected */ protected $prefix = 'wp'; @@ -37,7 +37,6 @@ abstract class WP_Async_Request { * (default value: 'async_request') * * @var string - * @access protected */ protected $action = 'async_request'; @@ -45,7 +44,6 @@ abstract class WP_Async_Request { * Identifier * * @var mixed - * @access protected */ protected $identifier; @@ -55,7 +53,6 @@ abstract class WP_Async_Request { * (default value: array()) * * @var array - * @access protected */ protected $data = array(); @@ -169,7 +166,7 @@ protected function get_post_args() { * Check for correct nonce and pass to handler. */ public function maybe_handle() { - // Don't lock up other requests while processing + // Don't lock up other requests while processing. session_write_close(); check_ajax_referer( $this->identifier, 'nonce' ); diff --git a/lib/action-scheduler/lib/cron-expression/CronExpression.php b/lib/action-scheduler/lib/cron-expression/CronExpression.php index 7f33c378..33040edd 100644 --- a/lib/action-scheduler/lib/cron-expression/CronExpression.php +++ b/lib/action-scheduler/lib/cron-expression/CronExpression.php @@ -54,7 +54,7 @@ class CronExpression * * @return CronExpression */ - public static function factory($expression, CronExpression_FieldFactory $fieldFactory = null) + public static function factory($expression, ?CronExpression_FieldFactory $fieldFactory = null) { $mappings = array( '@yearly' => '0 0 1 1 *', diff --git a/lib/action-scheduler/readme.txt b/lib/action-scheduler/readme.txt index d2ee033b..f4b96bb2 100644 --- a/lib/action-scheduler/readme.txt +++ b/lib/action-scheduler/readme.txt @@ -1,11 +1,11 @@ === Action Scheduler === Contributors: Automattic, wpmuguru, claudiosanches, peterfabian1000, vedjain, jamosova, obliviousharmony, konamiman, sadowski, royho, barryhughes-1 Tags: scheduler, cron -Stable tag: 3.8.1 +Stable tag: 3.9.2 License: GPLv3 -Requires at least: 6.3 -Tested up to: 6.5 -Requires PHP: 7.0 +Requires at least: 6.5 +Tested up to: 6.7 +Requires PHP: 7.2 Action Scheduler - Job Queue for WordPress @@ -29,7 +29,7 @@ If your plugin needs background processing, especially of large sets of tasks, A ## Learn More -To learn more about how to Action Scheduler works, and how to use it in your plugin, check out the docs on [ActionScheduler.org](https://actionscheduler.org). +To learn more about how Action Scheduler works, and how to use it in your plugin, check out the docs on [ActionScheduler.org](https://actionscheduler.org). There you will find: @@ -47,6 +47,30 @@ Collaboration is cool. We'd love to work with you to improve Action Scheduler. [ == Changelog == += 3.9.2 - 2025-02-03 = +* Fixed fatal errors by moving version info methods to a new class and deprecating conflicting ones in ActionScheduler_Versions + += 3.9.1 - 2025-01-21 = +* A number of new WP CLI commands have been added, making it easier to manage actions in the terminal and from scripts. +* New wp action-scheduler source command to help determine how Action Scheduler is being loaded. +* Additional information about the active instance of Action Scheduler is now available in the Help pull-down drawer. +* Make some other nullable parameters explicitly nullable. +* Set option value to `no` rather than deleting. + += 3.9.0 - 2024-11-14 = +* Minimum required version of PHP is now 7.1. +* Performance improvements for the `as_pending_actions_due()` function. +* Existing filter hook `action_scheduler_claim_actions_order_by` enhanced to provide callbacks with additional information. +* Improved compatibility with PHP 8.4, specifically by making implicitly nullable parameters explicitly nullable. +* A large number of coding standards-enhancements, to help reduce friction when submitting plugins to marketplaces and plugin directories. Special props @crstauf for this effort. +* Minor documentation tweaks and improvements. + += 3.8.2 - 2024-09-12 = +* Add missing parameter to the `pre_as_enqueue_async_action` hook. +* Bump minimum PHP version to 7.0. +* Bump minimum WordPress version to 6.4. +* Make the batch size adjustable during processing. + = 3.8.1 - 2024-06-20 = * Fix typos. * Improve the messaging in our unidentified action exceptions. diff --git a/lib/action-scheduler/vendor/autoload.php b/lib/action-scheduler/vendor/autoload.php deleted file mode 100644 index 888fa0a6..00000000 --- a/lib/action-scheduler/vendor/autoload.php +++ /dev/null @@ -1,12 +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\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 ?string */ - private $vendorDir; - - // PSR-4 - /** - * @var array[] - * @psalm-var array> - */ - private $prefixLengthsPsr4 = array(); - /** - * @var array[] - * @psalm-var array> - */ - private $prefixDirsPsr4 = array(); - /** - * @var array[] - * @psalm-var array - */ - private $fallbackDirsPsr4 = array(); - - // PSR-0 - /** - * @var array[] - * @psalm-var array> - */ - private $prefixesPsr0 = array(); - /** - * @var array[] - * @psalm-var array - */ - private $fallbackDirsPsr0 = array(); - - /** @var bool */ - private $useIncludePath = false; - - /** - * @var string[] - * @psalm-var array - */ - private $classMap = array(); - - /** @var bool */ - private $classMapAuthoritative = false; - - /** - * @var bool[] - * @psalm-var array - */ - private $missingClasses = array(); - - /** @var ?string */ - private $apcuPrefix; - - /** - * @var self[] - */ - private static $registeredLoaders = array(); - - /** - * @param ?string $vendorDir - */ - public function __construct($vendorDir = null) - { - $this->vendorDir = $vendorDir; - } - - /** - * @return string[] - */ - public function getPrefixes() - { - if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); - } - - return array(); - } - - /** - * @return array[] - * @psalm-return array> - */ - public function getPrefixesPsr4() - { - return $this->prefixDirsPsr4; - } - - /** - * @return array[] - * @psalm-return array - */ - public function getFallbackDirs() - { - return $this->fallbackDirsPsr0; - } - - /** - * @return array[] - * @psalm-return array - */ - public function getFallbackDirsPsr4() - { - return $this->fallbackDirsPsr4; - } - - /** - * @return string[] Array of classname => path - * @psalm-return array - */ - public function getClassMap() - { - return $this->classMap; - } - - /** - * @param string[] $classMap Class to filename map - * @psalm-param array $classMap - * - * @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 string[]|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories - * - * @return void - */ - public function add($prefix, $paths, $prepend = false) - { - if (!$prefix) { - if ($prepend) { - $this->fallbackDirsPsr0 = array_merge( - (array) $paths, - $this->fallbackDirsPsr0 - ); - } else { - $this->fallbackDirsPsr0 = array_merge( - $this->fallbackDirsPsr0, - (array) $paths - ); - } - - return; - } - - $first = $prefix[0]; - if (!isset($this->prefixesPsr0[$first][$prefix])) { - $this->prefixesPsr0[$first][$prefix] = (array) $paths; - - return; - } - if ($prepend) { - $this->prefixesPsr0[$first][$prefix] = array_merge( - (array) $paths, - $this->prefixesPsr0[$first][$prefix] - ); - } else { - $this->prefixesPsr0[$first][$prefix] = array_merge( - $this->prefixesPsr0[$first][$prefix], - (array) $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 string[]|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) - { - if (!$prefix) { - // Register directories for the root namespace. - if ($prepend) { - $this->fallbackDirsPsr4 = array_merge( - (array) $paths, - $this->fallbackDirsPsr4 - ); - } else { - $this->fallbackDirsPsr4 = array_merge( - $this->fallbackDirsPsr4, - (array) $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] = (array) $paths; - } elseif ($prepend) { - // Prepend directories for an already registered namespace. - $this->prefixDirsPsr4[$prefix] = array_merge( - (array) $paths, - $this->prefixDirsPsr4[$prefix] - ); - } else { - // Append directories for an already registered namespace. - $this->prefixDirsPsr4[$prefix] = array_merge( - $this->prefixDirsPsr4[$prefix], - (array) $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 string[]|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 string[]|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($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 indexed by their corresponding vendor directories. - * - * @return self[] - */ - 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; - } -} - -/** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - * @private - */ -function includeFile($file) -{ - include $file; -} diff --git a/lib/action-scheduler/vendor/composer/InstalledVersions.php b/lib/action-scheduler/vendor/composer/InstalledVersions.php deleted file mode 100644 index c6b54af7..00000000 --- a/lib/action-scheduler/vendor/composer/InstalledVersions.php +++ /dev/null @@ -1,352 +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 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|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 || empty($installed['versions'][$packageName]['dev_requirement']); - } - } - - 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($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(); - } - - /** - * @return array[] - * @psalm-return list}> - */ - private static function getInstalled() - { - if (null === self::$canGetVendors) { - self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); - } - - $installed = array(); - - if (self::$canGetVendors) { - foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { - if (isset(self::$installedByVendor[$vendorDir])) { - $installed[] = self::$installedByVendor[$vendorDir]; - } elseif (is_file($vendorDir.'/composer/installed.php')) { - $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; - if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { - self::$installed = $installed[count($installed) - 1]; - } - } - } - } - - 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 = require __DIR__ . '/installed.php'; - } else { - self::$installed = array(); - } - } - $installed[] = self::$installed; - - return $installed; - } -} diff --git a/lib/action-scheduler/vendor/composer/LICENSE b/lib/action-scheduler/vendor/composer/LICENSE deleted file mode 100644 index f27399a0..00000000 --- a/lib/action-scheduler/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/lib/action-scheduler/vendor/composer/autoload_classmap.php b/lib/action-scheduler/vendor/composer/autoload_classmap.php deleted file mode 100644 index 0fb0a2c1..00000000 --- a/lib/action-scheduler/vendor/composer/autoload_classmap.php +++ /dev/null @@ -1,10 +0,0 @@ - $vendorDir . '/composer/InstalledVersions.php', -); diff --git a/lib/action-scheduler/vendor/composer/autoload_namespaces.php b/lib/action-scheduler/vendor/composer/autoload_namespaces.php deleted file mode 100644 index 15a2ff3a..00000000 --- a/lib/action-scheduler/vendor/composer/autoload_namespaces.php +++ /dev/null @@ -1,9 +0,0 @@ -register(true); - - return $loader; - } -} diff --git a/lib/action-scheduler/vendor/composer/autoload_static.php b/lib/action-scheduler/vendor/composer/autoload_static.php deleted file mode 100644 index bd2d8a7d..00000000 --- a/lib/action-scheduler/vendor/composer/autoload_static.php +++ /dev/null @@ -1,20 +0,0 @@ - __DIR__ . '/..' . '/composer/InstalledVersions.php', - ); - - public static function getInitializer(ClassLoader $loader) - { - return \Closure::bind(function () use ($loader) { - $loader->classMap = ComposerStaticInit0ed398af1bd918658290810d69e2ba71::$classMap; - - }, null, ClassLoader::class); - } -} diff --git a/lib/action-scheduler/vendor/composer/installed.json b/lib/action-scheduler/vendor/composer/installed.json deleted file mode 100644 index f20a6c47..00000000 --- a/lib/action-scheduler/vendor/composer/installed.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "packages": [], - "dev": false, - "dev-package-names": [] -} diff --git a/lib/action-scheduler/vendor/composer/installed.php b/lib/action-scheduler/vendor/composer/installed.php deleted file mode 100644 index d43788d8..00000000 --- a/lib/action-scheduler/vendor/composer/installed.php +++ /dev/null @@ -1,23 +0,0 @@ - array( - 'name' => 'woocommerce/action-scheduler', - 'pretty_version' => 'dev-release/3.8.1', - 'version' => 'dev-release/3.8.1', - 'reference' => 'e331b534d7de10402d7545a0de50177b874c0779', - 'type' => 'wordpress-plugin', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev' => false, - ), - 'versions' => array( - 'woocommerce/action-scheduler' => array( - 'pretty_version' => 'dev-release/3.8.1', - 'version' => 'dev-release/3.8.1', - 'reference' => 'e331b534d7de10402d7545a0de50177b874c0779', - 'type' => 'wordpress-plugin', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev_requirement' => false, - ), - ), -); diff --git a/lib/action-scheduler/vendor/composer/platform_check.php b/lib/action-scheduler/vendor/composer/platform_check.php deleted file mode 100644 index 8b379f44..00000000 --- a/lib/action-scheduler/vendor/composer/platform_check.php +++ /dev/null @@ -1,26 +0,0 @@ -= 50600)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.'; -} - -if ($issues) { - if (!headers_sent()) { - header('HTTP/1.1 500 Internal Server Error'); - } - if (!ini_get('display_errors')) { - if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { - fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); - } elseif (!headers_sent()) { - echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; - } - } - trigger_error( - 'Composer detected issues in your platform: ' . implode(' ', $issues), - E_USER_ERROR - ); -} diff --git a/package-lock.json b/package-lock.json index 1f7b153e..844e2976 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5265,8 +5265,7 @@ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", "dev": true, - "funding": [ - { + "funding": [{ "type": "opencollective", "url": "https://opencollective.com/postcss/" }, @@ -5515,8 +5514,7 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, - "funding": [ - { + "funding": [{ "type": "github", "url": "https://github.com/sponsors/feross" }, @@ -5676,8 +5674,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, - "funding": [ - { + "funding": [{ "type": "opencollective", "url": "https://opencollective.com/browserslist" }, @@ -5717,8 +5714,7 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, - "funding": [ - { + "funding": [{ "type": "github", "url": "https://github.com/sponsors/feross" }, @@ -5884,8 +5880,7 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001558.tgz", "integrity": "sha512-/Et7DwLqpjS47JPEcz6VnxU9PwcIdVi0ciLXRWBQdj1XFye68pSQYpV0QtPTfUKWuOaEig+/Vez2l74eDc1tPQ==", "dev": true, - "funding": [ - { + "funding": [{ "type": "opencollective", "url": "https://opencollective.com/browserslist" }, @@ -5962,12 +5957,10 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "funding": [{ + "type": "individual", + "url": "https://paulmillr.com/funding/" + }], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -6016,12 +6009,10 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], + "funding": [{ + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + }], "engines": { "node": ">=8" } @@ -7126,12 +7117,10 @@ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] + "funding": [{ + "type": "github", + "url": "https://github.com/sponsors/fb55" + }] }, "node_modules/domexception": { "version": "2.0.1", @@ -8631,12 +8620,10 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "funding": [{ + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + }], "engines": { "node": ">=4.0" }, @@ -9261,8 +9248,7 @@ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", "dev": true, - "funding": [ - { + "funding": [{ "type": "github", "url": "https://github.com/sponsors/mdevils" }, @@ -9421,8 +9407,7 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, - "funding": [ - { + "funding": [{ "type": "github", "url": "https://github.com/sponsors/feross" }, @@ -11934,12 +11919,10 @@ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "funding": [{ + "type": "github", + "url": "https://github.com/sponsors/ai" + }], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -13013,8 +12996,7 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, - "funding": [ - { + "funding": [{ "type": "opencollective", "url": "https://opencollective.com/postcss/" }, @@ -13728,8 +13710,7 @@ "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", "dev": true, - "funding": [ - { + "funding": [{ "type": "opencollective", "url": "https://opencollective.com/postcss/" }, @@ -14060,8 +14041,7 @@ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, - "funding": [ - { + "funding": [{ "type": "github", "url": "https://github.com/sponsors/feross" }, @@ -14727,8 +14707,7 @@ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, - "funding": [ - { + "funding": [{ "type": "github", "url": "https://github.com/sponsors/feross" }, @@ -14783,8 +14762,7 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, - "funding": [ - { + "funding": [{ "type": "github", "url": "https://github.com/sponsors/feross" }, @@ -16544,8 +16522,7 @@ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, - "funding": [ - { + "funding": [{ "type": "opencollective", "url": "https://opencollective.com/browserslist" }, @@ -30417,4 +30394,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/templates/admin/reports/link-details.php b/templates/admin/reports/link-details.php index 44a98f4d..2a25b99b 100644 --- a/templates/admin/reports/link-details.php +++ b/templates/admin/reports/link-details.php @@ -49,7 +49,7 @@ if ( Link::PROCESS_NEW === $wlf_archive_process ) { esc_html_e( 'NEW - This link has been queued and will be processed by the Internet Archive as soon as possible', 'wpcomsp_wayback_link_fixer' ); } else { - esc_html_e( 'PENDING - Currently being processed by the Internet Archive. This may take a few minutes to complete', 'wpcomsp_wayback_link_fixer' ); + esc_html_e( 'PENDING – Queued for submission to the Internet Archive. Processing time varies based on queue size.', 'wpcomsp_wayback_link_fixer' ); } } elseif ( '' !== $wlf_link->get_archived_href() ) { printf( diff --git a/tests/Event/Test_Scan_Own_Posts_Event.php b/tests/Event/Test_Scan_Own_Posts_Event.php index 1f656e9e..b7564209 100644 --- a/tests/Event/Test_Scan_Own_Posts_Event.php +++ b/tests/Event/Test_Scan_Own_Posts_Event.php @@ -41,7 +41,12 @@ public function set_up(): void { $this->clear_clients(); // Get all existing posts. - $all = \get_posts( array( 'post_type' => 'any', 'posts_per_page' => -1 ) ); + $all = \get_posts( + array( + 'post_type' => 'any', + 'posts_per_page' => -1, + ) + ); // Iterate through all posts and remove them. foreach ( $all as $post ) { \wp_delete_post( $post->ID, true ); @@ -50,27 +55,6 @@ public function set_up(): void { parent::set_up(); } - /** - * @testdox When init is run, the event to check own should be added if we allow scanning own posts and scanning at defined intervals. - * @runInSeparateProcess - * @preserveGlobalState disabled - * @return void - */ - public function test_add_own_posts_to_action_scheduler(): void { - // Allow scanning own posts. - \add_filter( 'wlf_own_content_allow_post', '__return_true' ); - // Allow scanning at defined intervals. - \add_filter( 'wlf_routinely_update_wayback_machine', '__return_true' ); - - // Run init. - \do_action( 'init' ); - - // Check that the event has been added to the action scheduler. - $events = $GLOBALS['wpdb']->get_results( "SELECT * FROM {$GLOBALS['wpdb']->prefix}actionscheduler_actions WHERE hook='wlf_scan_existing_posts'" ); - $this->assertCount( 1, $events ); - $this->assertSame( 'pending', $events[0]->status ); - } - /** * @testdox If dont allow the adding of own events, the event should not be added to the action scheduler. * @@ -116,7 +100,6 @@ public function test_dont_allow_own_posts_to_action_scheduler_via_intervals(): v */ public function test_set_allowed_post_types(): void { - // Allow with the filters. \add_filter( 'wlf_own_content_allow_post', '__return_true' ); \add_filter( 'wlf_routinely_update_wayback_machine', '__return_true' ); @@ -133,8 +116,6 @@ function () { $post_id = $this->factory->post->create( array( 'post_type' => 'post' ) ); $page_id = $this->factory->post->create( array( 'post_type' => 'page' ) ); - - // Run the event. $event = new Scan_Own_Posts_Event(); $event(); @@ -146,7 +127,7 @@ function () { $this->assertCount( 1, $actions ); // Check that the action is for the post. - $this->assertSame($post_id, json_decode($actions[0]->args)->post_id); + $this->assertSame( $post_id, json_decode( $actions[0]->args )->post_id ); } /** @@ -161,7 +142,6 @@ public function test_only_get_posts_that_have_not_been_checked_in_last_24_hours( // Set the interval to 24 hours. \add_filter( 'wlf_routinely_update_wayback_machine_interval', fn() => 1 ); - // Create 2 posts. $post_id_1 = $this->factory->post->create( array( 'post_type' => 'post' ) ); $post_id_2 = $this->factory->post->create( array( 'post_type' => 'post' ) ); @@ -169,8 +149,8 @@ public function test_only_get_posts_that_have_not_been_checked_in_last_24_hours( $time_1 = time() - 2 * DAY_IN_SECONDS; $time_2 = time() - 1 * HOUR_IN_SECONDS; - update_post_meta($post_id_1, Settings::OWN_LINK_LAST_PROCESSED, $time_1); - update_post_meta($post_id_2, Settings::OWN_LINK_LAST_PROCESSED, $time_2); + update_post_meta( $post_id_1, Settings::OWN_LINK_LAST_PROCESSED, $time_1 ); + update_post_meta( $post_id_2, Settings::OWN_LINK_LAST_PROCESSED, $time_2 ); // Run the event. $event = new Scan_Own_Posts_Event(); @@ -183,6 +163,6 @@ public function test_only_get_posts_that_have_not_been_checked_in_last_24_hours( $this->assertCount( 1, $actions ); // Check that the action is for the post 1. - $this->assertSame($post_id_1, json_decode($actions[0]->args)->post_id); + $this->assertSame( $post_id_1, json_decode( $actions[0]->args )->post_id ); } } diff --git a/tests/wp-config.php b/tests/wp-config.php index 6b67d7fa..212ba452 100644 --- a/tests/wp-config.php +++ b/tests/wp-config.php @@ -1,7 +1,7 @@