diff --git a/.ddev/config.yaml b/.ddev/config.yaml index d65593b7c..5686c7920 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -1,7 +1,7 @@ name: woocommerce-paypal-payments type: php docroot: .ddev/wordpress -php_version: "7.2" +php_version: "7.4" webserver_type: apache-fpm router_http_port: "80" router_https_port: "443" @@ -18,7 +18,7 @@ hooks: pre-start: - exec-host: "mkdir -p .ddev/wordpress/wp-content/plugins/${DDEV_PROJECT}" web_environment: - - WP_VERSION=6.2.2 + - WP_VERSION=6.3.3 - WP_LOCALE=en_US - WP_TITLE=WooCommerce PayPal Payments - WP_MULTISITE=true diff --git a/.distignore b/.distignore new file mode 100644 index 000000000..614705af1 --- /dev/null +++ b/.distignore @@ -0,0 +1,34 @@ +.distignore +.editorconfig +.gitattributes +.gitignore +.eslintrc +.ddev +.git* +.phpstorm* +.idea +*.env* +.psalm* +tests +*.xml* +.phpunit.result.cache +babel.config.json +node_modules +modules/*/resources/css +modules/*/resources/js/**/*.js +*.lock +webpack.config.js +wp-cli.yml +ngrok.yml +composer.json +package.json +package-lock.json +.composer_compiled_assets +assets-compiler.json +patchwork.json +.babelrc +README.md +wordpress_org_assets +.DS_Store +auth.json +*.log diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index e78269a31..0dfb99455 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -7,8 +7,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['7.3', '7.4', '8.2'] - wc-versions: ['5.9.5', '7.7.2'] + php-versions: ['7.4', '8.2'] + wc-versions: ['6.9.4', '7.7.2'] name: PHP ${{ matrix.php-versions }} WC ${{ matrix.wc-versions }} steps: diff --git a/.github/workflows/package-new.yml b/.github/workflows/package-new.yml new file mode 100644 index 000000000..3d2125420 --- /dev/null +++ b/.github/workflows/package-new.yml @@ -0,0 +1,37 @@ +name: Build package (New) + +on: + workflow_dispatch: + inputs: + packageVersion: + description: 'Package version' + required: false + type: string + +jobs: + check_version: + runs-on: ubuntu-latest + env: + PACKAGE_VERSION: ${{ github.event.inputs.packageVersion }} + outputs: + version: ${{ env.PACKAGE_VERSION }} + name: Check version + steps: + - uses: actions/checkout@v4 + - name: Fix plugin version input # Add the version number if only suffix entered + run: echo "PACKAGE_VERSION=$(sed -nE '/Version:/s/.* ([0-9.]+).*/\1/p' woocommerce-paypal-payments.php)-$PACKAGE_VERSION" >> $GITHUB_ENV + if: env.PACKAGE_VERSION && !contains(env.PACKAGE_VERSION, '.') + - name: Fill plugin version # If the version number was not set, retrieve it from the file + run: echo "PACKAGE_VERSION=$(sed -nE '/Version:/s/.* ([0-9.]+).*/\1/p' woocommerce-paypal-payments.php)" >> $GITHUB_ENV + if: "!env.PACKAGE_VERSION" + + create_archive: + needs: check_version + uses: inpsyde/reusable-workflows/.github/workflows/build-plugin-archive.yml@main + with: + PHP_VERSION: 7.4 + PLUGIN_MAIN_FILE: ./woocommerce-paypal-payments.php + PLUGIN_VERSION: ${{ needs.check_version.outputs.version }} + PLUGIN_FOLDER_NAME: woocommerce-paypal-payments + ARCHIVE_NAME: woocommerce-paypal-payments-${{ needs.check_version.outputs.version }} + COMPILE_ASSETS_ARGS: '-vv --env=root' diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 70f6fe58e..e7957d210 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -27,7 +27,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 7.4 - name: Fix plugin version input # Add the version number if only suffix entered run: echo "PACKAGE_VERSION=$(sed -nE '/Version:/s/.* ([0-9.]+).*/\1/p' woocommerce-paypal-payments.php)-$PACKAGE_VERSION" >> $GITHUB_ENV diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index f4967e186..9bf4f02b0 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] + php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3'] name: PHP ${{ matrix.php-versions }} steps: diff --git a/.gitignore b/.gitignore index 9ffcb360b..0da873dac 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ modules/*/assets/* auth.json .DS_Store tests/.DS_Store +.composer_compiled_assets diff --git a/README.md b/README.md index 0b5c5c3fe..921614701 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, ## Dependencies -* PHP >= 7.2 -* WordPress >=5.3 -* WooCommerce >=4.5 +* PHP >= 7.4 +* WordPress >= 6.3 +* WooCommerce >= 6.9 ## Development diff --git a/assets-compiler.json b/assets-compiler.json new file mode 100644 index 000000000..9badf65f8 --- /dev/null +++ b/assets-compiler.json @@ -0,0 +1,4 @@ +{ + "dependencies": "install", + "commands": "yarn" +} diff --git a/bootstrap.php b/bootstrap.php index fe4552110..1972d92d3 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -5,13 +5,8 @@ * @package WooCommerce\PayPalCommerce */ -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\CachingContainer; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\CompositeCachingServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\CompositeContainer; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\DelegatingContainer; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ProxyContainer; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Package; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties\PluginProperties; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return function ( @@ -34,38 +29,15 @@ return function ( */ $modules = apply_filters( 'woocommerce_paypal_payments_modules', $modules ); - $providers = array_map( - function ( ModuleInterface $module ): ServiceProviderInterface { - return $module->setup(); - }, - $modules - ); - - $provider = new CompositeCachingServiceProvider( $providers ); - $proxy_container = new ProxyContainer(); - // TODO: caching does not work currently, - // may want to consider fixing it later (pass proxy as parent to DelegatingContainer) - // for now not fixed since we were using this behavior for long time and fixing it now may break things. - $container = new DelegatingContainer( $provider ); - /** - * Skip iterable vs array check. - * - * @psalm-suppress PossiblyInvalidArgument - */ - $app_container = new CachingContainer( - new CompositeContainer( - array_merge( - $additional_containers, - array( $container ) - ) - ) - ); - $proxy_container->setInnerContainer( $app_container ); + // Initialize plugin. + $properties = PluginProperties::new( __FILE__ ); + $bootstrap = Package::new( $properties ); foreach ( $modules as $module ) { - /* @var $module ModuleInterface module */ - $module->run( $app_container ); + $bootstrap->addModule( $module ); } - return $app_container; + $bootstrap->boot(); + + return $bootstrap->container(); }; diff --git a/changelog.txt b/changelog.txt index b7670a1ed..8fee175c0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,6 @@ *** Changelog *** -= 2.9.0 - xxxx-xx-xx = += 2.9.0 - 2024-09-02 = * Fix - Fatal error in Block Editor when using WooCommerce blocks #2534 * Fix - Can't pay from block pages when the shipping callback is enabled and no shipping methods defined #2429 * Fix - Various Google Pay button fixes #2496 diff --git a/composer.json b/composer.json index e378de009..4d4b9f2f2 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "description": "PayPal Commerce Platform for WooCommerce", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "ext-json": "*", "psr/log": "^1.1", "ralouphie/getallheaders": "^3.0", @@ -14,10 +14,12 @@ "symfony/polyfill-php80": "^1.19" }, "require-dev": { + "inpsyde/composer-assets-compiler": "^2.5", "psr/container": "^1.0", "dhii/module-interface": "^0.2 || ^0.3", "container-interop/service-provider": "^0.4.0", "dhii/containers": "^0.1.0-alpha1", + "inpsyde/modularity": "^1.7", "woocommerce/woocommerce-sniffs": "^0.1.0", "phpunit/phpunit": "^7.0 | ^8.0 | ^9.0", "brain/monkey": "^2.4", @@ -69,6 +71,15 @@ "vendor/bin/phpcbf" ] }, + "composer-asset-compiler": { + "auto-run": false, + "packages": { + "inpsyde/*": { + "dependencies": "install", + "script": "build" + } + } + }, "mozart": { "dep_namespace": "WooCommerce\\PayPalCommerce\\Vendor\\", "dep_directory": "/lib/packages/", @@ -77,13 +88,18 @@ "packages": [ "psr/container", "dhii/containers", - "dhii/module-interface" + "dhii/module-interface", + "inpsyde/modularity" ], "delete_vendor_directories": true } }, "config": { + "platform": { + "php": "7.4" + }, "allow-plugins": { + "inpsyde/composer-assets-compiler": true, "dealerdirect/phpcodesniffer-composer-installer": true, "wikimedia/composer-merge-plugin": true } diff --git a/composer.lock b/composer.lock index 528b9224e..a18b56927 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8d6d274f3742efb3f1278eee713257b4", + "content-hash": "2fa610ed883c0868838d3008b7127cbf", "packages": [ { "name": "container-interop/service-provider", @@ -296,20 +296,20 @@ }, { "name": "psr/container", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=7.4.0" }, "type": "library", "autoload": { @@ -338,9 +338,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.1" + "source": "https://github.com/php-fig/container/tree/1.1.2" }, - "time": "2021-03-05T17:36:06+00:00" + "time": "2021-11-05T16:50:12+00:00" }, { "name": "psr/log", @@ -438,16 +438,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { @@ -498,7 +498,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" }, "funding": [ { @@ -514,7 +514,7 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "wikimedia/composer-merge-plugin", @@ -979,30 +979,38 @@ }, { "name": "composer/pcre", - "version": "2.1.3", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "540af382c97b83c628227d5f87cf56466d476191" + "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/540af382c97b83c628227d5f87cf56466d476191", - "reference": "540af382c97b83c628227d5f87cf56466d476191", + "url": "https://api.github.com/repos/composer/pcre/zipball/63aaeac21d7e775ff9bc9d45021e1745c97521c4", + "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "phpstan/phpstan": "^1.3", + "phpstan/phpstan": "^1.11.10", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-main": "3.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] } }, "autoload": { @@ -1030,7 +1038,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/2.1.3" + "source": "https://github.com/composer/pcre/tree/3.3.1" }, "funding": [ { @@ -1046,20 +1054,20 @@ "type": "tidelift" } ], - "time": "2024-03-19T09:03:05+00:00" + "time": "2024-08-27T18:44:43+00:00" }, { "name": "composer/semver", - "version": "3.4.0", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", "shasum": "" }, "require": { @@ -1111,7 +1119,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" + "source": "https://github.com/composer/semver/tree/3.4.2" }, "funding": [ { @@ -1127,7 +1135,7 @@ "type": "tidelift" } ], - "time": "2023-08-31T09:50:34+00:00" + "time": "2024-07-12T11:35:52+00:00" }, { "name": "composer/xdebug-handler", @@ -1327,30 +1335,30 @@ }, { "name": "dhii/containers", - "version": "v0.1.4", + "version": "v0.1.5", "source": { "type": "git", "url": "https://github.com/Dhii/containers.git", - "reference": "42ab24683183fa0dc155f26c6a470ef697bbdc9a" + "reference": "1568cb2def9dee213c5846c4618f9d6b2cebbdef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Dhii/containers/zipball/42ab24683183fa0dc155f26c6a470ef697bbdc9a", - "reference": "42ab24683183fa0dc155f26c6a470ef697bbdc9a", + "url": "https://api.github.com/repos/Dhii/containers/zipball/1568cb2def9dee213c5846c4618f9d6b2cebbdef", + "reference": "1568cb2def9dee213c5846c4618f9d6b2cebbdef", "shasum": "" }, "require": { "container-interop/service-provider": "^0.4", "dhii/collections-interface": "^0.3.0-alpha4", - "php": "^7.1 | ^8.0" + "php": "^7.4 | ^8.0" }, "require-dev": { "gmazzap/andrew": "^1.1", - "phpunit/phpunit": "^7.0 | ^8.0 | ^9.0", + "phpunit/phpunit": "^9.0", "psr/container": "^1.0", "psr/simple-cache": "^1.0", "slevomat/coding-standard": "^6.0", - "vimeo/psalm": "^4.0", + "vimeo/psalm": "^5.0", "wildwolf/psr-memory-cache": "^1.0" }, "type": "library", @@ -1381,9 +1389,9 @@ ], "support": { "issues": "https://github.com/Dhii/containers/issues", - "source": "https://github.com/Dhii/containers/tree/v0.1.4" + "source": "https://github.com/Dhii/containers/tree/v0.1.5" }, - "time": "2021-10-06T11:13:51+00:00" + "time": "2024-04-27T01:55:40+00:00" }, { "name": "dnoegel/php-xdg-base-dir", @@ -1422,6 +1430,53 @@ }, "time": "2019-12-04T15:06:13+00:00" }, + { + "name": "doctrine/deprecations", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + }, + "time": "2024-01-30T19:34:25+00:00" + }, { "name": "doctrine/instantiator", "version": "1.5.0", @@ -1595,24 +1650,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.2", + "version": "v1.1.3", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862" + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862", - "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862", + "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.2" + "phpoption/phpoption": "^1.9.3" }, "require-dev": { - "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", "autoload": { @@ -1641,7 +1696,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" }, "funding": [ { @@ -1653,7 +1708,7 @@ "type": "tidelift" } ], - "time": "2023-11-12T22:16:48+00:00" + "time": "2024-07-20T21:45:45+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -1707,36 +1762,162 @@ "time": "2020-07-09T08:09:16+00:00" }, { - "name": "mockery/mockery", - "version": "1.3.6", + "name": "inpsyde/composer-assets-compiler", + "version": "2.5.3", "source": { "type": "git", - "url": "https://github.com/mockery/mockery.git", - "reference": "dc206df4fa314a50bbb81cf72239a305c5bbd5c0" + "url": "https://github.com/inpsyde/composer-asset-compiler.git", + "reference": "3db3bea66088421377b725d110bd16ee23716c05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/dc206df4fa314a50bbb81cf72239a305c5bbd5c0", - "reference": "dc206df4fa314a50bbb81cf72239a305c5bbd5c0", + "url": "https://api.github.com/repos/inpsyde/composer-asset-compiler/zipball/3db3bea66088421377b725d110bd16ee23716c05", + "reference": "3db3bea66088421377b725d110bd16ee23716c05", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1 || ^2", + "ext-json": "*", + "php": ">= 7.2" + }, + "require-dev": { + "composer/composer": "^2.1.9", + "inpsyde/php-coding-standards": "^1@dev", + "mikey179/vfsstream": "^1.6.10", + "mockery/mockery": "^1.3.5", + "phpunit/phpunit": "^8.5.21", + "vimeo/psalm": "^4.16.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Inpsyde\\AssetsCompiler\\Composer\\Plugin", + "branch-alias": { + "dev-master": "2.x-dev", + "dev-v1.x": "1.x-dev", + "dev-v2.x": "2.x-dev", + "dev-v3.x": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Inpsyde\\AssetsCompiler\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Inpsyde GmbH", + "email": "hello@inpsyde.com", + "homepage": "https://inpsyde.com/", + "role": "Company" + }, + { + "name": "Giuseppe Mazzapica", + "email": "g.mazzapica@inpsyde.com" + } + ], + "description": "Composer plugin that installs and compile frontend dependencies for installed Composer packages based on configuration.", + "support": { + "issues": "https://github.com/inpsyde/composer-asset-compiler/issues", + "source": "https://github.com/inpsyde/composer-asset-compiler/tree/2.5.3" + }, + "time": "2024-03-07T12:11:10+00:00" + }, + { + "name": "inpsyde/modularity", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/inpsyde/modularity.git", + "reference": "2119d0e32706741a3c6dc0a85d908ec19ebf142e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/inpsyde/modularity/zipball/2119d0e32706741a3c6dc0a85d908ec19ebf142e", + "reference": "2119d0e32706741a3c6dc0a85d908ec19ebf142e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.4 <8.4", + "psr/container": "^1.1.0 || ^2" + }, + "require-dev": { + "brain/monkey": "^2.6.1", + "inpsyde/php-coding-standards": "^2@dev", + "inpsyde/wp-stubs-versions": "dev-latest", + "mikey179/vfsstream": "^v1.6.11", + "phpunit/phpunit": "^9.6.19", + "roots/wordpress-no-content": "@dev", + "vimeo/psalm": "^5.24.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Inpsyde\\Modularity\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Inpsyde GmbH", + "email": "hello@inpsyde.com", + "homepage": "https://inpsyde.com/", + "role": "Company" + } + ], + "description": "Modular PSR-11 implementation for WordPress plugins, themes or libraries.", + "support": { + "issues": "https://github.com/inpsyde/modularity/issues", + "source": "https://github.com/inpsyde/modularity/tree/1.10.0" + }, + "time": "2024-09-03T10:42:50+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", "shasum": "" }, "require": { "hamcrest/hamcrest-php": "^2.0.1", "lib-pcre": ">=7.0", - "php": ">=5.6.0" + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.10|^6.5|^7.5|^8.5|^9.3" + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, "autoload": { - "psr-0": { - "Mockery": "library/" + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" } }, "notification-url": "https://packagist.org/downloads/", @@ -1747,12 +1928,20 @@ { "name": "Pádraic Brady", "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" + "homepage": "https://github.com/padraic", + "role": "Author" }, { "name": "Dave Marshall", "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" } ], "description": "Mockery is a simple yet flexible PHP mock object framework", @@ -1770,23 +1959,26 @@ "testing" ], "support": { + "docs": "https://docs.mockery.io/", "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.3.6" + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" }, - "time": "2022-09-07T15:05:49+00:00" + "time": "2024-05-16T03:13:13+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -1794,11 +1986,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "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", @@ -1824,7 +2017,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -1832,7 +2025,7 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "netresearch/jsonmapper", @@ -2114,16 +2307,16 @@ }, { "name": "php-stubs/woocommerce-stubs", - "version": "v8.9.0", + "version": "v8.9.1", "source": { "type": "git", "url": "https://github.com/php-stubs/woocommerce-stubs.git", - "reference": "2c0bb50a76a703206151fac3123faec6ba7d5879" + "reference": "2f5be3f363ff3d02b4e29404c7895d9d15fd3e73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/woocommerce-stubs/zipball/2c0bb50a76a703206151fac3123faec6ba7d5879", - "reference": "2c0bb50a76a703206151fac3123faec6ba7d5879", + "url": "https://api.github.com/repos/php-stubs/woocommerce-stubs/zipball/2f5be3f363ff3d02b4e29404c7895d9d15fd3e73", + "reference": "2f5be3f363ff3d02b4e29404c7895d9d15fd3e73", "shasum": "" }, "require": { @@ -2152,9 +2345,9 @@ ], "support": { "issues": "https://github.com/php-stubs/woocommerce-stubs/issues", - "source": "https://github.com/php-stubs/woocommerce-stubs/tree/v8.9.0" + "source": "https://github.com/php-stubs/woocommerce-stubs/tree/v8.9.1" }, - "time": "2024-05-14T16:07:36+00:00" + "time": "2024-05-21T11:47:54+00:00" }, { "name": "php-stubs/wordpress-stubs", @@ -2462,28 +2655,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", + "version": "5.4.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.1", "ext-filter": "*", - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" + "mockery/mockery": "~1.3.5", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.13" }, "type": "library", "extra": { @@ -2507,37 +2707,45 @@ }, { "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "email": "opensource@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" }, - "time": "2021-10-19T17:43:47+00:00" + "time": "2024-05-21T05:55:05+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.6.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" + "reference": "153ae662783729388a584b4361f2545e4d841e3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" }, "require-dev": { "ext-tokenizer": "*", - "psalm/phar": "^4.8" + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, "type": "library", "extra": { @@ -2563,22 +2771,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" }, - "time": "2022-03-15T21:29:03+00:00" + "time": "2024-02-23T11:10:43+00:00" }, { "name": "phpoption/phpoption", - "version": "1.9.2", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820" + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820", - "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", "shasum": "" }, "require": { @@ -2586,13 +2794,13 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "1.9-dev" @@ -2628,7 +2836,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.2" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" }, "funding": [ { @@ -2640,44 +2848,95 @@ "type": "tidelift" } ], - "time": "2023-11-12T21:59:55+00:00" + "time": "2024-07-20T21:41:07+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "7.0.17", + "name": "phpstan/phpdoc-parser", + "version": "1.30.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66" + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", - "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/5ceb0e384997db59f38774bf79c2a6134252c08f", + "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.0" + }, + "time": "2024-08-29T09:54:52+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": "*", - "php": ">=7.2", - "phpunit/php-file-iterator": "^2.0.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.1.3 || ^4.0", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^4.2.2", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1.3" + "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": "^8.2.2" + "phpunit/phpunit": "^9.6" }, "suggest": { - "ext-xdebug": "^2.7.2" + "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-master": "7.0-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -2705,7 +2964,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.17" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -2713,32 +2973,32 @@ "type": "github" } ], - "time": "2024-03-02T06:09:37+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "2.0.6", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "69deeb8664f611f156a924154985fbd4911eb36b" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/69deeb8664f611f156a924154985fbd4911eb36b", - "reference": "69deeb8664f611f156a924154985fbd4911eb36b", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2765,7 +3025,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.6" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -2773,26 +3033,97 @@ "type": "github" } ], - "time": "2024-03-01T13:39:50+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { - "name": "phpunit/php-text-template", - "version": "1.2.1", + "name": "phpunit/php-invoker", + "version": "3.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", "shasum": "" }, "require": { - "php": ">=5.3.3" + "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/" @@ -2816,34 +3147,40 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" }, - "time": "2015-06-21T13:50:34+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" }, { "name": "phpunit/php-timer", - "version": "2.1.4", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb" + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a691211e94ff39a34811abd521c31bd5b305b0bb", - "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -2869,7 +3206,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.4" + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" }, "funding": [ { @@ -2877,112 +3214,54 @@ "type": "github" } ], - "time": "2024-03-01T13:42:41+00:00" - }, - { - "name": "phpunit/php-token-stream", - "version": "3.1.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "9c1da83261628cb24b6a6df371b6e312b3954768" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9c1da83261628cb24b6a6df371b6e312b3954768", - "reference": "9c1da83261628cb24b6a6df371b6e312b3954768", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" - }, - "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" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", - "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "abandoned": true, - "time": "2021-07-26T12:15:06+00:00" + "time": "2020-10-26T13:16:10+00:00" }, { "name": "phpunit/phpunit", - "version": "8.5.38", + "version": "9.6.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1ecad678646c817a29e55a32c930f3601c3f5a8c" + "reference": "49d7820565836236411f5dc002d16dd689cde42f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1ecad678646c817a29e55a32c930f3601c3f5a8c", - "reference": "1ecad678646c817a29e55a32c930f3601c3f5a8c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", + "reference": "49d7820565836236411f5dc002d16dd689cde42f", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.0", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.2", - "phpunit/php-code-coverage": "^7.0.12", - "phpunit/php-file-iterator": "^2.0.4", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1.2", - "sebastian/comparator": "^3.0.5", - "sebastian/diff": "^3.0.2", - "sebastian/environment": "^4.2.3", - "sebastian/exporter": "^3.1.5", - "sebastian/global-state": "^3.0.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0.1", - "sebastian/type": "^1.1.3", - "sebastian/version": "^2.0.1" + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.31", + "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", - "phpunit/php-invoker": "To allow enforcing time limits" + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -2990,10 +3269,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "8.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -3019,7 +3301,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.38" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" }, "funding": [ { @@ -3035,32 +3317,144 @@ "type": "tidelift" } ], - "time": "2024-04-05T04:31:23+00:00" + "time": "2024-07-10T11:45:39+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.3", + "name": "sebastian/cli-parser", + "version": "1.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", - "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "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": { @@ -3082,7 +3476,7 @@ "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/1.0.3" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" }, "funding": [ { @@ -3090,34 +3484,34 @@ "type": "github" } ], - "time": "2024-03-01T13:45:45+00:00" + "time": "2020-09-28T05:30:19+00:00" }, { "name": "sebastian/comparator", - "version": "3.0.5", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dc7ceb4a24aede938c7af2a9ed1de09609ca770", - "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { - "php": ">=7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -3156,7 +3550,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.5" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -3164,33 +3558,90 @@ "type": "github" } ], - "time": "2022-09-14T12:31:48+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { - "name": "sebastian/diff", - "version": "3.0.6", + "name": "sebastian/complexity", + "version": "2.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/98ff311ca519c3aa73ccd3de053bdb377171d7b6", - "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "php": ">=7.1" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.0", - "symfony/process": "^2 || ^3.3 || ^4" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "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": { @@ -3222,7 +3673,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/3.0.6" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -3230,27 +3681,27 @@ "type": "github" } ], - "time": "2024-03-02T06:16:36+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", - "version": "4.2.5", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "56932f6049a0482853056ffd617c91ffcc754205" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/56932f6049a0482853056ffd617c91ffcc754205", - "reference": "56932f6049a0482853056ffd617c91ffcc754205", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-posix": "*" @@ -3258,7 +3709,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3285,7 +3736,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/4.2.5" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -3293,34 +3744,34 @@ "type": "github" } ], - "time": "2024-03-01T13:49:59+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.6", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "1939bc8fd1d39adcfa88c5b35335910869214c56" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/1939bc8fd1d39adcfa88c5b35335910869214c56", - "reference": "1939bc8fd1d39adcfa88c5b35335910869214c56", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { - "php": ">=7.2", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -3355,14 +3806,14 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "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/3.1.6" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -3370,30 +3821,30 @@ "type": "github" } ], - "time": "2024-03-02T06:21:38+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "3.0.5", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "91c7c47047a971f02de57ed6f040087ef110c5d9" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/91c7c47047a971f02de57ed6f040087ef110c5d9", - "reference": "91c7c47047a971f02de57ed6f040087ef110c5d9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { - "php": ">=7.2", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^8.0" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-uopz": "*" @@ -3401,7 +3852,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3426,7 +3877,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -3434,34 +3885,91 @@ "type": "github" } ], - "time": "2024-03-02T06:13:16+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "3.0.5", + "name": "sebastian/lines-of-code", + "version": "1.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "ac5b293dba925751b808e02923399fb44ff0d541" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/ac5b293dba925751b808e02923399fb44ff0d541", - "reference": "ac5b293dba925751b808e02923399fb44ff0d541", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "php": ">=7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "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": { @@ -3483,7 +3991,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.5" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" }, "funding": [ { @@ -3491,32 +3999,32 @@ "type": "github" } ], - "time": "2024-03-01T13:54:02+00:00" + "time": "2020-10-26T13:12:34+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "1d439c229e61f244ff1f211e5c99737f90c67def" + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/1d439c229e61f244ff1f211e5c99737f90c67def", - "reference": "1d439c229e61f244ff1f211e5c99737f90c67def", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -3538,7 +4046,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.3" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" }, "funding": [ { @@ -3546,32 +4054,32 @@ "type": "github" } ], - "time": "2024-03-01T13:56:04+00:00" + "time": "2020-10-26T13:14:26+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.2", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/9bfd3c6f1f08c026f542032dfb42813544f7d64c", - "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -3598,10 +4106,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -3609,29 +4117,32 @@ "type": "github" } ], - "time": "2024-03-01T14:07:30+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", - "version": "2.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/72a7f7674d053d548003b16ff5a106e7e0e06eee", - "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -3652,7 +4163,7 @@ "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/2.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -3660,32 +4171,32 @@ "type": "github" } ], - "time": "2024-03-01T13:59:09+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", - "version": "1.1.5", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/18f071c3a29892b037d35e6b20ddf3ea39b42874", - "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.2" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -3708,7 +4219,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/1.1.5" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -3716,29 +4227,29 @@ "type": "github" } ], - "time": "2024-03-01T14:04:07+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -3761,22 +4272,28 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/master" + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" }, - "time": "2016-10-03T07:35:21+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.9.2", + "version": "3.10.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "aac1f6f347a5c5ac6bc98ad395007df00990f480" + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/aac1f6f347a5c5ac6bc98ad395007df00990f480", - "reference": "aac1f6f347a5c5ac6bc98ad395007df00990f480", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", "shasum": "" }, "require": { @@ -3843,20 +4360,20 @@ "type": "open_collective" } ], - "time": "2024-04-23T20:25:34+00:00" + "time": "2024-07-21T23:26:44+00:00" }, { "name": "symfony/console", - "version": "v5.4.39", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f3e591c48688a0cfa1a3296205926c05e84b22b1" + "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f3e591c48688a0cfa1a3296205926c05e84b22b1", - "reference": "f3e591c48688a0cfa1a3296205926c05e84b22b1", + "url": "https://api.github.com/repos/symfony/console/zipball/e86f8554de667c16dde8aeb89a3990cfde924df9", + "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9", "shasum": "" }, "require": { @@ -3926,7 +4443,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.39" + "source": "https://github.com/symfony/console/tree/v5.4.43" }, "funding": [ { @@ -3942,7 +4459,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T08:26:06+00:00" + "time": "2024-08-13T16:31:56+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4013,16 +4530,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", "shasum": "" }, "require": { @@ -4072,7 +4589,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" }, "funding": [ { @@ -4088,20 +4605,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", "shasum": "" }, "require": { @@ -4150,7 +4667,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" }, "funding": [ { @@ -4166,20 +4683,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", "shasum": "" }, "require": { @@ -4231,7 +4748,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" }, "funding": [ { @@ -4247,20 +4764,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { @@ -4311,7 +4828,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -4327,20 +4844,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" + "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", - "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1", + "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1", "shasum": "" }, "require": { @@ -4387,7 +4904,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0" }, "funding": [ { @@ -4403,7 +4920,7 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/service-contracts", @@ -4490,16 +5007,16 @@ }, { "name": "symfony/string", - "version": "v5.4.39", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "495e71bae5862308051b9e63cc3e34078eed83ef" + "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/495e71bae5862308051b9e63cc3e34078eed83ef", - "reference": "495e71bae5862308051b9e63cc3e34078eed83ef", + "url": "https://api.github.com/repos/symfony/string/zipball/8be1d484951ff5ca995eaf8edcbcb8b9a5888450", + "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450", "shasum": "" }, "require": { @@ -4556,7 +5073,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.39" + "source": "https://github.com/symfony/string/tree/v5.4.43" }, "funding": [ { @@ -4572,7 +5089,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T08:26:06+00:00" + "time": "2024-08-01T10:24:28+00:00" }, { "name": "theseer/tokenizer", @@ -4734,23 +5251,23 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4" + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", - "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.2", + "graham-campbell/result-type": "^1.1.3", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.2", + "phpoption/phpoption": "^1.9.3", "symfony/polyfill-ctype": "^1.24", "symfony/polyfill-mbstring": "^1.24", "symfony/polyfill-php80": "^1.24" @@ -4767,7 +5284,7 @@ "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "5.6-dev" @@ -4802,7 +5319,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" }, "funding": [ { @@ -4814,7 +5331,7 @@ "type": "tidelift" } ], - "time": "2023-11-12T22:43:29+00:00" + "time": "2024-07-20T21:52:34+00:00" }, { "name": "webmozart/assert", @@ -5030,9 +5547,12 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "ext-json": "*" }, "platform-dev": [], + "platform-overrides": { + "php": "7.4" + }, "plugin-api-version": "2.6.0" } diff --git a/lib/packages/Inpsyde/Modularity/Container/ContainerConfigurator.php b/lib/packages/Inpsyde/Modularity/Container/ContainerConfigurator.php new file mode 100644 index 000000000..429869912 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Container/ContainerConfigurator.php @@ -0,0 +1,153 @@ + + */ + private $services = []; + + /** + * @var array + */ + private $factoryIds = []; + + /** + * @var array> + */ + private $extensions = []; + + /** + * @var ContainerInterface[] + */ + private $containers = []; + + /** + * @var null|ContainerInterface + */ + private $compiledContainer; + + /** + * ContainerConfigurator constructor. + * + * @param ContainerInterface[] $containers + */ + public function __construct(array $containers = []) + { + array_map([$this, 'addContainer'], $containers); + } + + /** + * Allowing to add child containers. + * + * @param ContainerInterface $container + */ + public function addContainer(ContainerInterface $container): void + { + $this->containers[] = $container; + } + + /** + * @param string $id + * @param callable(ContainerInterface $container):mixed $factory + */ + public function addFactory(string $id, callable $factory): void + { + $this->addService($id, $factory); + // We're using a hash table to detect later + // via isset() if a Service as a Factory. + $this->factoryIds[$id] = true; + } + + /** + * @param string $id + * @param callable(ContainerInterface $container):mixed $service + * + * @return void + */ + public function addService(string $id, callable $service): void + { + /* + * We are being intentionally permissive here, + * allowing a simple workflow for *intentional* overrides + * while accepting the (small?) risk of *accidental* overrides + * that could be hard to notice and debug. + * + * Clear a factory flag in case it was a factory. + * If needs be, it will get re-added after this function completes. + */ + unset($this->factoryIds[$id]); + + $this->services[$id] = $service; + } + + /** + * @param string $id + * + * @return bool + */ + public function hasService(string $id): bool + { + if (array_key_exists($id, $this->services)) { + return true; + } + + foreach ($this->containers as $container) { + if ($container->has($id)) { + return true; + } + } + + return false; + } + + /** + * @param string $id + * @param callable(mixed $service, ContainerInterface $container):mixed $extender + * + * @return void + */ + public function addExtension(string $id, callable $extender): void + { + if (!isset($this->extensions[$id])) { + $this->extensions[$id] = []; + } + + $this->extensions[$id][] = $extender; + } + + /** + * @param string $id + * + * @return bool + */ + public function hasExtension(string $id): bool + { + return isset($this->extensions[$id]); + } + + /** + * Returns a read only version of this Container. + * + * @return ContainerInterface + */ + public function createReadOnlyContainer(): ContainerInterface + { + if (!$this->compiledContainer) { + $this->compiledContainer = new ReadOnlyContainer( + $this->services, + $this->factoryIds, + $this->extensions, + $this->containers + ); + } + + return $this->compiledContainer; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Container/PackageProxyContainer.php b/lib/packages/Inpsyde/Modularity/Container/PackageProxyContainer.php new file mode 100644 index 000000000..2c8d79d5d --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Container/PackageProxyContainer.php @@ -0,0 +1,102 @@ +package = $package; + } + + /** + * @param string $id + * @return mixed + * + * @throws \Exception + */ + public function get(string $id) + { + $this->assertPackageBooted($id); + + return $this->container->get($id); + } + + /** + * @param string $id + * @return bool + * + * @throws \Exception + */ + public function has(string $id): bool + { + return $this->tryContainer() && $this->container->has($id); + } + + /** + * @return bool + * + * @throws \Exception + * @psalm-assert-if-true ContainerInterface $this->container + */ + private function tryContainer(): bool + { + if ($this->container) { + return true; + } + + /** TODO: We need a better way to deal with status checking besides equality */ + if ( + $this->package->statusIs(Package::STATUS_READY) + || $this->package->statusIs(Package::STATUS_BOOTED) + ) { + $this->container = $this->package->container(); + } + + return (bool)$this->container; + } + + /** + * @param string $id + * @return void + * + * @throws \Exception + * + * @psalm-assert ContainerInterface $this->container + */ + private function assertPackageBooted(string $id): void + { + if ($this->tryContainer()) { + return; + } + + $name = $this->package->name(); + $status = $this->package->statusIs(Package::STATUS_FAILED) + ? 'is errored' + : 'is not ready yet'; + + throw new class ("Error retrieving service {$id} because package {$name} {$status}.") + extends \Exception + implements ContainerExceptionInterface { + }; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php b/lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php new file mode 100644 index 000000000..763ae23ac --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php @@ -0,0 +1,138 @@ + + */ + private $services; + + /** + * @var array + */ + private $factoryIds; + + /** + * @var array> + */ + private $extensions; + + /** + * Resolved factories. + * + * @var array + */ + private $resolvedServices = []; + + /** + * @var ContainerInterface[] + */ + private $containers; + + /** + * ReadOnlyContainer constructor. + * + * @param array $services + * @param array $factoryIds + * @param array> $extensions + * @param ContainerInterface[] $containers + */ + public function __construct( + array $services, + array $factoryIds, + array $extensions, + array $containers + ) { + $this->services = $services; + $this->factoryIds = $factoryIds; + $this->extensions = $extensions; + $this->containers = $containers; + } + + /** + * @param string $id + * + * @return mixed + */ + public function get(string $id) + { + if (array_key_exists($id, $this->resolvedServices)) { + return $this->resolvedServices[$id]; + } + + if (array_key_exists($id, $this->services)) { + $service = $this->services[$id]($this); + $resolved = $this->resolveExtensions($id, $service); + + if (!isset($this->factoryIds[$id])) { + $this->resolvedServices[$id] = $resolved; + unset($this->services[$id]); + } + + return $resolved; + } + + foreach ($this->containers as $container) { + if ($container->has($id)) { + $service = $container->get($id); + + return $this->resolveExtensions($id, $service); + } + } + + throw new class ("Service with ID {$id} not found.") + extends \Exception + implements NotFoundExceptionInterface { + }; + } + + /** + * @param string $id + * + * @return bool + */ + public function has(string $id): bool + { + if (array_key_exists($id, $this->services)) { + return true; + } + + if (array_key_exists($id, $this->resolvedServices)) { + return true; + } + + foreach ($this->containers as $container) { + if ($container->has($id)) { + return true; + } + } + + return false; + } + + /** + * @param string $id + * @param mixed $service + * + * @return mixed + */ + private function resolveExtensions(string $id, $service) + { + if (!isset($this->extensions[$id])) { + return $service; + } + + foreach ($this->extensions[$id] as $extender) { + $service = $extender($service, $this); + } + + return $service; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php b/lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php new file mode 100644 index 000000000..2f7771fb5 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php @@ -0,0 +1,21 @@ + + */ + public function extensions(): array; +} diff --git a/lib/packages/Inpsyde/Modularity/Module/FactoryModule.php b/lib/packages/Inpsyde/Modularity/Module/FactoryModule.php new file mode 100644 index 000000000..6e686c0c2 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Module/FactoryModule.php @@ -0,0 +1,18 @@ + + */ + public function factories(): array; +} diff --git a/lib/packages/Inpsyde/Modularity/Module/Module.php b/lib/packages/Inpsyde/Modularity/Module/Module.php new file mode 100644 index 000000000..49dd0e5e3 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Module/Module.php @@ -0,0 +1,20 @@ + + */ + public function services(): array; +} diff --git a/lib/packages/Inpsyde/Modularity/Package.php b/lib/packages/Inpsyde/Modularity/Package.php new file mode 100644 index 000000000..f55eaeccb --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Package.php @@ -0,0 +1,727 @@ + + * $package = Package::new(); + * $package->boot(); + * + * $container = $package->container(); + * $container->has(Package::PROPERTIES); + * $container->get(Package::PROPERTIES); + * + * + * @var string + */ + public const PROPERTIES = 'properties'; + + /** + * Custom action to be used to add Modules to the package. + * It might also be used to access package properties. + * + * @example + * + * $package = Package::new(); + * + * add_action( + * $package->hookName(Package::ACTION_INIT), + * $callback + * ); + * + */ + public const ACTION_INIT = 'init'; + + /** + * Custom action which is triggered after the application + * is booted to access container and properties. + * + * @example + * + * $package = Package::new(); + * + * add_action( + * $package->hookName(Package::ACTION_READY), + * $callback + * ); + * + */ + public const ACTION_READY = 'ready'; + + /** + * Custom action which is triggered when a failure happens during the building stage. + * + * @example + * + * $package = Package::new(); + * + * add_action( + * $package->hookName(Package::ACTION_FAILED_BUILD), + * $callback + * ); + * + */ + public const ACTION_FAILED_BUILD = 'failed-build'; + + /** + * Custom action which is triggered when a failure happens during the booting stage. + * + * @example + * + * $package = Package::new(); + * + * add_action( + * $package->hookName(Package::ACTION_FAILED_BOOT), + * $callback + * ); + * + */ + public const ACTION_FAILED_BOOT = 'failed-boot'; + + /** + * Custom action which is triggered when a package is connected. + */ + public const ACTION_PACKAGE_CONNECTED = 'package-connected'; + + /** + * Custom action which is triggered when a package cannot be connected. + */ + public const ACTION_FAILED_CONNECTION = 'failed-connection'; + + /** + * Module states can be used to get information about your module. + * + * @example + * + * $package = Package::new(); + * $package->moduleIs(SomeModule::class, Package::MODULE_ADDED); // false + * $package->boot(new SomeModule()); + * $package->moduleIs(SomeModule::class, Package::MODULE_ADDED); // true + * + */ + public const MODULE_ADDED = 'added'; + public const MODULE_NOT_ADDED = 'not-added'; + public const MODULE_REGISTERED = 'registered'; + public const MODULE_REGISTERED_FACTORIES = 'registered-factories'; + public const MODULE_EXTENDED = 'extended'; + public const MODULE_EXECUTED = 'executed'; + public const MODULE_EXECUTION_FAILED = 'executed-failed'; + public const MODULES_ALL = '*'; + + /** + * Custom states for the class. + * + * @example + * + * $package = Package::new(); + * $package->statusIs(Package::IDLE); // true + * $package->boot(); + * $package->statusIs(Package::BOOTED); // true + * + */ + public const STATUS_IDLE = 2; + public const STATUS_INITIALIZED = 4; + public const STATUS_MODULES_ADDED = 5; + public const STATUS_READY = 7; + public const STATUS_BOOTED = 8; + public const STATUS_FAILED = -8; + + /** + * Current state of the application. + * + * @see Package::STATUS_* + * + * @var int + */ + private $status = self::STATUS_IDLE; + + /** + * Contains the progress of all modules. + * + * @see Package::moduleProgress() + * + * @var array> + */ + private $moduleStatus = [self::MODULES_ALL => []]; + + /** + * Hashmap of where keys are names of connected packages, and values are boolean, true + * if connection was successful. + * + * @see Package::connect() + * + * @var array + */ + private $connectedPackages = []; + + /** + * @var list + */ + private $executables = []; + + /** + * @var Properties + */ + private $properties; + + /** + * @var ContainerConfigurator + */ + private $containerConfigurator; + + /** + * @var bool + */ + private $built = false; + + /** + * @var bool + */ + private $hasContainer = false; + + /** + * @var \Throwable|null + */ + private $lastError = null; + + /** + * @param Properties $properties + * @param ContainerInterface[] $containers + * + * @return Package + */ + public static function new(Properties $properties, ContainerInterface ...$containers): Package + { + return new self($properties, ...$containers); + } + + /** + * @param Properties $properties + * @param ContainerInterface[] $containers + */ + private function __construct(Properties $properties, ContainerInterface ...$containers) + { + $this->properties = $properties; + + $this->containerConfigurator = new ContainerConfigurator($containers); + $this->containerConfigurator->addService( + self::PROPERTIES, + static function () use ($properties) { + return $properties; + } + ); + } + + /** + * @param Module $module + * + * @return static + * @throws \Exception + */ + public function addModule(Module $module): Package + { + try { + $this->assertStatus(self::STATUS_IDLE, sprintf('add module %s', $module->id())); + + $registeredServices = $this->addModuleServices( + $module, + self::MODULE_REGISTERED + ); + $registeredFactories = $this->addModuleServices( + $module, + self::MODULE_REGISTERED_FACTORIES + ); + $extended = $this->addModuleServices( + $module, + self::MODULE_EXTENDED + ); + $isExecutable = $module instanceof ExecutableModule; + + // ExecutableModules are collected and executed on Package::boot() + // when the Container is being compiled. + if ($isExecutable) { + /** @var ExecutableModule $module */ + $this->executables[] = $module; + } + + $added = $registeredServices || $registeredFactories || $extended || $isExecutable; + $status = $added ? self::MODULE_ADDED : self::MODULE_NOT_ADDED; + $this->moduleProgress($module->id(), $status); + } catch (\Throwable $throwable) { + $this->handleFailure($throwable, self::ACTION_FAILED_BUILD); + } + + return $this; + } + + /** + * @param Package $package + * @return bool + * @throws \Exception + */ + public function connect(Package $package): bool + { + try { + if ($package === $this) { + return false; + } + + $packageName = $package->name(); + $errorData = ['package' => $packageName, 'status' => $this->status]; + $errorMessage = "Failed connecting package {$packageName}"; + + // Don't connect, if already connected + if (array_key_exists($packageName, $this->connectedPackages)) { + $error = "{$errorMessage} because it was already connected."; + do_action( + $this->hookName(self::ACTION_FAILED_CONNECTION), + $packageName, + new \WP_Error('already_connected', $error, $errorData) + ); + + throw new \Exception($error, 0, $this->lastError); + } + + // Don't connect, if already booted or boot failed + $failed = $this->statusIs(self::STATUS_FAILED); + if ($failed || $this->statusIs(self::STATUS_BOOTED)) { + $status = $failed ? 'errored' : 'booted'; + $error = "{$errorMessage} to a {$status} package."; + do_action( + $this->hookName(self::ACTION_FAILED_CONNECTION), + $packageName, + new \WP_Error("no_connect_on_{$status}", $error, $errorData) + ); + + throw new \Exception($error, 0, $this->lastError); + } + + $this->connectedPackages[$packageName] = true; + + // We put connected package's properties in this package's container, so that in modules + // "run" method we can access them if we need to. + $this->containerConfigurator->addService( + sprintf('%s.%s', $package->name(), self::PROPERTIES), + static function () use ($package): Properties { + return $package->properties(); + } + ); + + // If the other package is booted, we can obtain a container, otherwise + // we build a proxy container + $container = $package->statusIs(self::STATUS_BOOTED) + ? $package->container() + : new PackageProxyContainer($package); + + $this->containerConfigurator->addContainer($container); + + do_action( + $this->hookName(self::ACTION_PACKAGE_CONNECTED), + $packageName, + $this->status, + $container instanceof PackageProxyContainer + ); + + return true; + } catch (\Throwable $throwable) { + if (isset($packageName)) { + $this->connectedPackages[$packageName] = false; + } + $this->handleFailure($throwable, self::ACTION_FAILED_BUILD); + + return false; + } + } + + /** + * @return static + */ + public function build(): Package + { + try { + // Don't allow building the application multiple times. + $this->assertStatus(self::STATUS_IDLE, 'build package'); + + do_action( + $this->hookName(self::ACTION_INIT), + $this + ); + // Changing the status here ensures we can not call this method again, and also we can not + // add new modules, because both this and `addModule()` methods check for idle status. + // For backward compatibility, adding new modules via `boot()` will still be possible, even + // if deprecated, at the condition that the container was not yet accessed at that point. + $this->progress(self::STATUS_INITIALIZED); + } catch (\Throwable $throwable) { + $this->handleFailure($throwable, self::ACTION_FAILED_BUILD); + } finally { + $this->built = true; + } + + return $this; + } + + /** + * @param Module ...$defaultModules Deprecated, use `addModule()` to add default modules. + * @return bool + * + * @throws \Throwable + */ + public function boot(Module ...$defaultModules): bool + { + try { + // Call build() if not called yet, and ensure any new module passed here is added + // as well, throwing if the container was already built. + $this->doBuild(...$defaultModules); + + // Don't allow booting the application multiple times. + $this->assertStatus(self::STATUS_MODULES_ADDED, 'boot application', '<'); + $this->assertStatus(self::STATUS_FAILED, 'boot application', '!='); + + $this->progress(self::STATUS_MODULES_ADDED); + + $this->doExecute(); + + $this->progress(self::STATUS_READY); + + do_action( + $this->hookName(self::ACTION_READY), + $this + ); + } catch (\Throwable $throwable) { + $this->handleFailure($throwable, self::ACTION_FAILED_BOOT); + + return false; + } + + $this->progress(self::STATUS_BOOTED); + + return true; + } + + /** + * @param Module ...$defaultModules + * @return void + */ + private function doBuild(Module ...$defaultModules): void + { + if ($defaultModules) { + $this->deprecatedArgument( + sprintf( + 'Passing default modules to %1$s::boot() is deprecated since version 1.7.0.' + . ' Please add modules via %1$s::addModule().', + __CLASS__ + ), + __METHOD__, + '1.7.0' + ); + } + + if (!$this->built) { + array_map([$this, 'addModule'], $defaultModules); + $this->build(); + + return; + } + + if ( + !$defaultModules + || ($this->status >= self::STATUS_MODULES_ADDED) + || ($this->statusIs(self::STATUS_FAILED)) + ) { + // if we don't have default modules, there's nothing to do, and if the status is beyond + // "modules added" or is failed, we do nothing as well and let `boot()` throw. + return; + } + + $backup = $this->status; + + try { + // simulate idle status to prevent `addModule()` from throwing + // only if we don't have a container yet + $this->hasContainer or $this->status = self::STATUS_IDLE; + + foreach ($defaultModules as $defaultModule) { + // If a module was added by `build()` or `addModule()` we can skip it, a + // deprecation was trigger to make it noticeable without breakage + if (!$this->moduleIs($defaultModule->id(), self::MODULE_ADDED)) { + $this->addModule($defaultModule); + } + } + } finally { + $this->status = $backup; + } + } + + /** + * @param Module $module + * @param string $status + * @return bool + */ + private function addModuleServices(Module $module, string $status): bool + { + $services = null; + $addCallback = null; + switch ($status) { + case self::MODULE_REGISTERED: + $services = $module instanceof ServiceModule ? $module->services() : null; + $addCallback = [$this->containerConfigurator, 'addService']; + break; + case self::MODULE_REGISTERED_FACTORIES: + $services = $module instanceof FactoryModule ? $module->factories() : null; + $addCallback = [$this->containerConfigurator, 'addFactory']; + break; + case self::MODULE_EXTENDED: + $services = $module instanceof ExtendingModule ? $module->extensions() : null; + $addCallback = [$this->containerConfigurator, 'addExtension']; + break; + } + + if (!$services) { + return false; + } + + $ids = []; + array_walk( + $services, + static function (callable $service, string $id) use ($addCallback, &$ids) { + /** @var callable(string, callable) $addCallback */ + $addCallback($id, $service); + /** @var list $ids */ + $ids[] = $id; + } + ); + /** @var list $ids */ + $this->moduleProgress($module->id(), $status, $ids); + + return true; + } + + /** + * @return void + * + * @throws \Throwable + */ + private function doExecute(): void + { + foreach ($this->executables as $executable) { + $success = $executable->run($this->container()); + $this->moduleProgress( + $executable->id(), + $success + ? self::MODULE_EXECUTED + : self::MODULE_EXECUTION_FAILED + ); + } + } + + /** + * @param string $moduleId + * @param string $status + * @param list|null $serviceIds + * + * @return void + */ + private function moduleProgress(string $moduleId, string $status, ?array $serviceIds = null) + { + isset($this->moduleStatus[$status]) or $this->moduleStatus[$status] = []; + $this->moduleStatus[$status][] = $moduleId; + + if (!$serviceIds || !$this->properties->isDebug()) { + $this->moduleStatus[self::MODULES_ALL][] = "{$moduleId} {$status}"; + + return; + } + + $description = sprintf('%s %s (%s)', $moduleId, $status, implode(', ', $serviceIds)); + $this->moduleStatus[self::MODULES_ALL][] = $description; + } + + /** + * @return array> + */ + public function modulesStatus(): array + { + return $this->moduleStatus; + } + + /** + * @return array + */ + public function connectedPackages(): array + { + return $this->connectedPackages; + } + + /** + * @param string $packageName + * @return bool + */ + public function isPackageConnected(string $packageName): bool + { + return $this->connectedPackages[$packageName] ?? false; + } + + /** + * @param string $moduleId + * @param string $status + * + * @return bool + */ + public function moduleIs(string $moduleId, string $status): bool + { + return in_array($moduleId, $this->moduleStatus[$status] ?? [], true); + } + + /** + * Return the filter name to be used to extend modules of the plugin. + * + * If the plugin is single file `my-plugin.php` in plugins folder the filter name will be: + * `inpsyde.modularity.my-plugin`. + * + * If the plugin is in a sub-folder e.g. `my-plugin/index.php` the filter name will be: + * `inpsyde.modularity.my-plugin` anyway, so the file name is not relevant. + * + * @param string $suffix + * + * @return string + * @see Package::name() + * + */ + public function hookName(string $suffix = ''): string + { + $filter = self::HOOK_PREFIX . $this->properties->baseName(); + + if ($suffix) { + $filter .= '.' . $suffix; + } + + return $filter; + } + + /** + * @return Properties + */ + public function properties(): Properties + { + return $this->properties; + } + + /** + * @return ContainerInterface + * + * @throws \Exception + */ + public function container(): ContainerInterface + { + $this->assertStatus(self::STATUS_INITIALIZED, 'obtain the container instance', '>='); + $this->hasContainer = true; + + return $this->containerConfigurator->createReadOnlyContainer(); + } + + /** + * @return string + */ + public function name(): string + { + return $this->properties->baseName(); + } + + /** + * @param int $status + */ + private function progress(int $status): void + { + $this->status = $status; + } + + /** + * @param int $status + * + * @return bool + */ + public function statusIs(int $status): bool + { + return $this->status === $status; + } + + /** + * @param \Throwable $throwable + * @param Package::ACTION_FAILED_* $action + * @return void + * @throws \Throwable + */ + private function handleFailure(\Throwable $throwable, string $action): void + { + $this->progress(self::STATUS_FAILED); + $hook = $this->hookName($action); + did_action($hook) or do_action($hook, $throwable); + + if ($this->properties->isDebug()) { + throw $throwable; + } + + $this->lastError = $throwable; + } + + /** + * @param int $status + * @param string $action + * @param string $operator + * + * @throws \Exception + * @psalm-suppress ArgumentTypeCoercion + */ + private function assertStatus(int $status, string $action, string $operator = '=='): void + { + if (!version_compare((string) $this->status, (string) $status, $operator)) { + throw new \Exception( + sprintf("Can't %s at this point of application.", $action), + 0, + $this->lastError + ); + } + } + + /** + * Similar to WP's `_deprecated_argument()`, but executes regardless of WP_DEBUG and without + * translated message (so without attempting loading translation files). + * + * @param string $message + * @param string $function + * @param string $version + * + * @return void + */ + private function deprecatedArgument(string $message, string $function, string $version): void + { + do_action('deprecated_argument_run', $function, $message, $version); + + if (apply_filters('deprecated_argument_trigger_error', true)) { + trigger_error($message, \E_USER_DEPRECATED); + } + } +} diff --git a/lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php b/lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php new file mode 100644 index 000000000..47bb4db74 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php @@ -0,0 +1,217 @@ +sanitizeBaseName($baseName); + $basePath = (string) trailingslashit($basePath); + if ($baseUrl) { + $baseUrl = (string) trailingslashit($baseUrl); + } + + $this->baseName = $baseName; + $this->basePath = $basePath; + $this->baseUrl = $baseUrl; + $this->properties = array_replace(Properties::DEFAULT_PROPERTIES, $properties); + } + + /** + * @param string $name + * + * @return string + */ + protected function sanitizeBaseName(string $name): string + { + substr_count($name, '/') and $name = dirname($name); + + return strtolower(pathinfo($name, PATHINFO_FILENAME)); + } + + /** + * @return string + */ + public function baseName(): string + { + return $this->baseName; + } + + /** + * @return string + */ + public function basePath(): string + { + return $this->basePath; + } + + /** + * @return string|null + */ + public function baseUrl(): ?string + { + return $this->baseUrl; + } + + /** + * @return string + */ + public function author(): string + { + return (string) $this->get(self::PROP_AUTHOR); + } + + /** + * @return string + */ + public function authorUri(): string + { + return (string) $this->get(self::PROP_AUTHOR_URI); + } + + /** + * @return string + */ + public function description(): string + { + return (string) $this->get(self::PROP_DESCRIPTION); + } + + /** + * @return string + */ + public function textDomain(): string + { + return (string) $this->get(self::PROP_TEXTDOMAIN); + } + + /** + * @return string + */ + public function domainPath(): string + { + return (string) $this->get(self::PROP_DOMAIN_PATH); + } + + /** + * @return string + */ + public function name(): string + { + return (string) $this->get(self::PROP_NAME); + } + + /** + * @return string + */ + public function uri(): string + { + return (string) $this->get(self::PROP_URI); + } + + /** + * @return string + */ + public function version(): string + { + return (string) $this->get(self::PROP_VERSION); + } + + /** + * @return string|null + */ + public function requiresWp(): ?string + { + $value = $this->get(self::PROP_REQUIRES_WP); + + return $value && is_string($value) ? $value : null; + } + + /** + * @return string|null + */ + public function requiresPhp(): ?string + { + $value = $this->get(self::PROP_REQUIRES_PHP); + + return $value && is_string($value) ? $value : null; + } + + /** + * @return array + */ + public function tags(): array + { + return (array) $this->get(self::PROP_TAGS); + } + + /** + * @param string $key + * @param null $default + * @return mixed + */ + public function get(string $key, $default = null) + { + return $this->properties[$key] ?? $default; + } + + /** + * @param string $key + * @return bool + */ + public function has(string $key): bool + { + return isset($this->properties[$key]); + } + + /** + * @return bool + * @see Properties::isDebug() + */ + public function isDebug(): bool + { + if ($this->isDebug === null) { + $this->isDebug = defined('WP_DEBUG') && WP_DEBUG; + } + + return $this->isDebug; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php b/lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php new file mode 100644 index 000000000..5c06577e1 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php @@ -0,0 +1,209 @@ + 0) { + $properties[self::PROP_AUTHOR] = implode(', ', $names); + } + + // Custom settings which can be stored in composer.json "extra.modularity" + $extra = $composerJsonData['extra']['modularity'] ?? []; + foreach (self::EXTRA_KEYS as $key) { + $properties[$key] = $extra[$key] ?? ''; + } + + // PHP requirement in composer.json "require" or "require-dev" + $properties[self::PROP_REQUIRES_PHP] = self::extractPhpVersion($composerJsonData); + + // composer.json might have "version" in root + $version = $composerJsonData['version'] ?? null; + if ($version && is_string($version)) { + $properties[self::PROP_VERSION] = $version; + } + + [$baseName, $name] = static::buildNames($composerJsonData); + $basePath = dirname($composerJsonFile); + if (empty($properties[self::PROP_NAME])) { + $properties[self::PROP_NAME] = $name; + } + + return new self( + $baseName, + $basePath, + $baseUrl, + $properties + ); + } + + /** + * @param array $composerJsonData + * + * @return array{string, string} + */ + private static function buildNames(array $composerJsonData): array + { + $composerName = (string) ($composerJsonData['name'] ?? ''); + $packageNamePieces = explode('/', $composerName, 2); + $basename = implode('-', $packageNamePieces); + // "inpsyde/foo-bar-baz" => "Inpsyde Foo Bar Baz" + $name = mb_convert_case( + str_replace(['-', '_', '.'], ' ', implode(' ', $packageNamePieces)), + MB_CASE_TITLE + ); + + return [$basename, $name]; + } + + /** + * Check PHP version in require, require-dev. + * + * Attempt to parse requirements to find the _minimum_ accepted version (consistent with WP). + * Composer requirements are parsed in a way that, for example: + * `>=7.2` returns `7.2` + * `^7.3` returns `7.3` + * `5.6 || >= 7.1` returns `5.6` + * `>= 7.1 < 8` returns `7.1` + * + * @param array $composerData + * @param string $key + * + * @return string|null + */ + private static function extractPhpVersion(array $composerData, string $key = 'require'): ?string + { + $nextKey = ($key === 'require') + ? 'require-dev' + : null; + $base = (array) ($composerData[$key] ?? []); + $requirement = $base['php'] ?? null; + $version = ($requirement && is_string($requirement)) + ? trim($requirement) + : null; + if (!$version) { + return $nextKey + ? static::extractPhpVersion($composerData, $nextKey) + : null; + } + + static $matcher; + $matcher or $matcher = static function (string $version): ?string { + $version = trim($version); + if (!$version) { + return null; + } + + // versions range like `>= 7.2.4 < 8` + if (preg_match('{>=?([\s0-9\.]+)<}', $version, $matches)) { + return trim($matches[1], " \t\n\r\0\x0B."); + } + + // aliases like `dev-src#abcde as 7.4` + if (preg_match('{as\s*([\s0-9\.]+)}', $version, $matches)) { + return trim($matches[1], " \t\n\r\0\x0B."); + } + + // Basic requirements like 7.2, >=7.2, ^7.2, ~7.2 + if (preg_match('{^(?:[>=\s~\^]+)?([0-9\.]+)}', $version, $matches)) { + return trim($matches[1], " \t\n\r\0\x0B."); + } + + return null; + }; + + // support for simpler requirements like `7.3`, `>=7.4` or alternative like `5.6 || >=7` + + $alternatives = explode('||', $version); + $found = null; + foreach ($alternatives as $alternative) { + /** @var callable(string):?string $matcher */ + $itemFound = $matcher($alternative); + if ($itemFound && (!$found || version_compare($itemFound, $found, '<'))) { + $found = $itemFound; + } + } + + if ($found) { + return $found; + } + + return $nextKey + ? static::extractPhpVersion($composerData, $nextKey) + : null; + } + + /** + * @param string $url + * + * @return static + * + * @throws \Exception + */ + public function withBaseUrl(string $url): LibraryProperties + { + if ($this->baseUrl !== null) { + throw new \Exception(sprintf('%s::$baseUrl property is not overridable.', __CLASS__)); + } + + $this->baseUrl = trailingslashit($url); + + return $this; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php b/lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php new file mode 100644 index 000000000..f3d3f5485 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php @@ -0,0 +1,176 @@ + 'Author', + self::PROP_AUTHOR_URI => 'AuthorURI', + self::PROP_DESCRIPTION => 'Description', + self::PROP_DOMAIN_PATH => 'DomainPath', + self::PROP_NAME => 'Name', + self::PROP_TEXTDOMAIN => 'TextDomain', + self::PROP_URI => 'PluginURI', + self::PROP_VERSION => 'Version', + self::PROP_REQUIRES_WP => 'RequiresWP', + self::PROP_REQUIRES_PHP => 'RequiresPHP', + + // additional headers + self::PROP_NETWORK => 'Network', + ]; + + /** + * @var string + */ + private $pluginMainFile; + + /** + * @var string + */ + private $pluginBaseName; + + /** + * @var bool|null + */ + protected $isMu; + + /** + * @var bool|null + */ + protected $isActive; + + /** + * @var bool|null + */ + protected $isNetworkActive; + + /** + * @param string $pluginMainFile + * + * @return PluginProperties + */ + public static function new(string $pluginMainFile): PluginProperties + { + return new self($pluginMainFile); + } + + /** + * PluginProperties constructor. + * + * @param string $pluginMainFile + */ + protected function __construct(string $pluginMainFile) + { + if (!function_exists('get_plugin_data')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $pluginData = get_plugin_data($pluginMainFile); + $properties = Properties::DEFAULT_PROPERTIES; + + // Map pluginData to internal structure. + foreach (self::HEADERS as $key => $pluginDataKey) { + $properties[$key] = $pluginData[$pluginDataKey] ?? ''; + unset($pluginData[$pluginDataKey]); + } + $properties = array_merge($properties, $pluginData); + + $this->pluginMainFile = wp_normalize_path($pluginMainFile); + + $this->pluginBaseName = plugin_basename($pluginMainFile); + $basePath = plugin_dir_path($pluginMainFile); + $baseUrl = plugins_url('/', $pluginMainFile); + + parent::__construct( + $this->pluginBaseName, + $basePath, + $baseUrl, + $properties + ); + } + + /** + * @return string + */ + public function pluginMainFile(): string + { + return $this->pluginMainFile; + } + + /** + * @return bool + * + * @psalm-suppress PossiblyFalseArgument + */ + public function network(): bool + { + return (bool) $this->get(self::PROP_NETWORK, false); + } + + /** + * @return bool + */ + public function isActive(): bool + { + if ($this->isActive === null) { + if (!function_exists('is_plugin_active')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + $this->isActive = is_plugin_active($this->pluginBaseName); + } + + return $this->isActive; + } + + /** + * @return bool + */ + public function isNetworkActive(): bool + { + if ($this->isNetworkActive === null) { + if (!function_exists('is_plugin_active_for_network')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + $this->isNetworkActive = is_plugin_active_for_network($this->pluginBaseName); + } + + return $this->isNetworkActive; + } + + /** + * @return bool + */ + public function isMuPlugin(): bool + { + if ($this->isMu === null) { + /** + * @psalm-suppress UndefinedConstant + * @psalm-suppress MixedArgument + */ + $muPluginDir = wp_normalize_path(WPMU_PLUGIN_DIR); + $this->isMu = strpos($this->pluginMainFile, $muPluginDir) === 0; + } + + return $this->isMu; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Properties/Properties.php b/lib/packages/Inpsyde/Modularity/Properties/Properties.php new file mode 100644 index 000000000..8663d6883 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Properties/Properties.php @@ -0,0 +1,139 @@ + '', + self::PROP_AUTHOR_URI => '', + self::PROP_DESCRIPTION => '', + self::PROP_DOMAIN_PATH => '', + self::PROP_NAME => '', + self::PROP_TEXTDOMAIN => '', + self::PROP_URI => '', + self::PROP_VERSION => '', + self::PROP_REQUIRES_WP => null, + self::PROP_REQUIRES_PHP => null, + self::PROP_TAGS => [], + ]; + + /** + * @param string $key + * @param null $default + * + * @return mixed + */ + public function get(string $key, $default = null); + + /** + * @param string $key + * + * @return bool + */ + public function has(string $key): bool; + + /** + * @return bool + */ + public function isDebug(): bool; + + /** + * @return string + */ + public function baseName(): string; + + /** + * @return string + */ + public function basePath(): string; + + /** + * @return string|null + */ + public function baseUrl(): ?string; + + /** + * @return string + */ + public function author(): string; + + /** + * @return string + */ + public function authorUri(): string; + + /** + * @return string + */ + public function description(): string; + + /** + * @return string + */ + public function textDomain(): string; + + /** + * @return string + */ + public function domainPath(): string; + + /** + * The name of the plugin, theme or library. + * + * @return string + */ + public function name(): string; + + /** + * The home page of the plugin, theme or library. + * @return string + */ + public function uri(): string; + + /** + * @return string + */ + public function version(): string; + + /** + * Optional. Specify the minimum required WordPress version. + * + * @return string|null + */ + public function requiresWp(): ?string; + + /** + * Optional. Specify the minimum required PHP version. + * + * @return string + */ + public function requiresPhp(): ?string; + + /** + * Optional. Currently, only available for Theme and Library. + * Plugins do not have support for "tags"/"keywords" in header. + * + * @link https://developer.wordpress.org/reference/classes/wp_theme/#properties + * @link https://getcomposer.org/doc/04-schema.md#keywords + * + * @return array + */ + public function tags(): array; +} diff --git a/lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php b/lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php new file mode 100644 index 000000000..24a464caa --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php @@ -0,0 +1,131 @@ + 'Author', + self::PROP_AUTHOR_URI => 'AuthorURI', + self::PROP_DESCRIPTION => 'Description', + self::PROP_DOMAIN_PATH => 'DomainPath', + self::PROP_NAME => 'Name', + self::PROP_TEXTDOMAIN => 'TextDomain', + self::PROP_URI => 'ThemeURI', + self::PROP_VERSION => 'Version', + self::PROP_REQUIRES_WP => 'RequiresWP', + self::PROP_REQUIRES_PHP => 'RequiresPHP', + + // additional headers + self::PROP_STATUS => 'Status', + self::PROP_TAGS => 'Tags', + self::PROP_TEMPLATE => 'Template', + ]; + + /** + * @param string $themeDirectory + * + * @return ThemeProperties + */ + public static function new(string $themeDirectory): ThemeProperties + { + return new self($themeDirectory); + } + + /** + * ThemeProperties constructor. + * + * @param string $themeDirectory + */ + protected function __construct(string $themeDirectory) + { + if (!function_exists('wp_get_theme')) { + require_once ABSPATH . 'wp-includes/theme.php'; + } + + $theme = wp_get_theme($themeDirectory); + $properties = Properties::DEFAULT_PROPERTIES; + + foreach (self::HEADERS as $key => $themeKey) { + /** @psalm-suppress DocblockTypeContradiction */ + $properties[$key] = $theme->get($themeKey) ?? ''; + } + + $baseName = $theme->get_stylesheet(); + $basePath = $theme->get_stylesheet_directory(); + $baseUrl = (string) trailingslashit($theme->get_stylesheet_directory_uri()); + + parent::__construct( + $baseName, + $basePath, + $baseUrl, + $properties + ); + } + + /** + * If the theme is published. + * + * @return string + */ + public function status(): string + { + return (string) $this->get(self::PROP_STATUS); + } + + public function template(): string + { + return (string) $this->get(self::PROP_TEMPLATE); + } + + /** + * @return bool + */ + public function isChildTheme(): bool + { + return (bool) $this->template(); + } + + /** + * @return bool + */ + public function isCurrentTheme(): bool + { + return get_stylesheet() === $this->baseName(); + } + + /** + * @return ThemeProperties|null + */ + public function parentThemeProperties(): ?ThemeProperties + { + $template = $this->template(); + if (!$template) { + return null; + } + + $parent = wp_get_theme($template, get_theme_root($template)); + + return static::new($parent->get_template_directory()); + } +} diff --git a/lib/packages/Psr/Container/ContainerExceptionInterface.php b/lib/packages/Psr/Container/ContainerExceptionInterface.php index e36fa7885..78ee05e4a 100644 --- a/lib/packages/Psr/Container/ContainerExceptionInterface.php +++ b/lib/packages/Psr/Container/ContainerExceptionInterface.php @@ -1,7 +1,4 @@ get( 'admin-notices.renderer' ); assert( $renderer instanceof RendererInterface ); @@ -103,13 +109,7 @@ class AdminNotices implements ModuleInterface { PersistentMessage::clear_all(); } ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-api-client/composer.json b/modules/ppcp-api-client/composer.json index 1dca85992..1f4627383 100644 --- a/modules/ppcp-api-client/composer.json +++ b/modules/ppcp-api-client/composer.json @@ -4,7 +4,7 @@ "description": "API client module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-api-client/module.php b/modules/ppcp-api-client/module.php index d01a4253f..401dab48a 100644 --- a/modules/ppcp-api-client/module.php +++ b/modules/ppcp-api-client/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return function (): ModuleInterface { +return function (): ApiModule { return new ApiModule(); }; diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index 8b2f43911..c75c63e6b 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -14,31 +14,37 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; /** * Class ApiModule */ -class ApiModule implements ModuleInterface { +class ApiModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { add_action( 'woocommerce_after_calculate_totals', function ( \WC_Cart $cart ) { @@ -96,13 +102,7 @@ class ApiModule implements ModuleInterface { 10, 2 ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-applepay/assets/images/applepay.png b/modules/ppcp-applepay/assets/images/applepay.png deleted file mode 100644 index d46807d77..000000000 Binary files a/modules/ppcp-applepay/assets/images/applepay.png and /dev/null differ diff --git a/modules/ppcp-applepay/assets/images/applepay.svg b/modules/ppcp-applepay/assets/images/applepay.svg new file mode 100644 index 000000000..42e09634d --- /dev/null +++ b/modules/ppcp-applepay/assets/images/applepay.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/ppcp-applepay/composer.json b/modules/ppcp-applepay/composer.json index d22092e7d..29eb00a9e 100644 --- a/modules/ppcp-applepay/composer.json +++ b/modules/ppcp-applepay/composer.json @@ -4,7 +4,7 @@ "description": "Applepay module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php index 8f9d84b5a..5eb6e47d6 100644 --- a/modules/ppcp-applepay/extensions.php +++ b/modules/ppcp-applepay/extensions.php @@ -18,7 +18,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { // Used in various places to mark fields for the preview button. $apm_name = 'ApplePay'; @@ -101,7 +101,7 @@ return array( 'applepay_button_enabled' => array( 'title' => __( 'Apple Pay Button', 'woocommerce-paypal-payments' ), 'title_html' => sprintf( - '%s', + '%s', $module_url, __( 'Apple Pay', 'woocommerce-paypal-payments' ) ), @@ -155,7 +155,7 @@ return array( 'applepay_button_enabled' => array( 'title' => __( 'Apple Pay Button', 'woocommerce-paypal-payments' ), 'title_html' => sprintf( - '%s', + '%s', $module_url, __( 'Apple Pay', 'woocommerce-paypal-payments' ) ), diff --git a/modules/ppcp-applepay/module.php b/modules/ppcp-applepay/module.php index b8a05dcc9..1ab30942c 100644 --- a/modules/ppcp-applepay/module.php +++ b/modules/ppcp-applepay/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Applepay; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): ApplepayModule { return new ApplepayModule(); }; diff --git a/modules/ppcp-applepay/resources/css/styles.scss b/modules/ppcp-applepay/resources/css/styles.scss index 3402b1a20..d2a0bf675 100644 --- a/modules/ppcp-applepay/resources/css/styles.scss +++ b/modules/ppcp-applepay/resources/css/styles.scss @@ -23,6 +23,11 @@ &.ppcp-button-minicart { --apple-pay-button-display: block; } + + &.ppcp-preview-button.ppcp-button-dummy { + /* URL must specify the correct module-folder! */ + --apm-button-dummy-background: url(../../../ppcp-applepay/assets/images/applepay.png); + } } .wp-block-woocommerce-checkout, .wp-block-woocommerce-cart { diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 1c77a5c45..0f217f606 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -57,6 +57,8 @@ const CONTEXT = { * On a single page, multiple Apple Pay buttons can be displayed, which also means multiple * ApplePayButton instances exist. A typical case is on the product page, where one Apple Pay button * is located inside the minicart-popup, and another pay-now button is in the product context. + * + * TODO - extend from PaymentButton (same as we do in GooglepayButton.js) */ class ApplePayButton { /** @@ -490,6 +492,7 @@ class ApplePayButton { const ppcpStyle = this.ppcpStyle; wrapper.innerHTML = ``; + wrapper.classList.remove( 'ppcp-button-rect', 'ppcp-button-pill' ); wrapper.classList.add( `ppcp-button-${ ppcpStyle.shape }`, 'ppcp-button-apm', diff --git a/modules/ppcp-applepay/resources/js/Preview/ApplePayPreviewButton.js b/modules/ppcp-applepay/resources/js/Preview/ApplePayPreviewButton.js new file mode 100644 index 000000000..2ad3e23f5 --- /dev/null +++ b/modules/ppcp-applepay/resources/js/Preview/ApplePayPreviewButton.js @@ -0,0 +1,54 @@ +import ApplepayButton from '../ApplepayButton'; +import PreviewButton from '../../../../ppcp-button/resources/js/modules/Preview/PreviewButton'; + +/** + * A single Apple Pay preview button instance. + */ +export default class ApplePayPreviewButton extends PreviewButton { + constructor( args ) { + super( args ); + + this.selector = `${ args.selector }ApplePay`; + this.defaultAttributes = { + button: { + type: 'pay', + color: 'black', + lang: 'en', + }, + }; + } + + createButton( buttonConfig ) { + const button = new ApplepayButton( + 'preview', + null, + buttonConfig, + this.ppcpConfig + ); + + button.init( this.apiConfig ); + } + + /** + * Merge form details into the config object for preview. + * Mutates the previewConfig object; no return value. + * @param buttonConfig + * @param ppcpConfig + */ + dynamicPreviewConfig( buttonConfig, ppcpConfig ) { + // The Apple Pay button expects the "wrapper" to be an ID without `#` prefix! + buttonConfig.button.wrapper = buttonConfig.button.wrapper.replace( + /^#/, + '' + ); + + // Merge the current form-values into the preview-button configuration. + if ( ppcpConfig.button ) { + buttonConfig.button.type = ppcpConfig.button.style.type; + buttonConfig.button.color = ppcpConfig.button.style.color; + buttonConfig.button.lang = + ppcpConfig.button.style?.lang || + ppcpConfig.button.style.language; + } + } +} diff --git a/modules/ppcp-applepay/resources/js/Preview/ApplePayPreviewButtonManager.js b/modules/ppcp-applepay/resources/js/Preview/ApplePayPreviewButtonManager.js new file mode 100644 index 000000000..be15166e6 --- /dev/null +++ b/modules/ppcp-applepay/resources/js/Preview/ApplePayPreviewButtonManager.js @@ -0,0 +1,50 @@ +import PreviewButtonManager from '../../../../ppcp-button/resources/js/modules/Preview/PreviewButtonManager'; +import ApplePayPreviewButton from './ApplePayPreviewButton'; + +/** + * Manages all Apple Pay preview buttons on this page. + */ +export default class ApplePayPreviewButtonManager extends PreviewButtonManager { + constructor() { + const args = { + methodName: 'ApplePay', + buttonConfig: window.wc_ppcp_applepay_admin, + }; + + super( args ); + } + + /** + * Responsible for fetching and returning the PayPal configuration object for this payment + * method. + * + * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. + * @return {Promise<{}>} + */ + async fetchConfig( payPal ) { + const apiMethod = payPal?.Applepay()?.config; + + if ( ! apiMethod ) { + this.error( + 'configuration object cannot be retrieved from PayPal' + ); + return {}; + } + + return await apiMethod(); + } + + /** + * This method is responsible for creating a new PreviewButton instance and returning it. + * + * @param {string} wrapperId - CSS ID of the wrapper element. + * @return {ApplePayPreviewButton} + */ + createButtonInstance( wrapperId ) { + return new ApplePayPreviewButton( { + selector: wrapperId, + apiConfig: this.apiConfig, + methodName: this.methodName, + } ); + } +} diff --git a/modules/ppcp-applepay/resources/js/boot-admin.js b/modules/ppcp-applepay/resources/js/boot-admin.js index 81d8c0b7b..e92c03cdf 100644 --- a/modules/ppcp-applepay/resources/js/boot-admin.js +++ b/modules/ppcp-applepay/resources/js/boot-admin.js @@ -1,6 +1,4 @@ -import ApplePayButton from './ApplepayButton'; -import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton'; -import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager'; +import ApplePayPreviewButtonManager from './Preview/ApplePayPreviewButtonManager'; /** * Accessor that creates and returns a single PreviewButtonManager instance. @@ -14,111 +12,5 @@ const buttonManager = () => { return ApplePayPreviewButtonManager.instance; }; -/** - * Manages all Apple Pay preview buttons on this page. - */ -class ApplePayPreviewButtonManager extends PreviewButtonManager { - constructor() { - const args = { - methodName: 'ApplePay', - buttonConfig: window.wc_ppcp_applepay_admin, - }; - - super( args ); - } - - /** - * Responsible for fetching and returning the PayPal configuration object for this payment - * method. - * - * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. - * @return {Promise<{}>} - */ - async fetchConfig( payPal ) { - const apiMethod = payPal?.Applepay()?.config; - - if ( ! apiMethod ) { - this.error( - 'configuration object cannot be retrieved from PayPal' - ); - return {}; - } - - return await apiMethod(); - } - - /** - * This method is responsible for creating a new PreviewButton instance and returning it. - * - * @param {string} wrapperId - CSS ID of the wrapper element. - * @return {ApplePayPreviewButton} - */ - createButtonInstance( wrapperId ) { - return new ApplePayPreviewButton( { - selector: wrapperId, - apiConfig: this.apiConfig, - } ); - } -} - -/** - * A single Apple Pay preview button instance. - */ -class ApplePayPreviewButton extends PreviewButton { - constructor( args ) { - super( args ); - - this.selector = `${ args.selector }ApplePay`; - this.defaultAttributes = { - button: { - type: 'pay', - color: 'black', - lang: 'en', - }, - }; - } - - createNewWrapper() { - const element = super.createNewWrapper(); - element.addClass( 'ppcp-button-applepay' ); - - return element; - } - - createButton( buttonConfig ) { - const button = new ApplePayButton( - 'preview', - null, - buttonConfig, - this.ppcpConfig - ); - - button.init( this.apiConfig ); - } - - /** - * Merge form details into the config object for preview. - * Mutates the previewConfig object; no return value. - * @param buttonConfig - * @param ppcpConfig - */ - dynamicPreviewConfig( buttonConfig, ppcpConfig ) { - // The Apple Pay button expects the "wrapper" to be an ID without `#` prefix! - buttonConfig.button.wrapper = buttonConfig.button.wrapper.replace( - /^#/, - '' - ); - - // Merge the current form-values into the preview-button configuration. - if ( ppcpConfig.button ) { - buttonConfig.button.type = ppcpConfig.button.style.type; - buttonConfig.button.color = ppcpConfig.button.style.color; - buttonConfig.button.lang = - ppcpConfig.button.style?.lang || - ppcpConfig.button.style.language; - } - } -} - // Initialize the preview button manager. buttonManager(); diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index 0b9c15372..1956d1ee0 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -306,7 +306,8 @@ return array( $container->get( 'wcgateway.processor.refunds' ), $container->get( 'wcgateway.transaction-url-provider' ), $container->get( 'session.handler' ), - $container->get( 'applepay.url' ) + $container->get( 'applepay.url' ), + $container->get( 'woocommerce.logger.woocommerce' ) ); }, diff --git a/modules/ppcp-applepay/src/ApplePayGateway.php b/modules/ppcp-applepay/src/ApplePayGateway.php index 1cccda0f2..3ee57ee96 100644 --- a/modules/ppcp-applepay/src/ApplePayGateway.php +++ b/modules/ppcp-applepay/src/ApplePayGateway.php @@ -10,6 +10,7 @@ declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\Applepay; use Exception; +use Psr\Log\LoggerInterface; use WC_Order; use WC_Payment_Gateway; use WooCommerce\PayPalCommerce\Session\SessionHandler; @@ -72,6 +73,13 @@ class ApplePayGateway extends WC_Payment_Gateway { */ private $module_url; + /** + * The logger. + * + * @var LoggerInterface + */ + private $logger; + /** * ApplePayGateway constructor. * @@ -84,6 +92,7 @@ class ApplePayGateway extends WC_Payment_Gateway { * view URL based on order. * @param SessionHandler $session_handler The Session Handler. * @param string $module_url The URL to the module. + * @param LoggerInterface $logger The logger. */ public function __construct( OrderProcessor $order_processor, @@ -91,7 +100,8 @@ class ApplePayGateway extends WC_Payment_Gateway { RefundProcessor $refund_processor, TransactionUrlProvider $transaction_url_provider, SessionHandler $session_handler, - string $module_url + string $module_url, + LoggerInterface $logger ) { $this->id = self::ID; @@ -102,7 +112,7 @@ class ApplePayGateway extends WC_Payment_Gateway { $this->description = $this->get_option( 'description', '' ); $this->module_url = $module_url; - $this->icon = esc_url( $this->module_url ) . 'assets/images/applepay.png'; + $this->icon = esc_url( $this->module_url ) . 'assets/images/applepay.svg'; $this->init_form_fields(); $this->init_settings(); @@ -111,6 +121,7 @@ class ApplePayGateway extends WC_Payment_Gateway { $this->refund_processor = $refund_processor; $this->transaction_url_provider = $transaction_url_provider; $this->session_handler = $session_handler; + $this->logger = $logger; add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php index 878d4b322..18b565544 100644 --- a/modules/ppcp-applepay/src/ApplepayModule.php +++ b/modules/ppcp-applepay/src/ApplepayModule.php @@ -18,30 +18,37 @@ use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice; use WooCommerce\PayPalCommerce\Onboarding\Environment; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class ApplepayModule */ -class ApplepayModule implements ModuleInterface { +class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { $module = $this; // Clears product status when appropriate. @@ -160,14 +167,8 @@ class ApplepayModule implements ModuleInterface { echo '
'; } ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } /** diff --git a/modules/ppcp-axo/composer.json b/modules/ppcp-axo/composer.json index 352c30523..976a4a272 100644 --- a/modules/ppcp-axo/composer.json +++ b/modules/ppcp-axo/composer.json @@ -4,7 +4,7 @@ "description": "Axo module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-axo/extensions.php b/modules/ppcp-axo/extensions.php index 636aee38e..fe67bb3d5 100644 --- a/modules/ppcp-axo/extensions.php +++ b/modules/ppcp-axo/extensions.php @@ -17,7 +17,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager; return array( - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { $insert_after = function( array $array, string $key, array $new ): array { $keys = array_keys( $array ); diff --git a/modules/ppcp-axo/module.php b/modules/ppcp-axo/module.php index 8e0a3064f..4d71cadde 100644 --- a/modules/ppcp-axo/module.php +++ b/modules/ppcp-axo/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Axo; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): AxoModule { return new AxoModule(); }; diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index cce8fbd14..2b554d1bb 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -18,9 +18,10 @@ use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -30,24 +31,28 @@ use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; /** * Class AxoModule */ -class AxoModule implements ModuleInterface { - +class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; use ContextTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { add_filter( 'woocommerce_payment_gateways', @@ -290,6 +295,7 @@ class AxoModule implements ModuleInterface { $endpoint->handle_request(); } ); + return true; } /** @@ -323,14 +329,6 @@ class AxoModule implements ModuleInterface { return $localized_script_data; } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { - } - /** * Condition to evaluate if Credit Card gateway should be hidden. * diff --git a/modules/ppcp-blocks/composer.json b/modules/ppcp-blocks/composer.json index f71b084a2..1811a6aee 100644 --- a/modules/ppcp-blocks/composer.json +++ b/modules/ppcp-blocks/composer.json @@ -4,7 +4,7 @@ "description": "Blocks module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php index 4499c404e..47f9cddc4 100644 --- a/modules/ppcp-blocks/extensions.php +++ b/modules/ppcp-blocks/extensions.php @@ -13,7 +13,7 @@ use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( - 'wcgateway.button.locations' => function ( ContainerInterface $container, array $locations ): array { + 'wcgateway.button.locations' => function ( array $locations, ContainerInterface $container ): array { return array_merge( $locations, array( @@ -22,13 +22,13 @@ return array( ) ); }, - 'wcgateway.settings.pay-later.messaging-locations' => function ( ContainerInterface $container, array $locations ): array { + 'wcgateway.settings.pay-later.messaging-locations' => function ( array $locations, ContainerInterface $container ): array { unset( $locations['checkout-block-express'] ); unset( $locations['cart-block'] ); return $locations; }, - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { $insert_after = function( array $array, string $key, array $new ): array { $keys = array_keys( $array ); $index = array_search( $key, $keys, true ); @@ -72,7 +72,7 @@ return array( ); }, - 'button.pay-now-contexts' => function ( ContainerInterface $container, array $contexts ): array { + 'button.pay-now-contexts' => function ( array $contexts, ContainerInterface $container ): array { if ( ! $container->get( 'blocks.settings.final_review_enabled' ) ) { $contexts[] = 'checkout-block'; $contexts[] = 'cart-block'; @@ -81,7 +81,7 @@ return array( return $contexts; }, - 'button.handle-shipping-in-paypal' => function ( ContainerInterface $container ): bool { + 'button.handle-shipping-in-paypal' => function ( bool $previous, ContainerInterface $container ): bool { return ! $container->get( 'blocks.settings.final_review_enabled' ); }, ); diff --git a/modules/ppcp-blocks/module.php b/modules/ppcp-blocks/module.php index cfb4f43f9..042ab6f56 100644 --- a/modules/ppcp-blocks/module.php +++ b/modules/ppcp-blocks/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Blocks; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): BlocksModule { return new BlocksModule(); }; diff --git a/modules/ppcp-blocks/src/BlocksModule.php b/modules/ppcp-blocks/src/BlocksModule.php index 1dd1560dd..d8363e415 100644 --- a/modules/ppcp-blocks/src/BlocksModule.php +++ b/modules/ppcp-blocks/src/BlocksModule.php @@ -12,29 +12,36 @@ namespace WooCommerce\PayPalCommerce\Blocks; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class BlocksModule */ -class BlocksModule implements ModuleInterface { +class BlocksModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { if ( ! class_exists( 'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType' ) || ! function_exists( 'woocommerce_store_api_register_payment_requirements' ) @@ -54,7 +61,7 @@ class BlocksModule implements ModuleInterface { } ); - return; + return true; } add_action( @@ -118,13 +125,6 @@ class BlocksModule implements ModuleInterface { return $components; } ); - } - - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-button/composer.json b/modules/ppcp-button/composer.json index db97aad22..ef2a9fcf9 100644 --- a/modules/ppcp-button/composer.json +++ b/modules/ppcp-button/composer.json @@ -4,7 +4,7 @@ "description": "Button module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-button/module.php b/modules/ppcp-button/module.php index b2077096e..68f04fe13 100644 --- a/modules/ppcp-button/module.php +++ b/modules/ppcp-button/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Button; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): ButtonModule { return new ButtonModule(); }; diff --git a/modules/ppcp-button/resources/css/hosted-fields.scss b/modules/ppcp-button/resources/css/hosted-fields.scss index 578a70a09..54482b573 100644 --- a/modules/ppcp-button/resources/css/hosted-fields.scss +++ b/modules/ppcp-button/resources/css/hosted-fields.scss @@ -1,11 +1,10 @@ -#payment ul.payment_methods li img.ppcp-card-icon { - padding: 0 0 3px 3px; - max-height: 25px; - display: inline-block; +#payment ul.payment_methods [class*="payment_method_ppcp-"] label img { + max-height: 25px; + display: inline-block; } .payments-sdk-contingency-handler { - z-index: 1000 !important; + z-index: 1000 !important; } .ppcp-credit-card-gateway-form-field-disabled { diff --git a/modules/ppcp-button/resources/js/modules/Helper/LocalStorage.js b/modules/ppcp-button/resources/js/modules/Helper/LocalStorage.js new file mode 100644 index 000000000..65494369e --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Helper/LocalStorage.js @@ -0,0 +1,179 @@ +/* global localStorage */ + +function checkLocalStorageAvailability() { + try { + const testKey = '__ppcp_test__'; + localStorage.setItem( testKey, 'test' ); + localStorage.removeItem( testKey ); + return true; + } catch ( e ) { + return false; + } +} + +function sanitizeKey( name ) { + return name + .toLowerCase() + .trim() + .replace( /[^a-z0-9_-]/g, '_' ); +} + +function deserializeEntry( serialized ) { + try { + const payload = JSON.parse( serialized ); + + return { + data: payload.data, + expires: payload.expires || 0, + }; + } catch ( e ) { + return null; + } +} + +function serializeEntry( data, timeToLive ) { + const payload = { + data, + expires: calculateExpiration( timeToLive ), + }; + + return JSON.stringify( payload ); +} + +function calculateExpiration( timeToLive ) { + return timeToLive ? Date.now() + timeToLive * 1000 : 0; +} + +/** + * A reusable class for handling data storage in the browser's local storage, + * with optional expiration. + * + * Can be extended for module specific logic. + * + * @see GooglePaySession + */ +export class LocalStorage { + /** + * @type {string} + */ + #group = ''; + + /** + * @type {null|boolean} + */ + #canUseLocalStorage = null; + + /** + * @param {string} group - Group name for all storage keys managed by this instance. + */ + constructor( group ) { + this.#group = sanitizeKey( group ) + ':'; + this.#removeExpired(); + } + + /** + * Removes all items in the current group that have reached the expiry date. + */ + #removeExpired() { + if ( ! this.canUseLocalStorage ) { + return; + } + + Object.keys( localStorage ).forEach( ( key ) => { + if ( ! key.startsWith( this.#group ) ) { + return; + } + + const entry = deserializeEntry( localStorage.getItem( key ) ); + if ( entry && entry.expires > 0 && entry.expires < Date.now() ) { + localStorage.removeItem( key ); + } + } ); + } + + /** + * Sanitizes the given entry name and adds the group prefix. + * + * @throws {Error} If the name is empty after sanitization. + * @param {string} name - Entry name. + * @return {string} Prefixed and sanitized entry name. + */ + #entryKey( name ) { + const sanitizedName = sanitizeKey( name ); + + if ( sanitizedName.length === 0 ) { + throw new Error( 'Name cannot be empty after sanitization' ); + } + + return `${ this.#group }${ sanitizedName }`; + } + + /** + * Indicates, whether localStorage is available. + * + * @return {boolean} True means the localStorage API is available. + */ + get canUseLocalStorage() { + if ( null === this.#canUseLocalStorage ) { + this.#canUseLocalStorage = checkLocalStorageAvailability(); + } + + return this.#canUseLocalStorage; + } + + /** + * Stores data in the browser's local storage, with an optional timeout. + * + * @param {string} name - Name of the item in the storage. + * @param {any} data - The data to store. + * @param {number} [timeToLive=0] - Lifespan in seconds. 0 means the data won't expire. + * @throws {Error} If local storage is not available. + */ + set( name, data, timeToLive = 0 ) { + if ( ! this.canUseLocalStorage ) { + throw new Error( 'Local storage is not available' ); + } + + const entry = serializeEntry( data, timeToLive ); + const entryKey = this.#entryKey( name ); + + localStorage.setItem( entryKey, entry ); + } + + /** + * Retrieves previously stored data from the browser's local storage. + * + * @param {string} name - Name of the stored item. + * @return {any|null} The stored data, or null when no valid entry is found or it has expired. + * @throws {Error} If local storage is not available. + */ + get( name ) { + if ( ! this.canUseLocalStorage ) { + throw new Error( 'Local storage is not available' ); + } + + const itemKey = this.#entryKey( name ); + const entry = deserializeEntry( localStorage.getItem( itemKey ) ); + + if ( ! entry ) { + return null; + } + + return entry.data; + } + + /** + * Removes the specified entry from the browser's local storage. + * + * @param {string} name - Name of the stored item. + * @throws {Error} If local storage is not available. + */ + clear( name ) { + if ( ! this.canUseLocalStorage ) { + throw new Error( 'Local storage is not available' ); + } + + const itemKey = this.#entryKey( name ); + localStorage.removeItem( itemKey ); + } +} diff --git a/modules/ppcp-button/resources/js/modules/Helper/PayerData.js b/modules/ppcp-button/resources/js/modules/Helper/PayerData.js index b9b84d14f..5695facb0 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/PayerData.js +++ b/modules/ppcp-button/resources/js/modules/Helper/PayerData.js @@ -1,59 +1,196 @@ -export const payerData = () => { - const payer = PayPalCommerceGateway.payer; +/** + * Name details. + * + * @typedef {Object} NameDetails + * @property {string} [given_name] - First name, e.g. "John". + * @property {string} [surname] - Last name, e.g. "Doe". + */ + +/** + * Postal address details. + * + * @typedef {Object} AddressDetails + * @property {string} [country_code] - Country code (2-letter). + * @property {string} [address_line_1] - Address details, line 1 (street, house number). + * @property {string} [address_line_2] - Address details, line 2. + * @property {string} [admin_area_1] - State or region. + * @property {string} [admin_area_2] - State or region. + * @property {string} [postal_code] - Zip code. + */ + +/** + * Phone details. + * + * @typedef {Object} PhoneDetails + * @property {string} [phone_type] - Type, usually 'HOME' + * @property {{national_number: string}} [phone_number] - Phone number details. + */ + +/** + * Payer details. + * + * @typedef {Object} PayerDetails + * @property {string} [email_address] - Email address for billing communication. + * @property {PhoneDetails} [phone] - Phone number for billing communication. + * @property {NameDetails} [name] - Payer's name. + * @property {AddressDetails} [address] - Postal billing address. + */ + +// Map checkout fields to PayerData object properties. +const FIELD_MAP = { + '#billing_email': [ 'email_address' ], + '#billing_last_name': [ 'name', 'surname' ], + '#billing_first_name': [ 'name', 'given_name' ], + '#billing_country': [ 'address', 'country_code' ], + '#billing_address_1': [ 'address', 'address_line_1' ], + '#billing_address_2': [ 'address', 'address_line_2' ], + '#billing_state': [ 'address', 'admin_area_1' ], + '#billing_city': [ 'address', 'admin_area_2' ], + '#billing_postcode': [ 'address', 'postal_code' ], + '#billing_phone': [ 'phone' ], +}; + +function normalizePayerDetails( details ) { + return { + email_address: details.email_address, + phone: details.phone, + name: { + surname: details.name?.surname, + given_name: details.name?.given_name, + }, + address: { + country_code: details.address?.country_code, + address_line_1: details.address?.address_line_1, + address_line_2: details.address?.address_line_2, + admin_area_1: details.address?.admin_area_1, + admin_area_2: details.address?.admin_area_2, + postal_code: details.address?.postal_code, + }, + }; +} + +function mergePayerDetails( firstPayer, secondPayer ) { + const mergeNestedObjects = ( target, source ) => { + for ( const [ key, value ] of Object.entries( source ) ) { + if ( null !== value && undefined !== value ) { + if ( 'object' === typeof value ) { + target[ key ] = mergeNestedObjects( + target[ key ] || {}, + value + ); + } else { + target[ key ] = value; + } + } + } + return target; + }; + + return mergeNestedObjects( + normalizePayerDetails( firstPayer ), + normalizePayerDetails( secondPayer ) + ); +} + +function getCheckoutBillingDetails() { + const getElementValue = ( selector ) => + document.querySelector( selector )?.value; + + const setNestedValue = ( obj, path, value ) => { + let current = obj; + for ( let i = 0; i < path.length - 1; i++ ) { + current = current[ path[ i ] ] = current[ path[ i ] ] || {}; + } + current[ path[ path.length - 1 ] ] = value; + }; + + const data = {}; + + Object.entries( FIELD_MAP ).forEach( ( [ selector, path ] ) => { + const value = getElementValue( selector ); + if ( value ) { + setNestedValue( data, path, value ); + } + } ); + + if ( data.phone && 'string' === typeof data.phone ) { + data.phone = { + phone_type: 'HOME', + phone_number: { national_number: data.phone }, + }; + } + + return data; +} + +function setCheckoutBillingDetails( payer ) { + const setValue = ( path, field, value ) => { + if ( null === value || undefined === value || ! field ) { + return; + } + + if ( 'phone' === path[ 0 ] && 'object' === typeof value ) { + value = value.phone_number?.national_number; + } + + field.value = value; + }; + + const getNestedValue = ( obj, path ) => + path.reduce( ( current, key ) => current?.[ key ], obj ); + + Object.entries( FIELD_MAP ).forEach( ( [ selector, path ] ) => { + const value = getNestedValue( payer, path ); + const element = document.querySelector( selector ); + + setValue( path, element, value ); + } ); +} + +export function getWooCommerceCustomerDetails() { + // Populated on server-side with details about the current WooCommerce customer. + return window?.PayPalCommerceGateway?.payer; +} + +export function getSessionBillingDetails() { + // Populated by JS via `setSessionBillingDetails()` + return window._PpcpPayerSessionDetails; +} + +/** + * Stores customer details in the current JS context for use in the same request. + * Details that are set are not persisted during navigation. + * + * @param {unknown} details - New payer details + */ +export function setSessionBillingDetails( details ) { + if ( ! details || 'object' !== typeof details ) { + return; + } + + window._PpcpPayerSessionDetails = normalizePayerDetails( details ); +} + +export function payerData() { + const payer = getWooCommerceCustomerDetails() ?? getSessionBillingDetails(); + if ( ! payer ) { return null; } - const phone = - document.querySelector( '#billing_phone' ) || - typeof payer.phone !== 'undefined' - ? { - phone_type: 'HOME', - phone_number: { - national_number: document.querySelector( - '#billing_phone' - ) - ? document.querySelector( '#billing_phone' ).value - : payer.phone.phone_number.national_number, - }, - } - : null; - const payerData = { - email_address: document.querySelector( '#billing_email' ) - ? document.querySelector( '#billing_email' ).value - : payer.email_address, - name: { - surname: document.querySelector( '#billing_last_name' ) - ? document.querySelector( '#billing_last_name' ).value - : payer.name.surname, - given_name: document.querySelector( '#billing_first_name' ) - ? document.querySelector( '#billing_first_name' ).value - : payer.name.given_name, - }, - address: { - country_code: document.querySelector( '#billing_country' ) - ? document.querySelector( '#billing_country' ).value - : payer.address.country_code, - address_line_1: document.querySelector( '#billing_address_1' ) - ? document.querySelector( '#billing_address_1' ).value - : payer.address.address_line_1, - address_line_2: document.querySelector( '#billing_address_2' ) - ? document.querySelector( '#billing_address_2' ).value - : payer.address.address_line_2, - admin_area_1: document.querySelector( '#billing_state' ) - ? document.querySelector( '#billing_state' ).value - : payer.address.admin_area_1, - admin_area_2: document.querySelector( '#billing_city' ) - ? document.querySelector( '#billing_city' ).value - : payer.address.admin_area_2, - postal_code: document.querySelector( '#billing_postcode' ) - ? document.querySelector( '#billing_postcode' ).value - : payer.address.postal_code, - }, - }; + const formData = getCheckoutBillingDetails(); - if ( phone ) { - payerData.phone = phone; + if ( formData ) { + return mergePayerDetails( payer, formData ); } - return payerData; -}; + + return normalizePayerDetails( payer ); +} + +export function setPayerData( payerDetails, updateCheckoutForm = false ) { + setSessionBillingDetails( payerDetails ); + + if ( updateCheckoutForm ) { + setCheckoutBillingDetails( payerDetails ); + } +} diff --git a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js index 54f4e123a..d492802f1 100644 --- a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js +++ b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js @@ -1,31 +1,49 @@ const onApprove = ( context, errorHandler ) => { return ( data, actions ) => { + const canCreateOrder = + ! context.config.vaultingEnabled || data.paymentSource !== 'venmo'; + + const payload = { + nonce: context.config.ajax.approve_order.nonce, + order_id: data.orderID, + funding_source: window.ppcpFundingSource, + should_create_wc_order: canCreateOrder, + }; + + if ( canCreateOrder && data.payer ) { + payload.payer = data.payer; + } + return fetch( context.config.ajax.approve_order.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'same-origin', - body: JSON.stringify( { - nonce: context.config.ajax.approve_order.nonce, - order_id: data.orderID, - funding_source: window.ppcpFundingSource, - should_create_wc_order: - ! context.config.vaultingEnabled || - data.paymentSource !== 'venmo', - } ), + body: JSON.stringify( payload ), } ) .then( ( res ) => { return res.json(); } ) - .then( ( data ) => { - if ( ! data.success ) { - location.href = context.config.redirect; + .then( ( approveData ) => { + if ( ! approveData.success ) { + errorHandler.genericError(); + return actions.restart().catch( ( err ) => { + errorHandler.genericError(); + } ); } - const orderReceivedUrl = data.data?.order_received_url; + const orderReceivedUrl = approveData.data?.order_received_url; - location.href = orderReceivedUrl + /** + * Notice how this step initiates a redirect to a new page using a plain + * URL as new location. This process does not send any details about the + * approved order or billed customer. + * Also, due to the redirect starting _instantly_ there should be no other + * logic scheduled after calling `await onApprove()`; + */ + + window.location.href = orderReceivedUrl ? orderReceivedUrl : context.config.redirect; } ); diff --git a/modules/ppcp-button/resources/js/modules/Preview/DummyPreviewButton.js b/modules/ppcp-button/resources/js/modules/Preview/DummyPreviewButton.js new file mode 100644 index 000000000..c780d188d --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Preview/DummyPreviewButton.js @@ -0,0 +1,52 @@ +import PreviewButton from './PreviewButton'; + +/** + * Dummy preview button, to use in case an APM button cannot be rendered + */ +export default class DummyPreviewButton extends PreviewButton { + #innerEl; + + constructor( args ) { + super( args ); + + this.selector = `${ args.selector }Dummy`; + this.label = args.label || 'Not Available'; + } + + createNewWrapper() { + const wrapper = super.createNewWrapper(); + wrapper.classList.add( 'ppcp-button-apm', 'ppcp-button-dummy' ); + + return wrapper; + } + + createButton( buttonConfig ) { + this.#innerEl?.remove(); + + this.#innerEl = document.createElement( 'div' ); + this.#innerEl.innerHTML = `
${ this.label }
`; + + this._applyStyles( this.ppcpConfig?.button?.style ); + + this.domWrapper.appendChild( this.#innerEl ); + } + + /** + * Applies the button shape (rect/pill) to the dummy button + * + * @param {{shape: string, height: number|null}} style + * @private + */ + _applyStyles( style ) { + this.domWrapper.classList.remove( + 'ppcp-button-pill', + 'ppcp-button-rect' + ); + + this.domWrapper.classList.add( `ppcp-button-${ style.shape }` ); + + if ( style.height ) { + this.domWrapper.style.height = `${ style.height }px`; + } + } +} diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PreviewButton.js b/modules/ppcp-button/resources/js/modules/Preview/PreviewButton.js similarity index 75% rename from modules/ppcp-button/resources/js/modules/Renderer/PreviewButton.js rename to modules/ppcp-button/resources/js/modules/Preview/PreviewButton.js index cec951927..418bb57f5 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PreviewButton.js +++ b/modules/ppcp-button/resources/js/modules/Preview/PreviewButton.js @@ -5,16 +5,21 @@ import merge from 'deepmerge'; */ class PreviewButton { /** - * @param {string} selector - CSS ID of the wrapper, including the `#` - * @param {Object} apiConfig - PayPal configuration object; retrieved via a - * widgetBuilder API method + * @param {string} selector - CSS ID of the wrapper, including the `#` + * @param {Object} apiConfig - PayPal configuration object; retrieved via a + * widgetBuilder API method + * @param {string} methodName - Name of the payment method, e.g. "Google Pay" */ - constructor( { selector, apiConfig } ) { + constructor( { selector, apiConfig, methodName = '' } ) { this.apiConfig = apiConfig; this.defaultAttributes = {}; this.buttonConfig = {}; this.ppcpConfig = {}; this.isDynamic = true; + this.methodName = methodName; + this.methodSlug = this.methodName + .toLowerCase() + .replace( /[^a-z]+/g, '' ); // The selector is usually overwritten in constructor of derived class. this.selector = selector; @@ -26,13 +31,16 @@ class PreviewButton { /** * Creates a new DOM node to contain the preview button. * - * @return {jQuery} Always a single jQuery element with the new DOM node. + * @return {HTMLElement} Always a single jQuery element with the new DOM node. */ createNewWrapper() { + const wrapper = document.createElement( 'div' ); const previewId = this.selector.replace( '#', '' ); - const previewClass = 'ppcp-button-apm'; + const previewClass = `ppcp-preview-button ppcp-button-apm ppcp-button-${ this.methodSlug }`; - return jQuery( `
` ); + wrapper.setAttribute( 'id', previewId ); + wrapper.setAttribute( 'class', previewClass ); + return wrapper; } /** @@ -109,10 +117,12 @@ class PreviewButton { console.error( 'Skip render, button is not configured yet' ); return; } + this.domWrapper = this.createNewWrapper(); - this.domWrapper.insertAfter( this.wrapper ); + this._insertWrapper(); } else { - this.domWrapper.empty().show(); + this._emptyWrapper(); + this._showWrapper(); } this.isVisible = true; @@ -151,16 +161,38 @@ class PreviewButton { * Using a timeout here will make the button visible again at the end of the current * event queue. */ - setTimeout( () => this.domWrapper.show() ); + setTimeout( () => this._showWrapper() ); } remove() { this.isVisible = false; if ( this.domWrapper ) { - this.domWrapper.hide().empty(); + this._hideWrapper(); + this._emptyWrapper(); } } + + _showWrapper() { + this.domWrapper.style.display = ''; + } + + _hideWrapper() { + this.domWrapper.style.display = 'none'; + } + + _emptyWrapper() { + this.domWrapper.innerHTML = ''; + } + + _insertWrapper() { + const wrapperElement = document.querySelector( this.wrapper ); + + wrapperElement.parentNode.insertBefore( + this.domWrapper, + wrapperElement.nextSibling + ); + } } export default PreviewButton; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PreviewButtonManager.js b/modules/ppcp-button/resources/js/modules/Preview/PreviewButtonManager.js similarity index 91% rename from modules/ppcp-button/resources/js/modules/Renderer/PreviewButtonManager.js rename to modules/ppcp-button/resources/js/modules/Preview/PreviewButtonManager.js index 1ea2f04bc..fbc958a0c 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PreviewButtonManager.js +++ b/modules/ppcp-button/resources/js/modules/Preview/PreviewButtonManager.js @@ -1,7 +1,8 @@ import { loadCustomScript } from '@paypal/paypal-js'; -import widgetBuilder from './WidgetBuilder'; +import widgetBuilder from '../Renderer/WidgetBuilder'; import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce'; import ConsoleLogger from '../../../../../ppcp-wc-gateway/resources/js/helper/ConsoleLogger'; +import DummyPreviewButton from './DummyPreviewButton'; /** * Manages all PreviewButton instances of a certain payment method on the page. @@ -26,6 +27,13 @@ class PreviewButtonManager { */ #onInit; + /** + * Initialize the new PreviewButtonManager. + * + * @param {string} methodName - Name of the payment method, e.g. "Google Pay" + * @param {Object} buttonConfig + * @param {Object} defaultAttributes + */ constructor( { methodName, buttonConfig, defaultAttributes } ) { // Define the payment method name in the derived class. this.methodName = methodName; @@ -102,27 +110,15 @@ class PreviewButtonManager { * * This dummy is only visible on the admin side, and not rendered on the front-end. * - * @todo Consider refactoring this into a new class that extends the PreviewButton class. * @param {string} wrapperId * @return {any} */ - createDummy( wrapperId ) { - const elButton = document.createElement( 'div' ); - elButton.classList.add( 'ppcp-button-apm', 'ppcp-button-dummy' ); - elButton.innerHTML = `${ - this.apiError ?? 'Not Available' - }`; - - document.querySelector( wrapperId ).appendChild( elButton ); - - const instDummy = { - setDynamic: () => instDummy, - setPpcpConfig: () => instDummy, - render: () => {}, - remove: () => {}, - }; - - return instDummy; + createDummyButtonInstance( wrapperId ) { + return new DummyPreviewButton( { + selector: wrapperId, + label: this.apiError, + methodName: this.methodName, + } ); } registerEventListeners() { @@ -319,6 +315,17 @@ class PreviewButtonManager { this.log( 'configureAllButtons', ppcpConfig ); Object.entries( this.buttons ).forEach( ( [ id, button ] ) => { + const limitWrapper = ppcpConfig.button?.wrapper; + + /** + * When the ppcpConfig object specifies a button wrapper, then ensure to limit preview + * changes to this individual wrapper. If no button wrapper is defined, the + * configuration is relevant for all buttons on the page. + */ + if ( limitWrapper && button.wrapper !== limitWrapper ) { + return; + } + this._configureButton( id, { ...ppcpConfig, button: { @@ -349,12 +356,11 @@ class PreviewButtonManager { let newInst; if ( this.apiConfig && 'object' === typeof this.apiConfig ) { - newInst = this.createButtonInstance( id ).setButtonConfig( - this.buttonConfig - ); + newInst = this.createButtonInstance( id ); } else { - newInst = this.createDummy( id ); + newInst = this.createDummyButtonInstance( id ); } + newInst.setButtonConfig( this.buttonConfig ); this.buttons[ id ] = newInst; } diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js index 8b5dcd3e9..3ce35c9b5 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js @@ -444,6 +444,17 @@ export default class PaymentButton { return this.wrappers.Default; } + /** + * Whether the button is placed inside a classic gateway context. + * + * Classic gateway contexts are: Classic checkout, Pay for Order page. + * + * @return {boolean} True indicates, the button is located inside a classic gateway. + */ + get isInsideClassicGateway() { + return PaymentContext.Gateways.includes( this.context ); + } + /** * Determines if the current payment button should be rendered as a stand-alone gateway. * The return value `false` usually means, that the payment button is bundled with all available @@ -456,18 +467,23 @@ export default class PaymentButton { get isSeparateGateway() { return ( this.#buttonConfig.is_wc_gateway_enabled && - PaymentContext.Gateways.includes( this.context ) + this.isInsideClassicGateway ); } /** * Whether the currently selected payment gateway is set to the payment method. * - * Only relevant on checkout pages, when `this.isSeparateGateway` is true. + * Only relevant on checkout pages where "classic" payment gateways are rendered. * * @return {boolean} True means that this payment method is selected as current gateway. */ get isCurrentGateway() { + if ( ! this.isInsideClassicGateway ) { + // This means, the button's visibility is managed by another script. + return true; + } + /* * We need to rely on `getCurrentPaymentMethod()` here, as the `CheckoutBootstrap.js` * module fires the "ButtonEvents.RENDER" event before any PaymentButton instances are @@ -609,6 +625,15 @@ export default class PaymentButton { this.#logger.error( ...args ); } + /** + * Open or close a log-group + * + * @param {?string} [label=null] Group label. + */ + logGroup( label = null ) { + this.#logger.group( label ); + } + /** * Determines if the current button instance has valid and complete configuration details. * Used during initialization to decide if the button can be initialized or should be skipped. @@ -683,7 +708,7 @@ export default class PaymentButton { } ); // Events relevant for buttons inside a payment gateway. - if ( PaymentContext.Gateways.includes( this.context ) ) { + if ( this.isInsideClassicGateway ) { const parentMethod = this.isSeparateGateway ? this.methodId : PaymentMethods.PAYPAL; @@ -728,12 +753,11 @@ export default class PaymentButton { * Only relevant on the checkout page, i.e., when `this.isSeparateGateway` is `true` */ showPaymentGateway() { - if ( this.#gatewayInitialized ) { - return; - } - this.#gatewayInitialized = true; - - if ( ! this.isSeparateGateway || ! this.isEligible ) { + if ( + this.#gatewayInitialized || + ! this.isSeparateGateway || + ! this.isEligible + ) { return; } @@ -749,6 +773,7 @@ export default class PaymentButton { .forEach( ( el ) => el.remove() ); this.log( 'Show gateway' ); + this.#gatewayInitialized = true; // This code runs only once, during button initialization, and fixes the initial visibility. this.isVisible = this.isCurrentGateway; diff --git a/modules/ppcp-button/src/ButtonModule.php b/modules/ppcp-button/src/ButtonModule.php index 67dec01f3..cd2c8d381 100644 --- a/modules/ppcp-button/src/ButtonModule.php +++ b/modules/ppcp-button/src/ButtonModule.php @@ -14,8 +14,6 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint; @@ -23,29 +21,36 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\StartPayPalVaultingEndpoint; use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class ButtonModule */ -class ButtonModule implements ModuleInterface { - +class ButtonModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { add_action( 'wp', @@ -91,6 +96,8 @@ class ButtonModule implements ModuleInterface { ); $this->register_ajax_endpoints( $c ); + + return true; } /** @@ -211,12 +218,4 @@ class ButtonModule implements ModuleInterface { } ); } - - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { - } } diff --git a/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php b/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php index 36069e463..a96302e65 100644 --- a/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php @@ -85,7 +85,8 @@ class SimulateCartEndpoint extends AbstractCartEndpoint { $this->add_products( $products ); $this->cart->calculate_totals(); - $total = (float) $this->cart->get_total( 'numeric' ); + $total = (float) $this->cart->get_total( 'numeric' ); + $shipping_fee = (float) $this->cart->get_shipping_total(); $this->restore_real_cart(); @@ -113,7 +114,7 @@ class SimulateCartEndpoint extends AbstractCartEndpoint { wp_send_json_success( array( 'total' => $total, - 'total_str' => ( new Money( $total, $currency_code ) )->value_str(), + 'shipping_fee' => $shipping_fee, 'currency_code' => $currency_code, 'country_code' => $shop_country_code, 'funding' => array( diff --git a/modules/ppcp-card-fields/composer.json b/modules/ppcp-card-fields/composer.json index ad57a2296..bb63b27e7 100644 --- a/modules/ppcp-card-fields/composer.json +++ b/modules/ppcp-card-fields/composer.json @@ -4,7 +4,7 @@ "description": "Advanced Checkout Card Fields module", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-card-fields/module.php b/modules/ppcp-card-fields/module.php index 088900ab9..94cc0782f 100644 --- a/modules/ppcp-card-fields/module.php +++ b/modules/ppcp-card-fields/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\CardFields; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): CardFieldsModule { return new CardFieldsModule(); }; diff --git a/modules/ppcp-card-fields/src/CardFieldsModule.php b/modules/ppcp-card-fields/src/CardFieldsModule.php index b499891a0..b76028c99 100644 --- a/modules/ppcp-card-fields/src/CardFieldsModule.php +++ b/modules/ppcp-card-fields/src/CardFieldsModule.php @@ -9,9 +9,10 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\CardFields; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -19,30 +20,35 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class CardFieldsModule */ -class CardFieldsModule implements ModuleInterface { +class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { if ( ! $c->get( 'card-fields.eligible' ) ) { - return; + return true; } $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); if ( ! $settings->has( 'dcc_enabled' ) || ! $settings->get( 'dcc_enabled' ) ) { - return; + return true; } /** @@ -137,5 +143,7 @@ class CardFieldsModule implements ModuleInterface { 10, 2 ); + + return true; } } diff --git a/modules/ppcp-compat/composer.json b/modules/ppcp-compat/composer.json index eb3c1d35f..1ee2832ab 100644 --- a/modules/ppcp-compat/composer.json +++ b/modules/ppcp-compat/composer.json @@ -4,7 +4,7 @@ "description": "Compatibility module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-compat/module.php b/modules/ppcp-compat/module.php index 2f9ccf4b0..50c3e58d2 100644 --- a/modules/ppcp-compat/module.php +++ b/modules/ppcp-compat/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Compat; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): CompatModule { return new CompatModule(); }; diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 5c53aa7dc..2b40354d2 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -14,9 +14,10 @@ use Psr\Log\LoggerInterface; use WC_Cart; use WC_Order; use WC_Order_Item_Product; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Compat\Assets\CompatAssets; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; @@ -26,17 +27,21 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class CompatModule */ -class CompatModule implements ModuleInterface { +class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** - * Setup the compatibility module. - * - * @return ServiceProviderInterface + * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; + } + + /** + * {@inheritDoc} + */ + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; } /** @@ -44,7 +49,7 @@ class CompatModule implements ModuleInterface { * * @throws NotFoundException */ - public function run( ContainerInterface $c ): void { + public function run( ContainerInterface $c ): bool { $this->initialize_ppec_compat_layer( $c ); $this->initialize_tracking_compat_layer( $c ); @@ -73,14 +78,8 @@ class CompatModule implements ModuleInterface { if ( $is_wc_bookings_active ) { $this->initialize_wc_bookings_compat_layer( $logger ); } - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } /** diff --git a/modules/ppcp-googlepay/assets/images/googlepay.png b/modules/ppcp-googlepay/assets/images/googlepay.png deleted file mode 100644 index b264fd0ee..000000000 Binary files a/modules/ppcp-googlepay/assets/images/googlepay.png and /dev/null differ diff --git a/modules/ppcp-googlepay/assets/images/googlepay.svg b/modules/ppcp-googlepay/assets/images/googlepay.svg new file mode 100644 index 000000000..0abef7bce --- /dev/null +++ b/modules/ppcp-googlepay/assets/images/googlepay.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + diff --git a/modules/ppcp-googlepay/composer.json b/modules/ppcp-googlepay/composer.json index 81698b434..5a4502b0b 100644 --- a/modules/ppcp-googlepay/composer.json +++ b/modules/ppcp-googlepay/composer.json @@ -4,7 +4,7 @@ "description": "Googlepay module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-googlepay/extensions.php b/modules/ppcp-googlepay/extensions.php index df012fb8a..20b19510c 100644 --- a/modules/ppcp-googlepay/extensions.php +++ b/modules/ppcp-googlepay/extensions.php @@ -18,7 +18,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { // Used in various places to mark fields for the preview button. $apm_name = 'GooglePay'; @@ -72,7 +72,7 @@ return array( 'googlepay_button_enabled' => array( 'title' => __( 'Google Pay Button', 'woocommerce-paypal-payments' ), 'title_html' => sprintf( - '%s', + '%s', $module_url, __( 'Google Pay', 'woocommerce-paypal-payments' ) ), @@ -117,7 +117,7 @@ return array( 'googlepay_button_enabled' => array( 'title' => __( 'Google Pay Button', 'woocommerce-paypal-payments' ), 'title_html' => sprintf( - '%s', + '%s', $module_url, __( 'Google Pay', 'woocommerce-paypal-payments' ) ), diff --git a/modules/ppcp-googlepay/module.php b/modules/ppcp-googlepay/module.php index 2e146435b..cfa949499 100644 --- a/modules/ppcp-googlepay/module.php +++ b/modules/ppcp-googlepay/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Googlepay; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): GooglepayModule { return new GooglepayModule(); }; diff --git a/modules/ppcp-googlepay/resources/css/styles.scss b/modules/ppcp-googlepay/resources/css/styles.scss index 93cb05f47..3c6fe8912 100644 --- a/modules/ppcp-googlepay/resources/css/styles.scss +++ b/modules/ppcp-googlepay/resources/css/styles.scss @@ -13,6 +13,11 @@ outline-offset: -1px; border-radius: var(--apm-button-border-radius); } + + &.ppcp-preview-button.ppcp-button-dummy { + /* URL must specify the correct module-folder! */ + --apm-button-dummy-background: url(../../../ppcp-googlepay/assets/images/googlepay.png); + } } .wp-block-woocommerce-checkout, .wp-block-woocommerce-cart { diff --git a/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js b/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js index d61b674a7..d49bee615 100644 --- a/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js @@ -1,5 +1,6 @@ import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler'; import CartActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler'; +import TransactionInfo from '../Helper/TransactionInfo'; class BaseHandler { constructor( buttonConfig, ppcpConfig, externalHandler ) { @@ -34,13 +35,14 @@ class BaseHandler { // handle script reload const data = result.data; + const transaction = new TransactionInfo( + data.total, + data.shipping_fee, + data.currency_code, + data.country_code + ); - resolve( { - countryCode: data.country_code, - currencyCode: data.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: data.total_str, - } ); + resolve( transaction ); } ); } ); } diff --git a/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js b/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js index 79de6b39d..81d60b078 100644 --- a/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js @@ -1,6 +1,7 @@ import Spinner from '../../../../ppcp-button/resources/js/modules/Helper/Spinner'; import BaseHandler from './BaseHandler'; import CheckoutActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler'; +import TransactionInfo from '../Helper/TransactionInfo'; class PayNowHandler extends BaseHandler { validateContext() { @@ -14,12 +15,14 @@ class PayNowHandler extends BaseHandler { return new Promise( async ( resolve, reject ) => { const data = this.ppcpConfig.pay_now; - resolve( { - countryCode: data.country_code, - currencyCode: data.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: data.total_str, - } ); + const transaction = new TransactionInfo( + data.total, + data.shipping_fee, + data.currency_code, + data.country_code + ); + + resolve( transaction ); } ); } diff --git a/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js index a8aa6e8bd..670b9a7c0 100644 --- a/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js @@ -3,6 +3,7 @@ import SimulateCart from '../../../../ppcp-button/resources/js/modules/Helper/Si import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler'; import UpdateCart from '../../../../ppcp-button/resources/js/modules/Helper/UpdateCart'; import BaseHandler from './BaseHandler'; +import TransactionInfo from '../Helper/TransactionInfo'; class SingleProductHandler extends BaseHandler { validateContext() { @@ -42,12 +43,14 @@ class SingleProductHandler extends BaseHandler { this.ppcpConfig.ajax.simulate_cart.endpoint, this.ppcpConfig.ajax.simulate_cart.nonce ).simulate( ( data ) => { - resolve( { - countryCode: data.country_code, - currencyCode: data.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: data.total_str, - } ); + const transaction = new TransactionInfo( + data.total, + data.shipping_fee, + data.currency_code, + data.country_code + ); + + resolve( transaction ); }, products ); } ); } diff --git a/modules/ppcp-googlepay/resources/js/ContextBootstrap/CheckoutBootstrap.js b/modules/ppcp-googlepay/resources/js/ContextBootstrap/CheckoutBootstrap.js new file mode 100644 index 000000000..1e1933a10 --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/ContextBootstrap/CheckoutBootstrap.js @@ -0,0 +1,102 @@ +import { GooglePayStorage } from '../Helper/GooglePayStorage'; +import { + getWooCommerceCustomerDetails, + setPayerData, +} from '../../../../ppcp-button/resources/js/modules/Helper/PayerData'; + +const CHECKOUT_FORM_SELECTOR = 'form.woocommerce-checkout'; + +export class CheckoutBootstrap { + /** + * @type {GooglePayStorage} + */ + #storage; + + /** + * @type {HTMLFormElement|null} + */ + #checkoutForm; + + /** + * @param {GooglePayStorage} storage + */ + constructor( storage ) { + this.#storage = storage; + this.#checkoutForm = CheckoutBootstrap.getCheckoutForm(); + } + + /** + * Indicates if the current page contains a checkout form. + * + * @return {boolean} True if a checkout form is present. + */ + static isPageWithCheckoutForm() { + return null !== CheckoutBootstrap.getCheckoutForm(); + } + + /** + * Retrieves the WooCommerce checkout form element. + * + * @return {HTMLFormElement|null} The form, or null if not a checkout page. + */ + static getCheckoutForm() { + return document.querySelector( CHECKOUT_FORM_SELECTOR ); + } + + /** + * Returns the WooCommerce checkout form element. + * + * @return {HTMLFormElement|null} The form, or null if not a checkout page. + */ + get checkoutForm() { + return this.#checkoutForm; + } + + /** + * Initializes the checkout process. + * + * @throws {Error} If called on a page without a checkout form. + */ + init() { + if ( ! this.#checkoutForm ) { + throw new Error( + 'Checkout form not found. Cannot initialize CheckoutBootstrap.' + ); + } + + this.#populateCheckoutFields(); + } + + /** + * Populates checkout fields with stored or customer data. + */ + #populateCheckoutFields() { + const loggedInData = getWooCommerceCustomerDetails(); + + if ( loggedInData ) { + // If customer is logged in, we use the details from the customer profile. + return; + } + + const billingData = this.#storage.getPayer(); + + if ( ! billingData ) { + return; + } + + setPayerData( billingData, true ); + this.checkoutForm.addEventListener( + 'submit', + this.#onFormSubmit.bind( this ) + ); + } + + /** + * Clean-up when checkout form is submitted. + * + * Immediately removes the payer details from the localStorage. + */ + #onFormSubmit() { + this.#storage.clearPayer(); + } +} diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 4b267c4e7..d4d9df55f 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -5,7 +5,10 @@ import { import PaymentButton from '../../../ppcp-button/resources/js/modules/Renderer/PaymentButton'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; +import TransactionInfo from './Helper/TransactionInfo'; import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; +import { setPayerData } from '../../../ppcp-button/resources/js/modules/Helper/PayerData'; +import moduleStorage from './Helper/GooglePayStorage'; /** * Plugin-specific styling. @@ -39,11 +42,17 @@ import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper * * @see https://developers.google.com/pay/api/web/reference/client * @typedef {Object} PaymentsClient - * @property {Function} createButton - The convenience method is used to generate a Google Pay payment button styled with the latest Google Pay branding for insertion into a webpage. - * @property {Function} isReadyToPay - Use the isReadyToPay(isReadyToPayRequest) method to determine a user's ability to return a form of payment from the Google Pay API. - * @property {Function} loadPaymentData - This method presents a Google Pay payment sheet that allows selection of a payment method and optionally configured parameters - * @property {Function} onPaymentAuthorized - This method is called when a payment is authorized in the payment sheet. - * @property {Function} onPaymentDataChanged - This method handles payment data changes in the payment sheet such as shipping address and shipping options. + * @property {Function} createButton - The convenience method is used to + * generate a Google Pay payment button styled with the latest Google Pay branding for + * insertion into a webpage. + * @property {Function} isReadyToPay - Use the isReadyToPay(isReadyToPayRequest) + * method to determine a user's ability to return a form of payment from the Google Pay API. + * @property {(Object) => Promise} loadPaymentData - This method presents a Google Pay payment + * sheet that allows selection of a payment method and optionally configured parameters + * @property {Function} onPaymentAuthorized - This method is called when a payment is + * authorized in the payment sheet. + * @property {Function} onPaymentDataChanged - This method handles payment data changes + * in the payment sheet such as shipping address and shipping options. */ /** @@ -53,14 +62,40 @@ import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper * @typedef {Object} TransactionInfo * @property {string} currencyCode - Required. The ISO 4217 alphabetic currency code. * @property {string} countryCode - Optional. required for EEA countries, - * @property {string} transactionId - Optional. A unique ID that identifies a facilitation attempt. Highly encouraged for troubleshooting. - * @property {string} totalPriceStatus - Required. [ESTIMATED|FINAL] The status of the total price used: - * @property {string} totalPrice - Required. Total monetary value of the transaction with an optional decimal precision of two decimal places. - * @property {Array} displayItems - Optional. A list of cart items shown in the payment sheet (e.g. subtotals, sales taxes, shipping charges, discounts etc.). - * @property {string} totalPriceLabel - Optional. Custom label for the total price within the display items. - * @property {string} checkoutOption - Optional. Affects the submit button text displayed in the Google Pay payment sheet. + * @property {string} transactionId - Optional. A unique ID that identifies a facilitation + * attempt. Highly encouraged for troubleshooting. + * @property {string} totalPriceStatus - Required. [ESTIMATED|FINAL] The status of the total price + * used: + * @property {string} totalPrice - Required. Total monetary value of the transaction with an + * optional decimal precision of two decimal places. + * @property {Array} displayItems - Optional. A list of cart items shown in the payment sheet + * (e.g. subtotals, sales taxes, shipping charges, discounts etc.). + * @property {string} totalPriceLabel - Optional. Custom label for the total price within the + * display items. + * @property {string} checkoutOption - Optional. Affects the submit button text displayed in the + * Google Pay payment sheet. */ +function payerDataFromPaymentResponse( response ) { + const raw = response?.paymentMethodData?.info?.billingAddress; + + return { + email_address: response?.email, + name: { + given_name: raw.name.split( ' ' )[ 0 ], // Assuming first name is the first part + surname: raw.name.split( ' ' ).slice( 1 ).join( ' ' ), // Assuming last name is the rest + }, + address: { + country_code: raw.countryCode, + address_line_1: raw.address1, + address_line_2: raw.address2, + admin_area_1: raw.administrativeArea, + admin_area_2: raw.locality, + postal_code: raw.postalCode, + }, + }; +} + class GooglepayButton extends PaymentButton { /** * @inheritDoc @@ -78,7 +113,7 @@ class GooglepayButton extends PaymentButton { #paymentsClient = null; /** - * Details about the processed transaction. + * Details about the processed transaction, provided to the Google SDK. * * @type {?TransactionInfo} */ @@ -388,12 +423,14 @@ class GooglepayButton extends PaymentButton { const initiatePaymentRequest = () => { window.ppcpFundingSource = 'googlepay'; const paymentDataRequest = this.paymentDataRequest(); + this.log( 'onButtonClick: paymentDataRequest', paymentDataRequest, this.context ); - this.paymentsClient.loadPaymentData( paymentDataRequest ); + + return this.paymentsClient.loadPaymentData( paymentDataRequest ); }; const validateForm = () => { @@ -434,28 +471,24 @@ class GooglepayButton extends PaymentButton { apiVersionMinor: 0, }; - const googlePayConfig = this.googlePayConfig; - const paymentDataRequest = Object.assign( {}, baseRequest ); - paymentDataRequest.allowedPaymentMethods = - googlePayConfig.allowedPaymentMethods; - paymentDataRequest.transactionInfo = this.transactionInfo; - paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; + const useShippingCallback = this.requiresShipping; + const callbackIntents = [ 'PAYMENT_AUTHORIZATION' ]; - if ( this.requiresShipping ) { - paymentDataRequest.callbackIntents = [ - 'SHIPPING_ADDRESS', - 'SHIPPING_OPTION', - 'PAYMENT_AUTHORIZATION', - ]; - paymentDataRequest.shippingAddressRequired = true; - paymentDataRequest.shippingAddressParameters = - this.shippingAddressParameters(); - paymentDataRequest.shippingOptionRequired = true; - } else { - paymentDataRequest.callbackIntents = [ 'PAYMENT_AUTHORIZATION' ]; + if ( useShippingCallback ) { + callbackIntents.push( 'SHIPPING_ADDRESS', 'SHIPPING_OPTION' ); } - return paymentDataRequest; + return { + ...baseRequest, + allowedPaymentMethods: this.googlePayConfig.allowedPaymentMethods, + transactionInfo: this.transactionInfo.finalObject, + merchantInfo: this.googlePayConfig.merchantInfo, + callbackIntents, + emailRequired: true, + shippingAddressRequired: useShippingCallback, + shippingOptionRequired: useShippingCallback, + shippingAddressParameters: this.shippingAddressParameters(), + }; } //------------------------ @@ -481,6 +514,16 @@ class GooglepayButton extends PaymentButton { ).update( paymentData ); const transactionInfo = this.transactionInfo; + // Check, if the current context uses the WC cart. + const hasRealCart = [ + 'checkout-block', + 'checkout', + 'cart-block', + 'cart', + 'mini-cart', + 'pay-now', + ].includes( this.context ); + this.log( 'onPaymentDataChanged:updatedData', updatedData ); this.log( 'onPaymentDataChanged:transactionInfo', @@ -489,7 +532,6 @@ class GooglepayButton extends PaymentButton { updatedData.country_code = transactionInfo.countryCode; updatedData.currency_code = transactionInfo.currencyCode; - updatedData.total_str = transactionInfo.totalPrice; // Handle unserviceable address. if ( ! updatedData.shipping_options?.shippingOptions?.length ) { @@ -499,20 +541,37 @@ class GooglepayButton extends PaymentButton { return; } - switch ( paymentData.callbackTrigger ) { - case 'INITIALIZE': - case 'SHIPPING_ADDRESS': - paymentDataRequestUpdate.newShippingOptionParameters = - updatedData.shipping_options; - paymentDataRequestUpdate.newTransactionInfo = - this.calculateNewTransactionInfo( updatedData ); - break; - case 'SHIPPING_OPTION': - paymentDataRequestUpdate.newTransactionInfo = - this.calculateNewTransactionInfo( updatedData ); - break; + if ( + [ 'INITIALIZE', 'SHIPPING_ADDRESS' ].includes( + paymentData.callbackTrigger + ) + ) { + paymentDataRequestUpdate.newShippingOptionParameters = + this.sanitizeShippingOptions( + updatedData.shipping_options + ); } + if ( updatedData.total && hasRealCart ) { + transactionInfo.setTotal( + updatedData.total, + updatedData.shipping_fee + ); + + // This page contains a real cart and potentially a form for shipping options. + this.syncShippingOptionWithForm( + paymentData?.shippingOptionData?.id + ); + } else { + transactionInfo.shippingFee = this.getShippingCosts( + paymentData?.shippingOptionData?.id, + updatedData.shipping_options + ); + } + + paymentDataRequestUpdate.newTransactionInfo = + this.calculateNewTransactionInfo( transactionInfo ); + resolve( paymentDataRequestUpdate ); } catch ( error ) { this.error( 'Error during onPaymentDataChanged:', error ); @@ -521,6 +580,76 @@ class GooglepayButton extends PaymentButton { } ); } + /** + * Google Pay throws an error, when the shippingOptions entries contain + * custom properties. This function strips unsupported properties from the + * provided ajax response. + * + * @param {Object} responseData Data returned from the ajax endpoint. + * @return {Object} Sanitized object. + */ + sanitizeShippingOptions( responseData ) { + // Sanitize the shipping options. + const cleanOptions = responseData.shippingOptions.map( ( item ) => ( { + id: item.id, + label: item.label, + description: item.description, + } ) ); + + // Ensure that the default option is valid. + let defaultOptionId = responseData.defaultSelectedOptionId; + if ( ! cleanOptions.some( ( item ) => item.id === defaultOptionId ) ) { + defaultOptionId = cleanOptions[ 0 ].id; + } + + return { + defaultSelectedOptionId: defaultOptionId, + shippingOptions: cleanOptions, + }; + } + + /** + * Returns the shipping costs as numeric value. + * + * TODO - Move this to the PaymentButton base class + * + * @param {string} shippingId - The shipping method ID. + * @param {Object} shippingData - The PaymentDataRequest object that + * contains shipping options. + * @param {Array} shippingData.shippingOptions + * @param {string} shippingData.defaultSelectedOptionId + * + * @return {number} The shipping costs. + */ + getShippingCosts( + shippingId, + { shippingOptions = [], defaultSelectedOptionId = '' } = {} + ) { + if ( ! shippingOptions?.length ) { + this.log( 'Cannot calculate shipping cost: No Shipping Options' ); + return 0; + } + + const findOptionById = ( id ) => + shippingOptions.find( ( option ) => option.id === id ); + + const getValidShippingId = () => { + if ( + 'shipping_option_unselected' === shippingId || + ! findOptionById( shippingId ) + ) { + // Entered on initial call, and when changing the shipping country. + return defaultSelectedOptionId; + } + + return shippingId; + }; + + const currentOption = findOptionById( getValidShippingId() ); + + return Number( currentOption?.cost ) || 0; + } + unserviceableShippingAddressError() { return { reason: 'SHIPPING_ADDRESS_UNSERVICEABLE', @@ -529,13 +658,14 @@ class GooglepayButton extends PaymentButton { }; } - calculateNewTransactionInfo( updatedData ) { - return { - countryCode: updatedData.country_code, - currencyCode: updatedData.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: updatedData.total_str, - }; + /** + * Recalculates and returns the plain transaction info object. + * + * @param {TransactionInfo} transactionInfo - Internal transactionInfo instance. + * @return {{totalPrice: string, countryCode: string, totalPriceStatus: string, currencyCode: string}} Updated details. + */ + calculateNewTransactionInfo( transactionInfo ) { + return transactionInfo.finalObject; } //------------------------ @@ -543,83 +673,111 @@ class GooglepayButton extends PaymentButton { //------------------------ onPaymentAuthorized( paymentData ) { - this.log( 'onPaymentAuthorized' ); + this.log( 'onPaymentAuthorized', paymentData ); + return this.processPayment( paymentData ); } async processPayment( paymentData ) { - this.log( 'processPayment' ); + this.logGroup( 'processPayment' ); - return new Promise( async ( resolve, reject ) => { - try { - const id = await this.contextHandler.createOrder(); + const payer = payerDataFromPaymentResponse( paymentData ); - this.log( 'processPayment: createOrder', id ); + const paymentError = ( reason ) => { + this.error( reason ); - const confirmOrderResponse = await widgetBuilder.paypal - .Googlepay() - .confirmOrder( { - orderId: id, - paymentMethodData: paymentData.paymentMethodData, - } ); + return this.processPaymentResponse( + 'ERROR', + 'PAYMENT_AUTHORIZATION', + reason + ); + }; - this.log( - 'processPayment: confirmOrder', - confirmOrderResponse - ); + const checkPayPalApproval = async ( orderId ) => { + const confirmationData = { + orderId, + paymentMethodData: paymentData.paymentMethodData, + }; - /** Capture the Order on the Server */ - if ( confirmOrderResponse.status === 'APPROVED' ) { - let approveFailed = false; - await this.contextHandler.approveOrder( - { - orderID: id, - }, - { - // actions mock object. - restart: () => - new Promise( ( resolve, reject ) => { - approveFailed = true; - resolve(); - } ), - order: { - get: () => - new Promise( ( resolve, reject ) => { - resolve( null ); - } ), - }, - } - ); + const confirmOrderResponse = await widgetBuilder.paypal + .Googlepay() + .confirmOrder( confirmationData ); - if ( ! approveFailed ) { - resolve( this.processPaymentResponse( 'SUCCESS' ) ); - } else { - resolve( - this.processPaymentResponse( - 'ERROR', - 'PAYMENT_AUTHORIZATION', - 'FAILED TO APPROVE' - ) - ); - } - } else { - resolve( - this.processPaymentResponse( - 'ERROR', - 'PAYMENT_AUTHORIZATION', - 'TRANSACTION FAILED' - ) - ); + this.log( 'confirmOrder', confirmOrderResponse ); + + return 'APPROVED' === confirmOrderResponse?.status; + }; + + /** + * This approval mainly confirms that the orderID is valid. + * + * It's still needed because this handler redirects to the checkout page if the server-side + * approval was successful. + * + * @param {string} orderID + */ + const approveOrderServerSide = async ( orderID ) => { + let isApproved = true; + + this.log( 'approveOrder', orderID ); + + await this.contextHandler.approveOrder( + { orderID, payer }, + { + restart: () => + new Promise( ( resolve ) => { + isApproved = false; + resolve(); + } ), + order: { + get: () => + new Promise( ( resolve ) => { + resolve( null ); + } ), + }, } - } catch ( err ) { - resolve( - this.processPaymentResponse( - 'ERROR', - 'PAYMENT_AUTHORIZATION', - err.message - ) - ); + ); + + return isApproved; + }; + + const processPaymentPromise = async ( resolve ) => { + const id = await this.contextHandler.createOrder(); + + this.log( 'createOrder', id ); + + const isApprovedByPayPal = await checkPayPalApproval( id ); + + if ( ! isApprovedByPayPal ) { + resolve( paymentError( 'TRANSACTION FAILED' ) ); + + return; } + + // This must be the last step in the process, as it initiates a redirect. + const success = await approveOrderServerSide( id ); + + if ( success ) { + resolve( this.processPaymentResponse( 'SUCCESS' ) ); + } else { + resolve( paymentError( 'FAILED TO APPROVE' ) ); + } + }; + + const addBillingDataToSession = () => { + moduleStorage.setPayer( payer ); + setPayerData( payer ); + }; + + return new Promise( async ( resolve ) => { + try { + addBillingDataToSession(); + await processPaymentPromise( resolve ); + } catch ( err ) { + resolve( paymentError( err.message ) ); + } + + this.logGroup(); } ); } @@ -639,6 +797,55 @@ class GooglepayButton extends PaymentButton { return response; } + + /** + * Updates the shipping option in the checkout form, if a form with shipping options is + * detected. + * + * @param {string} shippingOption - The shipping option ID, e.g. "flat_rate:4". + * @return {boolean} - True if a shipping option was found and selected, false otherwise. + */ + syncShippingOptionWithForm( shippingOption ) { + const wrappers = [ + // Classic checkout, Classic cart. + '.woocommerce-shipping-methods', + // Block checkout. + '.wc-block-components-shipping-rates-control', + // Block cart. + '.wc-block-components-totals-shipping', + ]; + + const sanitizedShippingOption = shippingOption.replace( /"/g, '' ); + + // Check for radio buttons with shipping options. + for ( const wrapper of wrappers ) { + const selector = `${ wrapper } input[type="radio"][value="${ sanitizedShippingOption }"]`; + const radioInput = document.querySelector( selector ); + + if ( radioInput ) { + radioInput.click(); + return true; + } + } + + // Check for select list with shipping options. + for ( const wrapper of wrappers ) { + const selector = `${ wrapper } select option[value="${ sanitizedShippingOption }"]`; + const selectOption = document.querySelector( selector ); + + if ( selectOption ) { + const selectElement = selectOption.closest( 'select' ); + + if ( selectElement ) { + selectElement.value = sanitizedShippingOption; + selectElement.dispatchEvent( new Event( 'change' ) ); + return true; + } + } + } + + return false; + } } export default GooglepayButton; diff --git a/modules/ppcp-googlepay/resources/js/Helper/GooglePayStorage.js b/modules/ppcp-googlepay/resources/js/Helper/GooglePayStorage.js new file mode 100644 index 000000000..faa2520e5 --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Helper/GooglePayStorage.js @@ -0,0 +1,31 @@ +import { LocalStorage } from '../../../../ppcp-button/resources/js/modules/Helper/LocalStorage'; + +export class GooglePayStorage extends LocalStorage { + static PAYER = 'payer'; + static PAYER_TTL = 900; // 15 minutes in seconds + + constructor() { + super( 'ppcp-googlepay' ); + } + + getPayer() { + return this.get( GooglePayStorage.PAYER ); + } + + setPayer( data ) { + /* + * The payer details are deleted on successful checkout, or after the TTL is reached. + * This helps to remove stale data from the browser, in case the customer chooses to + * use a different method to complete the purchase. + */ + this.set( GooglePayStorage.PAYER, data, GooglePayStorage.PAYER_TTL ); + } + + clearPayer() { + this.clear( GooglePayStorage.PAYER ); + } +} + +const moduleStorage = new GooglePayStorage(); + +export default moduleStorage; diff --git a/modules/ppcp-googlepay/resources/js/Helper/TransactionInfo.js b/modules/ppcp-googlepay/resources/js/Helper/TransactionInfo.js new file mode 100644 index 000000000..9216ad7c9 --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Helper/TransactionInfo.js @@ -0,0 +1,73 @@ +export default class TransactionInfo { + #country = ''; + #currency = ''; + #amount = 0; + #shippingFee = 0; + + constructor( total, shippingFee, currency, country ) { + this.#country = country; + this.#currency = currency; + + this.shippingFee = shippingFee; + this.amount = total - shippingFee; + } + + set amount( newAmount ) { + this.#amount = this.toAmount( newAmount ); + } + + get amount() { + return this.#amount; + } + + set shippingFee( newCost ) { + this.#shippingFee = this.toAmount( newCost ); + } + + get shippingFee() { + return this.#shippingFee; + } + + get currencyCode() { + return this.#currency; + } + + get countryCode() { + return this.#country; + } + + get totalPrice() { + const total = this.#amount + this.#shippingFee; + + return total.toFixed( 2 ); + } + + get finalObject() { + return { + countryCode: this.countryCode, + currencyCode: this.currencyCode, + totalPriceStatus: 'FINAL', + totalPrice: this.totalPrice, + }; + } + + /** + * Converts the value to a number and rounds to a precision of 2 digits. + * + * @param {any} value - The value to sanitize. + * @return {number} Numeric value. + */ + toAmount( value ) { + value = Number( value ) || 0; + return Math.round( value * 100 ) / 100; + } + + setTotal( totalPrice, shippingFee ) { + totalPrice = this.toAmount( totalPrice ); + + if ( totalPrice ) { + this.shippingFee = shippingFee; + this.amount = totalPrice - this.shippingFee; + } + } +} diff --git a/modules/ppcp-googlepay/resources/js/Preview/GooglePayPreviewButton.js b/modules/ppcp-googlepay/resources/js/Preview/GooglePayPreviewButton.js new file mode 100644 index 000000000..e6b27ee55 --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Preview/GooglePayPreviewButton.js @@ -0,0 +1,46 @@ +import GooglepayButton from '../GooglepayButton'; +import PreviewButton from '../../../../ppcp-button/resources/js/modules/Preview/PreviewButton'; + +/** + * A single GooglePay preview button instance. + */ +export default class GooglePayPreviewButton extends PreviewButton { + constructor( args ) { + super( args ); + + this.selector = `${ args.selector }GooglePay`; + this.defaultAttributes = { + button: { + style: { + type: 'pay', + color: 'black', + language: 'en', + }, + }, + }; + } + + createButton( buttonConfig ) { + const button = new GooglepayButton( + 'preview', + null, + buttonConfig, + this.ppcpConfig + ); + + button.init( this.apiConfig ); + } + + /** + * Merge form details into the config object for preview. + * Mutates the previewConfig object; no return value. + * @param buttonConfig + * @param ppcpConfig + */ + dynamicPreviewConfig( buttonConfig, ppcpConfig ) { + // Merge the current form-values into the preview-button configuration. + if ( ppcpConfig.button && buttonConfig.button ) { + Object.assign( buttonConfig.button.style, ppcpConfig.button.style ); + } + } +} diff --git a/modules/ppcp-googlepay/resources/js/Preview/GooglePayPreviewButtonManager.js b/modules/ppcp-googlepay/resources/js/Preview/GooglePayPreviewButtonManager.js new file mode 100644 index 000000000..a3e9f66af --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Preview/GooglePayPreviewButtonManager.js @@ -0,0 +1,57 @@ +import PreviewButtonManager from '../../../../ppcp-button/resources/js/modules/Preview/PreviewButtonManager'; +import GooglePayPreviewButton from './GooglePayPreviewButton'; + +/** + * Manages all GooglePay preview buttons on this page. + */ +export default class GooglePayPreviewButtonManager extends PreviewButtonManager { + constructor() { + const args = { + methodName: 'GooglePay', + buttonConfig: window.wc_ppcp_googlepay_admin, + }; + + super( args ); + } + + /** + * Responsible for fetching and returning the PayPal configuration object for this payment + * method. + * + * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. + * @return {Promise<{}>} + */ + async fetchConfig( payPal ) { + const apiMethod = payPal?.Googlepay()?.config; + + if ( ! apiMethod ) { + this.error( + 'configuration object cannot be retrieved from PayPal' + ); + return {}; + } + + try { + return await apiMethod(); + } catch ( error ) { + if ( error.message.includes( 'Not Eligible' ) ) { + this.apiError = 'Not Eligible'; + } + return null; + } + } + + /** + * This method is responsible for creating a new PreviewButton instance and returning it. + * + * @param {string} wrapperId - CSS ID of the wrapper element. + * @return {GooglePayPreviewButton} + */ + createButtonInstance( wrapperId ) { + return new GooglePayPreviewButton( { + selector: wrapperId, + apiConfig: this.apiConfig, + methodName: this.methodName, + } ); + } +} diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js index 7b5342078..953a6088e 100644 --- a/modules/ppcp-googlepay/resources/js/boot-admin.js +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -1,5 +1,4 @@ -import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager'; -import GooglePayPreviewButton from './GooglepayPreviewButton'; +import GooglePayPreviewButtonManager from './Preview/GooglePayPreviewButtonManager'; /** * Accessor that creates and returns a single PreviewButtonManager instance. @@ -13,59 +12,5 @@ const buttonManager = () => { return GooglePayPreviewButtonManager.instance; }; -/** - * Manages all GooglePay preview buttons on this page. - */ -class GooglePayPreviewButtonManager extends PreviewButtonManager { - constructor() { - const args = { - methodName: 'GooglePay', - buttonConfig: window.wc_ppcp_googlepay_admin, - }; - - super( args ); - } - - /** - * Responsible for fetching and returning the PayPal configuration object for this payment - * method. - * - * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. - * @return {Promise<{}>} Promise that resolves when API configuration is available. - */ - async fetchConfig( payPal ) { - const apiMethod = payPal?.Googlepay()?.config; - - if ( ! apiMethod ) { - this.error( - 'configuration object cannot be retrieved from PayPal' - ); - return {}; - } - - try { - return await apiMethod(); - } catch ( error ) { - if ( error.message.includes( 'Not Eligible' ) ) { - this.apiError = 'Not Eligible'; - } - return null; - } - } - - /** - * This method is responsible for creating a new PreviewButton instance and returning it. - * - * @param {string} wrapperId - CSS ID of the wrapper element. - * @return {GooglePayPreviewButton} The new preview button instance. - */ - createButtonInstance( wrapperId ) { - return new GooglePayPreviewButton( { - selector: wrapperId, - apiConfig: this.apiConfig, - } ); - } -} - // Initialize the preview button manager. buttonManager(); diff --git a/modules/ppcp-googlepay/resources/js/boot.js b/modules/ppcp-googlepay/resources/js/boot.js index 99dd414f5..fb9e8e313 100644 --- a/modules/ppcp-googlepay/resources/js/boot.js +++ b/modules/ppcp-googlepay/resources/js/boot.js @@ -1,28 +1,62 @@ +/** + * Initialize the GooglePay module in the front end. + * In some cases, this module is loaded when the `window.PayPalCommerceGateway` object is not + * present. In that case, the page does not contain a Google Pay button, but some other logic + * that is related to Google Pay (e.g., the CheckoutBootstrap module) + * + * @file + */ + import { loadCustomScript } from '@paypal/paypal-js'; import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'; import GooglepayManager from './GooglepayManager'; import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper'; +import { CheckoutBootstrap } from './ContextBootstrap/CheckoutBootstrap'; +import moduleStorage from './Helper/GooglePayStorage'; -( function ( { buttonConfig, ppcpConfig, jQuery } ) { - let manager; +( function ( { buttonConfig, ppcpConfig = {} } ) { + const context = ppcpConfig.context; - const bootstrap = function () { - manager = new GooglepayManager( buttonConfig, ppcpConfig ); - manager.init(); - }; - - setupButtonEvents( function () { - if ( manager ) { - manager.reinit(); + function bootstrapPayButton() { + if ( ! buttonConfig || ! ppcpConfig ) { + return; } - } ); + + const manager = new GooglepayManager( buttonConfig, ppcpConfig ); + manager.init(); + + setupButtonEvents( function () { + manager.reinit(); + } ); + } + + function bootstrapCheckout() { + if ( context && ! [ 'continuation', 'checkout' ].includes( context ) ) { + // Context must be missing/empty, or "continuation"/"checkout" to proceed. + return; + } + if ( ! CheckoutBootstrap.isPageWithCheckoutForm() ) { + return; + } + + const checkoutBootstrap = new CheckoutBootstrap( moduleStorage ); + checkoutBootstrap.init(); + } + + function bootstrap() { + bootstrapPayButton(); + bootstrapCheckout(); + } document.addEventListener( 'DOMContentLoaded', () => { - if ( - typeof buttonConfig === 'undefined' || - typeof ppcpConfig === 'undefined' - ) { - // No PayPal buttons present on this page. + if ( ! buttonConfig || ! ppcpConfig ) { + /* + * No PayPal buttons present on this page, but maybe a bootstrap module needs to be + * initialized. Skip loading the SDK or gateway configuration, and directly initialize + * the module. + */ + bootstrap(); + return; } @@ -52,5 +86,4 @@ import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Hel } )( { buttonConfig: window.wc_ppcp_googlepay, ppcpConfig: window.PayPalCommerceGateway, - jQuery: window.jQuery, } ); diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 110b56937..fb64b2626 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -267,7 +267,8 @@ return array( $container->get( 'wcgateway.processor.refunds' ), $container->get( 'wcgateway.transaction-url-provider' ), $container->get( 'session.handler' ), - $container->get( 'googlepay.url' ) + $container->get( 'googlepay.url' ), + $container->get( 'woocommerce.logger.woocommerce' ) ); }, ); diff --git a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php index 27da5ef48..e489bc771 100644 --- a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php +++ b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php @@ -90,7 +90,8 @@ class UpdatePaymentDataEndpoint { WC()->cart->calculate_fees(); WC()->cart->calculate_totals(); - $total = (float) WC()->cart->get_total( 'numeric' ); + $total = (float) WC()->cart->get_total( 'numeric' ); + $shipping_fee = (float) WC()->cart->get_shipping_total(); // Shop settings. $base_location = wc_get_base_location(); @@ -100,7 +101,7 @@ class UpdatePaymentDataEndpoint { wp_send_json_success( array( 'total' => $total, - 'total_str' => ( new Money( $total, $currency_code ) )->value_str(), + 'shipping_fee' => $shipping_fee, 'currency_code' => $currency_code, 'country_code' => $shop_country_code, 'shipping_options' => $this->get_shipping_options(), @@ -146,6 +147,7 @@ class UpdatePaymentDataEndpoint { wc_price( (float) $rate->get_cost(), array( 'currency' => get_woocommerce_currency() ) ) ) ), + 'cost' => $rate->get_cost(), ); } diff --git a/modules/ppcp-googlepay/src/GooglePayGateway.php b/modules/ppcp-googlepay/src/GooglePayGateway.php index 235a67a85..16fe5f690 100644 --- a/modules/ppcp-googlepay/src/GooglePayGateway.php +++ b/modules/ppcp-googlepay/src/GooglePayGateway.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Googlepay; use Exception; +use Psr\Log\LoggerInterface; use WC_Order; use WC_Payment_Gateway; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; @@ -72,6 +73,13 @@ class GooglePayGateway extends WC_Payment_Gateway { */ private $module_url; + /** + * The logger. + * + * @var LoggerInterface + */ + private $logger; + /** * GooglePayGateway constructor. * @@ -81,6 +89,7 @@ class GooglePayGateway extends WC_Payment_Gateway { * @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order. * @param SessionHandler $session_handler The Session Handler. * @param string $module_url The URL to the module. + * @param LoggerInterface $logger The logger. */ public function __construct( OrderProcessor $order_processor, @@ -88,7 +97,8 @@ class GooglePayGateway extends WC_Payment_Gateway { RefundProcessor $refund_processor, TransactionUrlProvider $transaction_url_provider, SessionHandler $session_handler, - string $module_url + string $module_url, + LoggerInterface $logger ) { $this->id = self::ID; @@ -104,7 +114,7 @@ class GooglePayGateway extends WC_Payment_Gateway { $this->description = $this->get_option( 'description', '' ); $this->module_url = $module_url; - $this->icon = esc_url( $this->module_url ) . 'assets/images/googlepay.png'; + $this->icon = esc_url( $this->module_url ) . 'assets/images/googlepay.svg'; $this->init_form_fields(); $this->init_settings(); @@ -113,6 +123,7 @@ class GooglePayGateway extends WC_Payment_Gateway { $this->refund_processor = $refund_processor; $this->transaction_url_provider = $transaction_url_provider; $this->session_handler = $session_handler; + $this->logger = $logger; add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index b7feedc07..1b57a816a 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -16,30 +16,37 @@ use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class GooglepayModule */ -class GooglepayModule implements ModuleInterface { +class GooglepayModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { // Clears product status when appropriate. add_action( @@ -93,11 +100,21 @@ class GooglepayModule implements ModuleInterface { static function () use ( $c, $button ) { $smart_button = $c->get( 'button.smart-button' ); assert( $smart_button instanceof SmartButtonInterface ); + if ( $smart_button->should_load_ppcp_script() ) { $button->enqueue(); return; } + /* + * Checkout page, but no PPCP scripts were loaded. Most likely in continuation mode. + * Need to enqueue some Google Pay scripts to populate the billing form with details + * provided by Google Pay. + */ + if ( is_checkout() ) { + $button->enqueue(); + } + if ( has_block( 'woocommerce/checkout' ) || has_block( 'woocommerce/cart' ) ) { /** * Should add this to the ButtonInterface. @@ -200,13 +217,7 @@ class GooglepayModule implements ModuleInterface { echo '
'; } ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-local-alternative-payment-methods/composer.json b/modules/ppcp-local-alternative-payment-methods/composer.json index 278244301..d49279693 100644 --- a/modules/ppcp-local-alternative-payment-methods/composer.json +++ b/modules/ppcp-local-alternative-payment-methods/composer.json @@ -4,7 +4,7 @@ "description": "Country based Alternative Payment Methods module", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-local-alternative-payment-methods/module.php b/modules/ppcp-local-alternative-payment-methods/module.php index 9b69c59b1..ef3a095f8 100644 --- a/modules/ppcp-local-alternative-payment-methods/module.php +++ b/modules/ppcp-local-alternative-payment-methods/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): LocalAlternativePaymentMethodsModule { return new LocalAlternativePaymentMethodsModule(); }; diff --git a/modules/ppcp-local-alternative-payment-methods/src/LocalAlternativePaymentMethodsModule.php b/modules/ppcp-local-alternative-payment-methods/src/LocalAlternativePaymentMethodsModule.php index 5dda3b818..6db9de464 100644 --- a/modules/ppcp-local-alternative-payment-methods/src/LocalAlternativePaymentMethodsModule.php +++ b/modules/ppcp-local-alternative-payment-methods/src/LocalAlternativePaymentMethodsModule.php @@ -9,11 +9,12 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods; -use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use WC_Order; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Helper\FeesUpdater; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -21,27 +22,32 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class LocalAlternativePaymentMethodsModule */ -class LocalAlternativePaymentMethodsModule implements ModuleInterface { +class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services() : array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions() : array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); if ( ! $settings->has( 'allow_local_apm_gateways' ) || $settings->get( 'allow_local_apm_gateways' ) !== true ) { - return; + return true; } add_filter( @@ -190,6 +196,8 @@ class LocalAlternativePaymentMethodsModule implements ModuleInterface { return $payment_methods; } ); + + return true; } /** diff --git a/modules/ppcp-onboarding/composer.json b/modules/ppcp-onboarding/composer.json index 27886d3b7..805a1bb1f 100644 --- a/modules/ppcp-onboarding/composer.json +++ b/modules/ppcp-onboarding/composer.json @@ -4,7 +4,7 @@ "description": "Onboarding module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-onboarding/module.php b/modules/ppcp-onboarding/module.php index 55d1b4df3..dc255e3c2 100644 --- a/modules/ppcp-onboarding/module.php +++ b/modules/ppcp-onboarding/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Onboarding; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): OnboardingModule { return new OnboardingModule(); }; diff --git a/modules/ppcp-onboarding/resources/js/onboarding.js b/modules/ppcp-onboarding/resources/js/onboarding.js index 905c66c65..5a6ab333a 100644 --- a/modules/ppcp-onboarding/resources/js/onboarding.js +++ b/modules/ppcp-onboarding/resources/js/onboarding.js @@ -326,7 +326,9 @@ window.ppcp_onboarding_productionCallback = function ( ...args ) { isDisconnecting = true; - document.querySelector( '.woocommerce-save-button' ).click(); + const saveButton = document.querySelector( '.woocommerce-save-button' ); + saveButton.removeAttribute( 'disabled' ); + saveButton.click(); }; // Prevent the message about unsaved checkbox/radiobutton when reloading the page. diff --git a/modules/ppcp-onboarding/src/OnboardingModule.php b/modules/ppcp-onboarding/src/OnboardingModule.php index 578cda42c..40e137832 100644 --- a/modules/ppcp-onboarding/src/OnboardingModule.php +++ b/modules/ppcp-onboarding/src/OnboardingModule.php @@ -10,33 +10,39 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Onboarding; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class OnboardingModule */ -class OnboardingModule implements ModuleInterface { +class OnboardingModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { $asset_loader = $c->get( 'onboarding.assets' ); /** @@ -107,13 +113,7 @@ class OnboardingModule implements ModuleInterface { // Initialize REST routes at the appropriate time. $rest_controller = $c->get( 'onboarding.rest' ); add_action( 'rest_api_init', array( $rest_controller, 'register_routes' ) ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-order-tracking/composer.json b/modules/ppcp-order-tracking/composer.json index 3e4a60b56..f901b6657 100644 --- a/modules/ppcp-order-tracking/composer.json +++ b/modules/ppcp-order-tracking/composer.json @@ -4,7 +4,7 @@ "description": "Order tracking module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-order-tracking/module.php b/modules/ppcp-order-tracking/module.php index a82bbb033..7816e8d4a 100644 --- a/modules/ppcp-order-tracking/module.php +++ b/modules/ppcp-order-tracking/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\OrderTracking; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): OrderTrackingModule { return new OrderTrackingModule(); }; diff --git a/modules/ppcp-order-tracking/src/OrderTrackingModule.php b/modules/ppcp-order-tracking/src/OrderTrackingModule.php index d3373900d..cdc44fbdb 100644 --- a/modules/ppcp-order-tracking/src/OrderTrackingModule.php +++ b/modules/ppcp-order-tracking/src/OrderTrackingModule.php @@ -12,9 +12,10 @@ namespace WooCommerce\PayPalCommerce\OrderTracking; use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; use Exception; use WC_Order; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\OrderTracking\Assets\OrderEditPageAssets; @@ -27,8 +28,8 @@ use function WooCommerce\PayPalCommerce\Api\ppcp_get_paypal_order; /** * Class OrderTrackingModule */ -class OrderTrackingModule implements ModuleInterface { - +class OrderTrackingModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; use TrackingAvailabilityTrait, TransactionIdHandlingTrait; public const PPCP_TRACKING_INFO_META_NAME = '_ppcp_paypal_tracking_info_meta_name'; @@ -36,11 +37,15 @@ class OrderTrackingModule implements ModuleInterface { /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; + } + + /** + * {@inheritDoc} + */ + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; } /** @@ -49,7 +54,7 @@ class OrderTrackingModule implements ModuleInterface { * @param ContainerInterface $c A services container instance. * @throws NotFoundException */ - public function run( ContainerInterface $c ): void { + public function run( ContainerInterface $c ): bool { $endpoint = $c->get( 'order-tracking.endpoint.controller' ); assert( $endpoint instanceof OrderTrackingEndpoint ); @@ -144,5 +149,7 @@ class OrderTrackingModule implements ModuleInterface { 10, 2 ); + + return true; } } diff --git a/modules/ppcp-paylater-block/composer.json b/modules/ppcp-paylater-block/composer.json index 58b7a5545..2be488dda 100644 --- a/modules/ppcp-paylater-block/composer.json +++ b/modules/ppcp-paylater-block/composer.json @@ -4,7 +4,7 @@ "description": "Pay Later Block module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-paylater-block/module.php b/modules/ppcp-paylater-block/module.php index cd2274eda..fc559cb57 100644 --- a/modules/ppcp-paylater-block/module.php +++ b/modules/ppcp-paylater-block/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayLaterBlock; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): PayLaterBlockModule { return new PayLaterBlockModule(); }; diff --git a/modules/ppcp-paylater-block/src/PayLaterBlockModule.php b/modules/ppcp-paylater-block/src/PayLaterBlockModule.php index 418d374d2..896d98027 100644 --- a/modules/ppcp-paylater-block/src/PayLaterBlockModule.php +++ b/modules/ppcp-paylater-block/src/PayLaterBlockModule.php @@ -11,9 +11,10 @@ namespace WooCommerce\PayPalCommerce\PayLaterBlock; use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint; use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -21,7 +22,9 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class PayLaterBlockModule */ -class PayLaterBlockModule implements ModuleInterface { +class PayLaterBlockModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** * Returns whether the block module should be loaded. */ @@ -46,22 +49,26 @@ class PayLaterBlockModule implements ModuleInterface { /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { $messages_apply = $c->get( 'button.helper.messages-apply' ); assert( $messages_apply instanceof MessagesApply ); if ( ! $messages_apply->for_country() ) { - return; + return true; } $settings = $c->get( 'wcgateway.settings' ); @@ -119,13 +126,7 @@ class PayLaterBlockModule implements ModuleInterface { }, 20 ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-paylater-configurator/composer.json b/modules/ppcp-paylater-configurator/composer.json index 755aa0dce..a78114979 100644 --- a/modules/ppcp-paylater-configurator/composer.json +++ b/modules/ppcp-paylater-configurator/composer.json @@ -4,7 +4,7 @@ "description": "Pay Later Messaging configurator module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-paylater-configurator/extensions.php b/modules/ppcp-paylater-configurator/extensions.php index c899812db..775fcb319 100644 --- a/modules/ppcp-paylater-configurator/extensions.php +++ b/modules/ppcp-paylater-configurator/extensions.php @@ -13,7 +13,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { $old_fields = array( 'pay_later_messaging_locations', 'pay_later_enable_styling_per_messaging_location', diff --git a/modules/ppcp-paylater-configurator/module.php b/modules/ppcp-paylater-configurator/module.php index d4234abcf..2099805e6 100644 --- a/modules/ppcp-paylater-configurator/module.php +++ b/modules/ppcp-paylater-configurator/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayLaterConfigurator; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): PayLaterConfiguratorModule { return new PayLaterConfiguratorModule(); }; diff --git a/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php b/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php index 4ec4ed394..c7fef6ceb 100644 --- a/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php +++ b/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php @@ -12,9 +12,10 @@ namespace WooCommerce\PayPalCommerce\PayLaterConfigurator; use WooCommerce\PayPalCommerce\PayLaterConfigurator\Endpoint\GetConfig; use WooCommerce\PayPalCommerce\PayLaterConfigurator\Endpoint\SaveConfig; use WooCommerce\PayPalCommerce\PayLaterConfigurator\Factory\ConfigFactory; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository; @@ -23,7 +24,9 @@ use WooCommerce\PayPalCommerce\AdminNotices\Entity\PersistentMessage; /** * Class PayLaterConfiguratorModule */ -class PayLaterConfiguratorModule implements ModuleInterface { +class PayLaterConfiguratorModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** * Returns whether the module should be loaded. */ @@ -38,21 +41,25 @@ class PayLaterConfiguratorModule implements ModuleInterface { /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ) : void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ) : bool { $is_available = $c->get( 'paylater-configurator.is-available' ); if ( ! $is_available ) { - return; + return true; } $current_page_id = $c->get( 'wcgateway.current-ppcp-settings-page-id' ); @@ -83,7 +90,7 @@ class PayLaterConfiguratorModule implements ModuleInterface { ); if ( $current_page_id !== Settings::PAY_LATER_TAB_ID ) { - return; + return true; } add_action( @@ -142,14 +149,8 @@ class PayLaterConfiguratorModule implements ModuleInterface { ); } ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } /** diff --git a/modules/ppcp-paylater-wc-blocks/composer.json b/modules/ppcp-paylater-wc-blocks/composer.json index d2832053b..7b33caae4 100644 --- a/modules/ppcp-paylater-wc-blocks/composer.json +++ b/modules/ppcp-paylater-wc-blocks/composer.json @@ -4,7 +4,7 @@ "description": "Pay Later WooCommerce Blocks module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-paylater-wc-blocks/module.php b/modules/ppcp-paylater-wc-blocks/module.php index 51fabb1fd..044de74d5 100644 --- a/modules/ppcp-paylater-wc-blocks/module.php +++ b/modules/ppcp-paylater-wc-blocks/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayLaterWCBlocks; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): PayLaterWCBlocksModule { return new PayLaterWCBlocksModule(); }; diff --git a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php index 70e6afe88..fe547ea17 100644 --- a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php +++ b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php @@ -11,19 +11,35 @@ namespace WooCommerce\PayPalCommerce\PayLaterWCBlocks; use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint; use WooCommerce\PayPalCommerce\PayLaterConfigurator\Factory\ConfigFactory; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; -use WooCommerce\PayPalCommerce\PayLaterWCBlocks\PayLaterWCBlocksUtils; /** * Class PayLaterWCBlocksModule */ -class PayLaterWCBlocksModule implements ModuleInterface { +class PayLaterWCBlocksModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + + /** + * {@inheritDoc} + */ + public function services(): array { + return require __DIR__ . '/../services.php'; + } + + /** + * {@inheritDoc} + */ + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + /** * Returns whether the block module should be loaded. * @@ -75,22 +91,12 @@ class PayLaterWCBlocksModule implements ModuleInterface { /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); - } - - /** - * {@inheritDoc} - */ - public function run( ContainerInterface $c ): void { + public function run( ContainerInterface $c ): bool { $messages_apply = $c->get( 'button.helper.messages-apply' ); assert( $messages_apply instanceof MessagesApply ); if ( ! $messages_apply->for_country() ) { - return; + return true; } $settings = $c->get( 'wcgateway.settings' ); @@ -281,13 +287,6 @@ class PayLaterWCBlocksModule implements ModuleInterface { } ); } - } - - /** - * Returns the key for the module. - * - * @return void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-paypal-subscriptions/composer.json b/modules/ppcp-paypal-subscriptions/composer.json index 161c4b9ce..8e7969c46 100644 --- a/modules/ppcp-paypal-subscriptions/composer.json +++ b/modules/ppcp-paypal-subscriptions/composer.json @@ -4,7 +4,7 @@ "description": "Module for PayPal Subscriptions API integration", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-paypal-subscriptions/extensions.php b/modules/ppcp-paypal-subscriptions/extensions.php index fc91cd245..a02c96045 100644 --- a/modules/ppcp-paypal-subscriptions/extensions.php +++ b/modules/ppcp-paypal-subscriptions/extensions.php @@ -9,6 +9,4 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayPalSubscriptions; -use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; - return array(); diff --git a/modules/ppcp-paypal-subscriptions/module.php b/modules/ppcp-paypal-subscriptions/module.php index ee5f79da8..f09b27206 100644 --- a/modules/ppcp-paypal-subscriptions/module.php +++ b/modules/ppcp-paypal-subscriptions/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayPalSubscriptions; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): PayPalSubscriptionsModule { return new PayPalSubscriptionsModule(); }; diff --git a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php index 77cb8ae49..07dd5c8c6 100644 --- a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php +++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php @@ -22,9 +22,10 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Onboarding\Environment; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -34,22 +35,27 @@ use WP_Post; /** * Class SavedPaymentCheckerModule */ -class PayPalSubscriptionsModule implements ModuleInterface { +class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { add_action( 'save_post', /** @@ -703,6 +709,8 @@ class PayPalSubscriptionsModule implements ModuleInterface { } } ); + + return true; } /** diff --git a/modules/ppcp-save-payment-methods/composer.json b/modules/ppcp-save-payment-methods/composer.json index 50ff8c251..54656aa2e 100644 --- a/modules/ppcp-save-payment-methods/composer.json +++ b/modules/ppcp-save-payment-methods/composer.json @@ -4,7 +4,7 @@ "description": "Save payment methods module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-save-payment-methods/extensions.php b/modules/ppcp-save-payment-methods/extensions.php index 1758f8960..cbcb904d7 100644 --- a/modules/ppcp-save-payment-methods/extensions.php +++ b/modules/ppcp-save-payment-methods/extensions.php @@ -9,6 +9,4 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\SavePaymentMethods; -use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; - return array(); diff --git a/modules/ppcp-save-payment-methods/module.php b/modules/ppcp-save-payment-methods/module.php index 85f1b6416..a4187d4e3 100644 --- a/modules/ppcp-save-payment-methods/module.php +++ b/modules/ppcp-save-payment-methods/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\SavePaymentMethods; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): SavePaymentMethodsModule { return new SavePaymentMethodsModule(); }; diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index f82095bf4..414f896ff 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -22,10 +22,11 @@ use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken; use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentTokenForGuest; use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vaulting\WooCommercePaymentTokens; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -36,26 +37,30 @@ use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; /** * Class SavePaymentMethodsModule */ -class SavePaymentMethodsModule implements ModuleInterface { - +class SavePaymentMethodsModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; use ContextTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { if ( ! $c->get( 'save-payment-methods.eligible' ) ) { - return; + return true; } $settings = $c->get( 'wcgateway.settings' ); @@ -79,7 +84,7 @@ class SavePaymentMethodsModule implements ModuleInterface { ( ! $settings->has( 'vault_enabled' ) || ! $settings->get( 'vault_enabled' ) ) && ( ! $settings->has( 'vault_enabled_dcc' ) || ! $settings->get( 'vault_enabled_dcc' ) ) ) { - return; + return true; } add_filter( @@ -431,6 +436,8 @@ class SavePaymentMethodsModule implements ModuleInterface { return true; } ); + + return true; } /** diff --git a/modules/ppcp-saved-payment-checker/composer.json b/modules/ppcp-saved-payment-checker/composer.json index 96c8931c3..00a15a36a 100644 --- a/modules/ppcp-saved-payment-checker/composer.json +++ b/modules/ppcp-saved-payment-checker/composer.json @@ -4,7 +4,7 @@ "description": "Saved payments checker module", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-saved-payment-checker/extensions.php b/modules/ppcp-saved-payment-checker/extensions.php index e712a03fc..dcdbd169d 100644 --- a/modules/ppcp-saved-payment-checker/extensions.php +++ b/modules/ppcp-saved-payment-checker/extensions.php @@ -14,7 +14,7 @@ use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { $subscription_helper = $container->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); diff --git a/modules/ppcp-saved-payment-checker/module.php b/modules/ppcp-saved-payment-checker/module.php index 9f5217ab8..63e55692b 100644 --- a/modules/ppcp-saved-payment-checker/module.php +++ b/modules/ppcp-saved-payment-checker/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\SavedPaymentChecker; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): SavedPaymentCheckerModule { return new SavedPaymentCheckerModule(); }; diff --git a/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php b/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php index 6a6ae3c79..2c2d58ff1 100644 --- a/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php +++ b/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php @@ -11,31 +11,37 @@ namespace WooCommerce\PayPalCommerce\SavedPaymentChecker; use Psr\Log\LoggerInterface; use WC_Order; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class SavedPaymentCheckerModule */ -class SavedPaymentCheckerModule implements ModuleInterface { +class SavedPaymentCheckerModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { /** * Set authorize intent for vaulted subscriptions, so we can void if payment not saved. @@ -138,5 +144,7 @@ class SavedPaymentCheckerModule implements ModuleInterface { } } ); + + return true; } } diff --git a/modules/ppcp-session/composer.json b/modules/ppcp-session/composer.json index b5cd73378..3a59ed8fc 100644 --- a/modules/ppcp-session/composer.json +++ b/modules/ppcp-session/composer.json @@ -4,7 +4,7 @@ "description": "Session module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-session/module.php b/modules/ppcp-session/module.php index 80392d729..b75551e9f 100644 --- a/modules/ppcp-session/module.php +++ b/modules/ppcp-session/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Session; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return function (): ModuleInterface { +return function (): SessionModule { return new SessionModule(); }; diff --git a/modules/ppcp-session/src/SessionModule.php b/modules/ppcp-session/src/SessionModule.php index cecc1a98f..e67d6014c 100644 --- a/modules/ppcp-session/src/SessionModule.php +++ b/modules/ppcp-session/src/SessionModule.php @@ -14,16 +14,19 @@ use Throwable; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Session\Cancellation\CancelController; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class SessionModule */ -class SessionModule implements ModuleInterface { +class SessionModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** * A flag to avoid multiple requests to reload order. * @@ -34,17 +37,21 @@ class SessionModule implements ModuleInterface { /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { add_action( 'woocommerce_init', function () use ( $c ) { @@ -96,13 +103,7 @@ class SessionModule implements ModuleInterface { 10, 2 ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-status-report/composer.json b/modules/ppcp-status-report/composer.json index 181a6590f..53ac3c4d3 100644 --- a/modules/ppcp-status-report/composer.json +++ b/modules/ppcp-status-report/composer.json @@ -4,7 +4,7 @@ "description": "Status report module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-status-report/module.php b/modules/ppcp-status-report/module.php index 35edc894c..0dd438cdb 100644 --- a/modules/ppcp-status-report/module.php +++ b/modules/ppcp-status-report/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\StatusReport; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): StatusReportModule { return new StatusReportModule(); }; diff --git a/modules/ppcp-status-report/src/StatusReportModule.php b/modules/ppcp-status-report/src/StatusReportModule.php index a7e810abe..8aaf8fc6c 100644 --- a/modules/ppcp-status-report/src/StatusReportModule.php +++ b/modules/ppcp-status-report/src/StatusReportModule.php @@ -9,10 +9,11 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\StatusReport; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint; @@ -26,16 +27,21 @@ use WooCommerce\PayPalCommerce\Webhooks\WebhookEventStorage; /** * Class StatusReportModule */ -class StatusReportModule implements ModuleInterface { +class StatusReportModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; + } + + /** + * {@inheritDoc} + */ + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; } /** @@ -43,7 +49,7 @@ class StatusReportModule implements ModuleInterface { * * @param ContainerInterface $c A services container instance. */ - public function run( ContainerInterface $c ): void { + public function run( ContainerInterface $c ): bool { add_action( 'woocommerce_system_status_report', function () use ( $c ) { @@ -185,12 +191,9 @@ class StatusReportModule implements ModuleInterface { ); } ); - } - /** - * {@inheritDoc} - */ - public function getKey() { } + return true; + } /** * It returns the current onboarding status. diff --git a/modules/ppcp-uninstall/composer.json b/modules/ppcp-uninstall/composer.json index 53351b6dc..efede6ce8 100644 --- a/modules/ppcp-uninstall/composer.json +++ b/modules/ppcp-uninstall/composer.json @@ -4,7 +4,7 @@ "description": "Uninstall module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-uninstall/extensions.php b/modules/ppcp-uninstall/extensions.php index 94e44737e..6f40091e7 100644 --- a/modules/ppcp-uninstall/extensions.php +++ b/modules/ppcp-uninstall/extensions.php @@ -14,7 +14,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( - 'wcgateway.settings.fields' => static function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => static function ( array $fields, ContainerInterface $container ): array { $uninstall_fields = array( 'uninstall_heading' => array( 'heading' => __( 'Uninstall/Clear Database', 'woocommerce-paypal-payments' ), diff --git a/modules/ppcp-uninstall/module.php b/modules/ppcp-uninstall/module.php index c3783cbb3..a3fca3039 100644 --- a/modules/ppcp-uninstall/module.php +++ b/modules/ppcp-uninstall/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Uninstall; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return function (): ModuleInterface { +return function (): UninstallModule { return new UninstallModule(); }; diff --git a/modules/ppcp-uninstall/src/UninstallModule.php b/modules/ppcp-uninstall/src/UninstallModule.php index 25d65ae0f..2cb2dce52 100644 --- a/modules/ppcp-uninstall/src/UninstallModule.php +++ b/modules/ppcp-uninstall/src/UninstallModule.php @@ -12,31 +12,37 @@ namespace WooCommerce\PayPalCommerce\Uninstall; use Exception; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; use WooCommerce\PayPalCommerce\Uninstall\Assets\ClearDatabaseAssets; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class UninstallModule */ -class UninstallModule implements ModuleInterface { +class UninstallModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $container ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $container ): bool { $page_id = $container->get( 'wcgateway.current-ppcp-settings-page-id' ); if ( Settings::CONNECTION_TAB_ID === $page_id ) { $this->registerClearDatabaseAssets( $container->get( 'uninstall.clear-db-assets' ) ); @@ -50,6 +56,8 @@ class UninstallModule implements ModuleInterface { $action_names = $container->get( 'uninstall.ppcp-all-action-names' ); $this->handleClearDbAjaxRequest( $request_data, $clear_db, $clear_db_endpoint, $option_names, $scheduled_action_names, $action_names ); + + return true; } /** diff --git a/modules/ppcp-vaulting/composer.json b/modules/ppcp-vaulting/composer.json index e24f10fe6..7bacd2d46 100644 --- a/modules/ppcp-vaulting/composer.json +++ b/modules/ppcp-vaulting/composer.json @@ -4,7 +4,7 @@ "description": "Vaulting module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-vaulting/module.php b/modules/ppcp-vaulting/module.php index fb42c4f9c..4266fbf00 100644 --- a/modules/ppcp-vaulting/module.php +++ b/modules/ppcp-vaulting/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Vaulting; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): VaultingModule { return new VaultingModule(); }; diff --git a/modules/ppcp-vaulting/src/VaultingModule.php b/modules/ppcp-vaulting/src/VaultingModule.php index 5216b82c3..041c61c35 100644 --- a/modules/ppcp-vaulting/src/VaultingModule.php +++ b/modules/ppcp-vaulting/src/VaultingModule.php @@ -13,9 +13,10 @@ use Psr\Log\LoggerInterface; use RuntimeException; use WC_Payment_Token; use WC_Payment_Tokens; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; @@ -26,17 +27,21 @@ use WP_User_Query; /** * Class StatusReportModule */ -class VaultingModule implements ModuleInterface { - +class VaultingModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; + } + + /** + * {@inheritDoc} + */ + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; } /** @@ -45,7 +50,7 @@ class VaultingModule implements ModuleInterface { * @param ContainerInterface $container A services container instance. * @throws NotFoundException When service could not be found. */ - public function run( ContainerInterface $container ): void { + public function run( ContainerInterface $container ): bool { $listener = $container->get( 'vaulting.customer-approval-listener' ); assert( $listener instanceof CustomerApprovalListener ); @@ -264,6 +269,8 @@ class VaultingModule implements ModuleInterface { return $methods; } ); + + return true; } /** @@ -321,9 +328,4 @@ class VaultingModule implements ModuleInterface { $timestamp += $interval_in_seconds; } } - - /** - * {@inheritDoc} - */ - public function getKey() { } } diff --git a/modules/ppcp-wc-gateway/composer.json b/modules/ppcp-wc-gateway/composer.json index b9ea62f55..d833fd575 100644 --- a/modules/ppcp-wc-gateway/composer.json +++ b/modules/ppcp-wc-gateway/composer.json @@ -4,7 +4,7 @@ "description": "WC gateway module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-wc-gateway/extensions.php b/modules/ppcp-wc-gateway/extensions.php index aca11f1b1..fe2de006b 100644 --- a/modules/ppcp-wc-gateway/extensions.php +++ b/modules/ppcp-wc-gateway/extensions.php @@ -17,15 +17,15 @@ use Psr\Log\LoggerInterface; return array( - 'api.merchant_email' => static function ( ContainerInterface $container ): string { + 'api.merchant_email' => static function ( string $previous, ContainerInterface $container ): string { $settings = $container->get( 'wcgateway.settings' ); return $settings->has( 'merchant_email' ) ? (string) $settings->get( 'merchant_email' ) : ''; }, - 'api.merchant_id' => static function ( ContainerInterface $container ): string { + 'api.merchant_id' => static function ( string $previous, ContainerInterface $container ): string { $settings = $container->get( 'wcgateway.settings' ); return $settings->has( 'merchant_id' ) ? (string) $settings->get( 'merchant_id' ) : ''; }, - 'api.partner_merchant_id' => static function ( ContainerInterface $container ): string { + 'api.partner_merchant_id' => static function ( string $previous, ContainerInterface $container ): string { $environment = $container->get( 'onboarding.environment' ); /** @@ -36,20 +36,20 @@ return array( return $environment->current_environment_is( Environment::SANDBOX ) ? (string) $container->get( 'api.partner_merchant_id-sandbox' ) : (string) $container->get( 'api.partner_merchant_id-production' ); }, - 'api.key' => static function ( ContainerInterface $container ): string { + 'api.key' => static function ( string $previous, ContainerInterface $container ): string { $settings = $container->get( 'wcgateway.settings' ); $key = $settings->has( 'client_id' ) ? (string) $settings->get( 'client_id' ) : ''; return $key; }, - 'api.secret' => static function ( ContainerInterface $container ): string { + 'api.secret' => static function ( string $previous, ContainerInterface $container ): string { $settings = $container->get( 'wcgateway.settings' ); return $settings->has( 'client_secret' ) ? (string) $settings->get( 'client_secret' ) : ''; }, - 'api.prefix' => static function ( ContainerInterface $container ): string { + 'api.prefix' => static function ( string $previous, ContainerInterface $container ): string { $settings = $container->get( 'wcgateway.settings' ); return $settings->has( 'prefix' ) ? (string) $settings->get( 'prefix' ) : 'WC-'; }, - 'woocommerce.logger.woocommerce' => function ( ContainerInterface $container ): LoggerInterface { + 'woocommerce.logger.woocommerce' => function ( LoggerInterface $previous, ContainerInterface $container ): LoggerInterface { if ( ! function_exists( 'wc_get_logger' ) || ! $container->get( 'wcgateway.logging.is-enabled' ) ) { return new NullLogger(); } @@ -60,7 +60,7 @@ return array( $source ); }, - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { $files = array( 'paypal-smart-button-fields.php', 'connection-tab-fields.php', diff --git a/modules/ppcp-wc-gateway/module.php b/modules/ppcp-wc-gateway/module.php index 64b4601d8..ce8f30ed6 100644 --- a/modules/ppcp-wc-gateway/module.php +++ b/modules/ppcp-wc-gateway/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): WCGatewayModule { return new WCGatewayModule(); }; diff --git a/modules/ppcp-wc-gateway/resources/css/common.scss b/modules/ppcp-wc-gateway/resources/css/common.scss index ac4bcab32..85cf0bf68 100644 --- a/modules/ppcp-wc-gateway/resources/css/common.scss +++ b/modules/ppcp-wc-gateway/resources/css/common.scss @@ -31,9 +31,25 @@ $background-ident-color: #fbfbfb; &.ppcp-button-dummy { display: flex; + min-height: 25px; align-items: center; justify-content: center; background: #0001; + position: relative; + + &:before { + content: ''; + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + width: 42px; + height: 24px; + background-image: var(--apm-button-dummy-background, none); + background-repeat: no-repeat; + background-size: contain; + background-position: center left; + } } } diff --git a/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js b/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js index c76aa8960..a7b61b32a 100644 --- a/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js +++ b/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js @@ -18,6 +18,13 @@ export default class ConsoleLogger { */ #enabled = false; + /** + * Tracks the current log-group that was started using `this.group()` + * + * @type {?string} + */ + #openGroup = null; + constructor( ...prefixes ) { if ( prefixes.length ) { this.#prefix = `[${ prefixes.join( ' | ' ) }]`; @@ -55,4 +62,28 @@ export default class ConsoleLogger { error( ...args ) { console.error( this.#prefix, ...args ); } + + /** + * Starts or ends a group in the browser console. + * + * @param {string} [label=null] - The group label. Omit to end the current group. + */ + group( label = null ) { + if ( ! this.#enabled ) { + return; + } + + if ( ! label || this.#openGroup ) { + // eslint-disable-next-line + console.groupEnd(); + this.#openGroup = null; + } + + if ( label ) { + // eslint-disable-next-line + console.group( label ); + + this.#openGroup = label; + } + } } diff --git a/modules/ppcp-wc-gateway/src/Settings/Fields/card-button-fields.php b/modules/ppcp-wc-gateway/src/Settings/Fields/card-button-fields.php index 1b8d13d9c..1bb6c24b8 100644 --- a/modules/ppcp-wc-gateway/src/Settings/Fields/card-button-fields.php +++ b/modules/ppcp-wc-gateway/src/Settings/Fields/card-button-fields.php @@ -32,7 +32,7 @@ return function ( ContainerInterface $container, array $fields ): array { ), '', '', - '' + '
' ), 'type' => 'ppcp-heading', 'screens' => array( diff --git a/modules/ppcp-wc-gateway/src/Settings/Fields/pay-later-tab-fields.php b/modules/ppcp-wc-gateway/src/Settings/Fields/pay-later-tab-fields.php index 06b4f51ce..0090b6632 100644 --- a/modules/ppcp-wc-gateway/src/Settings/Fields/pay-later-tab-fields.php +++ b/modules/ppcp-wc-gateway/src/Settings/Fields/pay-later-tab-fields.php @@ -847,7 +847,7 @@ return function ( ContainerInterface $container, array $fields ): array { __( 'When enabled, a %1$sPay Later button%2$s is displayed for eligible customers.%3$sPayPal buttons must be enabled to display the Pay Later button.', 'woocommerce-paypal-payments' ), '', '', - '' + '
' ), ), 'pay_later_button_enabled' => array( diff --git a/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php b/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php index 3a2513e98..011cfd192 100644 --- a/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php +++ b/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php @@ -233,7 +233,7 @@ return function ( ContainerInterface $container, array $fields ): array { ), '', '', - '' + '
' ), 'type' => 'ppcp-heading', 'screens' => array( diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index f2b250a5f..09cceda99 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -16,10 +16,12 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders; use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint; use WooCommerce\PayPalCommerce\WcGateway\Processor\CreditCardOrderInfoHandlingTrait; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WC_Order; use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository; use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture; @@ -57,24 +59,29 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class WcGatewayModule */ -class WCGatewayModule implements ModuleInterface { +class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; use CreditCardOrderInfoHandlingTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { $this->register_payment_gateways( $c ); $this->register_order_functionality( $c ); $this->register_columns( $c ); @@ -510,6 +517,8 @@ class WCGatewayModule implements ModuleInterface { } } ); + + return true; } /** @@ -822,13 +831,4 @@ class WCGatewayModule implements ModuleInterface { 2 ); } - - - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { - } } diff --git a/modules/ppcp-wc-subscriptions/composer.json b/modules/ppcp-wc-subscriptions/composer.json index 3cf50034b..4d8036d07 100644 --- a/modules/ppcp-wc-subscriptions/composer.json +++ b/modules/ppcp-wc-subscriptions/composer.json @@ -4,7 +4,7 @@ "description": "Module for WC Subscriptions plugin integration", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-wc-subscriptions/module.php b/modules/ppcp-wc-subscriptions/module.php index a298bac91..2d87674af 100644 --- a/modules/ppcp-wc-subscriptions/module.php +++ b/modules/ppcp-wc-subscriptions/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcSubscriptions; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): WcSubscriptionsModule { return new WcSubscriptionsModule(); }; diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php index 835ec4d4a..6b309be92 100644 --- a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php +++ b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php @@ -15,9 +15,10 @@ use WC_Payment_Token_CC; use WC_Payment_Tokens; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; @@ -31,26 +32,29 @@ use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; /** * Class SubscriptionModule */ -class WcSubscriptionsModule implements ModuleInterface { - +class WcSubscriptionsModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; use TransactionIdHandlingTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { - $this->add_gateways_support( $c ); + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { + $this->add_gateways_support( $c ); add_action( 'woocommerce_scheduled_subscription_payment_' . PayPalGateway::ID, /** @@ -255,14 +259,8 @@ class WcSubscriptionsModule implements ModuleInterface { 10, 3 ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } /** diff --git a/modules/ppcp-webhooks/composer.json b/modules/ppcp-webhooks/composer.json index 47795ad03..df9fc9b14 100644 --- a/modules/ppcp-webhooks/composer.json +++ b/modules/ppcp-webhooks/composer.json @@ -4,7 +4,7 @@ "description": "Webhooks module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/ppcp-webhooks/extensions.php b/modules/ppcp-webhooks/extensions.php index 7368e3c89..5f61258b6 100644 --- a/modules/ppcp-webhooks/extensions.php +++ b/modules/ppcp-webhooks/extensions.php @@ -10,10 +10,11 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Webhooks; use WooCommerce\PayPalCommerce\Onboarding\State; +use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( - 'wcgateway.settings.fields' => static function ( $container, array $fields ): array { + 'wcgateway.settings.fields' => static function ( array $fields, ContainerInterface $container ): array { $status_page_fields = array( 'webhook_status_heading' => array( 'heading' => __( 'Webhook Status', 'woocommerce-paypal-payments' ), diff --git a/modules/ppcp-webhooks/module.php b/modules/ppcp-webhooks/module.php index 7d223a528..2425c3049 100644 --- a/modules/ppcp-webhooks/module.php +++ b/modules/ppcp-webhooks/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Webhooks; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): WebhookModule { return new WebhookModule(); }; diff --git a/modules/ppcp-webhooks/src/WebhookModule.php b/modules/ppcp-webhooks/src/WebhookModule.php index 939a8d512..64c7dfea5 100644 --- a/modules/ppcp-webhooks/src/WebhookModule.php +++ b/modules/ppcp-webhooks/src/WebhookModule.php @@ -11,10 +11,11 @@ namespace WooCommerce\PayPalCommerce\Webhooks; use WC_Order; use WooCommerce\PayPalCommerce\Onboarding\State; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use Exception; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -26,22 +27,27 @@ use WooCommerce\PayPalCommerce\Webhooks\Status\Assets\WebhooksStatusPageAssets; /** * Class WebhookModule */ -class WebhookModule implements ModuleInterface { +class WebhookModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $container ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $container ): bool { $logger = $container->get( 'woocommerce.logger.woocommerce' ); assert( $logger instanceof LoggerInterface ); @@ -159,13 +165,7 @@ class WebhookModule implements ModuleInterface { ); } ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/woocommerce-logging/composer.json b/modules/woocommerce-logging/composer.json index 129854023..f1149f3fc 100644 --- a/modules/woocommerce-logging/composer.json +++ b/modules/woocommerce-logging/composer.json @@ -4,7 +4,7 @@ "description": "WC logging module for PPCP", "license": "GPL-2.0", "require": { - "php": "^7.2 | ^8.0", + "php": "^7.4 | ^8.0", "dhii/module-interface": "^0.3.0-alpha1" }, "autoload": { diff --git a/modules/woocommerce-logging/module.php b/modules/woocommerce-logging/module.php index b4ebf4c90..ac9624d5f 100644 --- a/modules/woocommerce-logging/module.php +++ b/modules/woocommerce-logging/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\WooCommerce\Logging; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return function (): ModuleInterface { +return function (): WooCommerceLoggingModule { return new WooCommerceLoggingModule(); }; diff --git a/modules/woocommerce-logging/src/WooCommerceLoggingModule.php b/modules/woocommerce-logging/src/WooCommerceLoggingModule.php index b314bc445..f345da263 100644 --- a/modules/woocommerce-logging/src/WooCommerceLoggingModule.php +++ b/modules/woocommerce-logging/src/WooCommerceLoggingModule.php @@ -9,38 +9,36 @@ declare(strict_types=1); namespace WooCommerce\WooCommerce\Logging; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class WooCommerceLoggingModule */ -class WooCommerceLoggingModule implements ModuleInterface { +class WooCommerceLoggingModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; } - /** - * Returns the key for the module. - * - * @return string|void + * {@inheritDoc} */ - public function getKey() { + public function run( ContainerInterface $c ): bool { + return true; } } diff --git a/package.json b/package.json index 05f4a5f22..b5dec8450 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "prearchive": "rm -rf $npm_package_name.zip", "archive": "zip -r $npm_package_name.zip . -x **.git/\\* **node_modules/\\*", "postarchive": "yarn run archive:cleanup && rm -rf $npm_package_name && unzip $npm_package_name.zip -d $npm_package_name && rm $npm_package_name.zip && zip -r $npm_package_name.zip $npm_package_name && rm -rf $npm_package_name", - "archive:cleanup": "zip -d $npm_package_name.zip .env* .ddev/\\* \\*.idea/\\* .editorconfig .eslintrc babel.config.json tests/\\* \\*test/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store \\*README.md \\*.gitattributes \\*.gitignore \\*composer.json \\*composer.lock patchwork.json phpunit.xml.dist .phpunit.result.cache phpcs.xml* psalm*.xml* playwright.config.js wp-cli.yml \\*.babelrc \\*package.json \\*webpack.config.js \\*yarn.lock \\*.travis.yml\\" + "archive:cleanup": "zip -d $npm_package_name.zip .env* .ddev/\\* \\*.idea/\\* .editorconfig tests/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store \\*README.md \\*.gitattributes \\*.gitignore \\*composer.json \\*composer.lock patchwork.json phpunit.xml.dist .phpunit.result.cache phpcs.xml* psalm*.xml* playwright.config.js wp-cli.yml \\*.babelrc \\*package.json \\*webpack.config.js \\*yarn.lock \\*.travis.yml\\" }, "config": { "wp_org_slug": "woocommerce-paypal-payments" diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 8e32114f5..d8661df81 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -3,8 +3,8 @@ WooCommerce PayPal Payments ruleset. - - + + diff --git a/readme.txt b/readme.txt index 8283e2669..6ed6f5d54 100644 --- a/readme.txt +++ b/readme.txt @@ -1,9 +1,9 @@ === WooCommerce PayPal Payments === Contributors: woocommerce, automattic, syde -Tags: woocommerce, paypal, payments, ecommerce, checkout, cart, pay later, apple pay, subscriptions, debit card, credit card, google pay -Requires at least: 5.3 +Tags: woocommerce, paypal, payments, ecommerce, credit card +Requires at least: 6.3 Tested up to: 6.6 -Requires PHP: 7.2 +Requires PHP: 7.4 Stable tag: 2.9.0 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -135,9 +135,9 @@ Check out the [Frequently Asked Questions](https://woocommerce.com/document/woo To install and configure WooCommerce PayPal Payments, you will need: -* WordPress Version 5.3 or newer (installed) -* WooCommerce Version 3.9 or newer (installed and activated) -* PHP Version 7.2 or newer +* WordPress Version 6.3 or newer (installed) +* WooCommerce Version 6.9 or newer (installed and activated) +* PHP Version 7.4 or newer * PayPal business **or** personal account = Installation instructions = @@ -179,7 +179,7 @@ If you encounter issues with the PayPal buttons not appearing after an update, p == Changelog == -= 2.9.0 - xxxx-xx-xx = += 2.9.0 - 2024-09-02 = * Fix - Fatal error in Block Editor when using WooCommerce blocks #2534 * Fix - Can't pay from block pages when the shipping callback is enabled and no shipping methods defined #2429 * Fix - Various Google Pay button fixes #2496 diff --git a/src/FilePathPluginFactory.php b/src/FilePathPluginFactory.php index 15f4f761d..f561b14d5 100644 --- a/src/FilePathPluginFactory.php +++ b/src/FilePathPluginFactory.php @@ -82,8 +82,8 @@ class FilePathPluginFactory implements FilePathPluginFactoryInterface { 'Title' => '', 'Description' => '', 'TextDomain' => '', - 'RequiresWP' => '5.0', - 'RequiresPHP' => '7.2', + 'RequiresWP' => '6.3', + 'RequiresPHP' => '7.4', ), $plugin_data ); diff --git a/src/PluginModule.php b/src/PluginModule.php index 5ec283941..fa4784f5c 100644 --- a/src/PluginModule.php +++ b/src/PluginModule.php @@ -9,37 +9,36 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class PluginModule */ -class PluginModule implements ModuleInterface { +class PluginModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/services.php', - require __DIR__ . '/extensions.php' - ); + public function services(): array { + return require __DIR__ . '/services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/extensions.php'; } /** - * Returns the key for the module. - * - * @return string|void + * {@inheritDoc} */ - public function getKey() { + public function run( ContainerInterface $c ): bool { + return true; } } diff --git a/tests/PHPUnit/ModularTestCase.php b/tests/PHPUnit/ModularTestCase.php index 14b7e61ef..7b77eee00 100644 --- a/tests/PHPUnit/ModularTestCase.php +++ b/tests/PHPUnit/ModularTestCase.php @@ -4,10 +4,10 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce; use WooCommerce\PayPalCommerce\Helper\RedirectorStub; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use function Brain\Monkey\Functions\when; @@ -69,16 +69,19 @@ class ModularTestCase extends TestCase } ], $overriddenServices); - $module = new class ($overriddenServices) implements ModuleInterface { + $module = new class ($overriddenServices) implements ServiceModule, ExecutableModule { + use ModuleClassNameIdTrait; + public function __construct(array $services) { $this->services = $services; } - public function setup(): ServiceProviderInterface{ - return new ServiceProvider($this->services, []); + public function services(): array { + return $this->services; } - public function run(ContainerInterface $c): void { + public function run(ContainerInterface $c): bool { + return true; } }; diff --git a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php index 95f1b7b48..207d31967 100644 --- a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php +++ b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php @@ -7,7 +7,6 @@ use Exception; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Dictionary; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization; @@ -23,6 +22,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper; use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Session\SessionHandler; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container\ReadOnlyContainer; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\TestCase; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -37,7 +37,7 @@ class OrderProcessorTest extends TestCase public function setUp(): void { parent::setUp(); - $this->environment = new Environment(new Dictionary([])); + $this->environment = new Environment(new ReadOnlyContainer([], [], [], [])); } public function testAuthorize() { diff --git a/tests/PHPUnit/bootstrap.php b/tests/PHPUnit/bootstrap.php index 75145ac11..8910b0241 100644 --- a/tests/PHPUnit/bootstrap.php +++ b/tests/PHPUnit/bootstrap.php @@ -4,6 +4,7 @@ declare(strict_types=1); define('TESTS_ROOT_DIR', dirname(__DIR__)); define('ROOT_DIR', dirname(TESTS_ROOT_DIR)); +require_once TESTS_ROOT_DIR . '/inc/wp_functions.php'; require_once ROOT_DIR . '/vendor/autoload.php'; require_once TESTS_ROOT_DIR . '/stubs/WC_Payment_Gateway.php'; require_once TESTS_ROOT_DIR . '/stubs/WC_Payment_Gateway_CC.php'; diff --git a/tests/inc/wp_functions.php b/tests/inc/wp_functions.php new file mode 100644 index 000000000..035e35885 --- /dev/null +++ b/tests/inc/wp_functions.php @@ -0,0 +1,41 @@ +get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index afae61309..978aaf017 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -7,9 +7,9 @@ * Author: WooCommerce * Author URI: https://woocommerce.com/ * License: GPL-2.0 - * Requires PHP: 7.2 + * Requires PHP: 7.4 * Requires Plugins: woocommerce - * WC requires at least: 3.9 + * WC requires at least: 6.9 * WC tested up to: 9.2 * Text Domain: woocommerce-paypal-payments * @@ -59,11 +59,11 @@ define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' ); return; } - if ( version_compare( PHP_VERSION, '7.2', '<' ) ) { + if ( version_compare( PHP_VERSION, '7.4', '<' ) ) { add_action( 'admin_notices', function() { - echo '

' . esc_html__( 'WooCommerce PayPal Payments requires PHP 7.1 or above.', 'woocommerce-paypal-payments' ), '

'; + echo '

' . esc_html__( 'WooCommerce PayPal Payments requires PHP 7.4 or above.', 'woocommerce-paypal-payments' ), '

'; } ); @@ -223,22 +223,6 @@ define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' ); } ); - add_action( - 'in_plugin_update_message-woocommerce-paypal-payments/woocommerce-paypal-payments.php', - static function( array $plugin_data, \stdClass $new_data ) { - if ( version_compare( $plugin_data['Version'], '3.0.0', '<' ) && - version_compare( $new_data->new_version, '3.0.0', '>=' ) ) { - printf( - '
%s: %s', - esc_html__( 'Warning', 'woocommerce-paypal-payments' ), - esc_html__( 'WooCommerce PayPal Payments version 3.0.0 contains significant changes that may impact your website. We strongly recommend reviewing the changes and testing the update on a staging site before updating it on your production environment.', 'woocommerce-paypal-payments' ) - ); - } - }, - 10, - 2 - ); - /** * Check if WooCommerce is active. * diff --git a/yarn.lock b/yarn.lock index b27939569..cd0043eb6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3031,9 +3031,9 @@ axe-core@=4.7.0: integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== axios@^1.6.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" - integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0"