Compare commits

...

254 commits

Author SHA1 Message Date
Himad M
bde2eb8fbe
Clean AppSwitch hash params on Pay for Order page
Some checks are pending
CI / PHP 7.4 (push) Waiting to run
CI / PHP 8.0 (push) Waiting to run
CI / PHP 8.1 (push) Waiting to run
CI / PHP 8.2 (push) Waiting to run
CI / PHP 8.3 (push) Waiting to run
CI / PHP 8.4 (push) Waiting to run
PR Playground Demo / prepare_version (push) Waiting to run
PR Playground Demo / build_plugin (push) Blocked by required conditions
PR Playground Demo / create_archive (push) Blocked by required conditions
PR Playground Demo / Comment on PR with Playground details (push) Blocked by required conditions
2025-08-26 12:41:44 -04:00
Emili Castells
af71409850
Merge pull request #3611 from woocommerce/PCP-5179-validate-and-update-developer-section-readme-md
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
PR Playground Demo / prepare_version (push) Has been cancelled
PR Playground Demo / build_plugin (push) Has been cancelled
PR Playground Demo / create_archive (push) Has been cancelled
PR Playground Demo / Comment on PR with Playground details (push) Has been cancelled
Validate and update Developer section README.md (5179)
2025-08-26 14:25:37 +02:00
Emili Castells
c8ae55c6aa
Merge pull request #3610 from woocommerce/PCP-5188-create-things-to-do-next-item-for-global-pay-later-messaging-enablement
Create 'Things to do next' item for global Pay Later messaging enablement (5188)
2025-08-26 09:29:57 +02:00
Miguel Pérez Pellicer
9d53b4ecf7 Tweak WordPress and WooCommerce wording 2025-08-25 18:12:38 +02:00
Miguel Pérez Pellicer
9c688efbeb Update README.md Dev section 2025-08-25 15:24:12 +02:00
Emili Castells
3b80ec8333
Merge pull request #3609 from woocommerce/PCP-5189-create-banner-for-global-pay-later-messaging-enablement-in-legacy-settings-ui
Add Pay Later messaging enablement banner for legacy UI (5189)
2025-08-25 14:30:04 +02:00
Narek Zakarian
54980aff73
feat: Create 'Things to do next' item for global Pay Later messaging enablement 2025-08-25 15:06:23 +04:00
Narek Zakarian
8e8ad4ff28
Add definition for the Todo 2025-08-25 15:05:41 +04:00
Narek Zakarian
d221544442
Add eligibility for the Todo 2025-08-25 15:05:31 +04:00
Narek Zakarian
7905fe29ad
feat: add Pay Later messaging enablement banner for legacy UI
Display notice on legacy settings pages when Pay Later messaging is automatically
enabled during 3.1.0 update, informing users about the change and providing links
to relevant settings.
2025-08-25 14:23:21 +04:00
Emili Castells
46eeeb0b36
Merge pull request #3607 from woocommerce/PCP-5180-fix-php-8-compatibility-issues-dynamic-properties-and-phpcs-configuration-phpcs
Some checks are pending
CI / PHP 7.4 (push) Waiting to run
CI / PHP 8.0 (push) Waiting to run
CI / PHP 8.1 (push) Waiting to run
CI / PHP 8.2 (push) Waiting to run
CI / PHP 8.3 (push) Waiting to run
CI / PHP 8.4 (push) Waiting to run
PR Playground Demo / prepare_version (push) Waiting to run
PR Playground Demo / build_plugin (push) Blocked by required conditions
PR Playground Demo / create_archive (push) Blocked by required conditions
PR Playground Demo / Comment on PR with Playground details (push) Blocked by required conditions
Refactor PHPCS configuration to be compatible to PHP8 (5180)
2025-08-25 09:21:27 +02:00
Miguel Pérez Pellicer
11ef14b13c Fix psalm false positive
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
PR Playground Demo / prepare_version (push) Has been cancelled
PR Playground Demo / build_plugin (push) Has been cancelled
PR Playground Demo / create_archive (push) Has been cancelled
PR Playground Demo / Comment on PR with Playground details (push) Has been cancelled
2025-08-22 17:37:38 +02:00
Miguel Pérez Pellicer
6856448c63 Fixing PHPCS parenthesis issue 2025-08-22 17:22:51 +02:00
Miguel Pérez Pellicer
1c2d7e032a Merge branch 'develop' into PCP-5180-fix-php-8-compatibility-issues-dynamic-properties-and-phpcs-configuration-phpcs 2025-08-22 17:16:02 +02:00
Miguel Pérez Pellicer
d1afbe4a46 Update PHPCS rules 2025-08-22 17:14:58 +02:00
Miguel Pérez Pellicer
2925b73430 Fix empty catch clause 2025-08-22 17:14:43 +02:00
Miguel Pérez Pellicer
224900c6a4 Fix else with simple inner ifs 2025-08-22 17:14:34 +02:00
Miguel Pérez Pellicer
720acca8a5 Fix loose equality ignore comment 2025-08-22 17:14:12 +02:00
Miguel Pérez Pellicer
ddb0ae3b11 Fix not necessary colon 2025-08-22 17:02:09 +02:00
Miguel Pérez Pellicer
4c9ca19ec8 Fix standalone increment issues 2025-08-22 17:02:00 +02:00
Miguel Pérez Pellicer
e2fb54c7a9 Fix grouped "use" statement issues. 2025-08-22 17:01:29 +02:00
Miguel Pérez Pellicer
060a86f5c9 Fix PHPCS spacing issues 2025-08-22 17:00:32 +02:00
Emili Castells
4faef951de
Merge pull request #3606 from woocommerce/PCP-5180-fix-php-8-compatibility-issues-dynamic-properties-and-phpcs-configuration
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
PR Playground Demo / prepare_version (push) Has been cancelled
PR Playground Demo / build_plugin (push) Has been cancelled
PR Playground Demo / create_archive (push) Has been cancelled
PR Playground Demo / Comment on PR with Playground details (push) Has been cancelled
Fix Dynamic Property Deprecation Warnings in PHP8 (5180)
2025-08-22 16:41:31 +02:00
Miguel Pérez Pellicer
2ab3b5ef54 Bump woocommerce-sniffs 2025-08-22 16:36:37 +02:00
Miguel Pérez Pellicer
fef445c928 Fix parenthesis PHPCS error 2025-08-22 16:33:44 +02:00
Emili Castells
7453207e17
Merge pull request #3603 from woocommerce/PCP-5047-branded-only-mode-not-entered-when-installing-plugin-via-payments-tab
Remove redundant double action
2025-08-22 11:20:45 +02:00
Miguel Pérez Pellicer
1c2403092b Remove unused import 2025-08-21 17:38:00 +02:00
Miguel Pérez Pellicer
b32d4eec46 Set default values as in WC_Payment_Gateway 2025-08-21 17:37:43 +02:00
Miguel Pérez Pellicer
c92ae6bca2 Fix deprecation messages in api-client module 2025-08-21 17:07:26 +02:00
Miguel Pérez Pellicer
71472084de Fix deprecation messages in tests 2025-08-21 17:06:39 +02:00
Miguel Pérez Pellicer
8aa1948ef9 Fix deprecation messages in wc-gateway module 2025-08-21 17:06:22 +02:00
Narek Zakarian
221786087a
Remove redundant double action 2025-08-20 19:08:12 +04:00
Emili Castells
0535faad10
Merge pull request #3601 from woocommerce/trunk
Merge trunk into develop
2025-08-19 14:35:08 +02:00
Emili Castells
f6b26a33ac
Merge pull request #3589 from woocommerce/PCP-5058-add-fastlane-support-for-au-merchants
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
PR Playground Demo / prepare_version (push) Has been cancelled
PR Playground Demo / build_plugin (push) Has been cancelled
PR Playground Demo / create_archive (push) Has been cancelled
PR Playground Demo / Comment on PR with Playground details (push) Has been cancelled
Add Fastlane support for AU/AUD (5058)
2025-08-19 14:15:03 +02:00
Emili Castells
88d7fc86c0
Merge pull request #3599 from woocommerce/PCP-5166-phase-2-add-wc-inbox-item
Phase 2: Settings Migration - Add Woo Inbox item (5166)
2025-08-19 12:47:09 +02:00
Narek Zakarian
4e949b0733
Check if element exists 2025-08-19 14:39:05 +04:00
Narek Zakarian
e3549abd8d
Fix possible duplication 2025-08-19 14:38:32 +04:00
Narek Zakarian
d372194d64
Merge branch 'PCP-5166-phase-2-add-wc-inbox-item' of github.com:woocommerce/woocommerce-paypal-payments into PCP-5166-phase-2-add-wc-inbox-item 2025-08-19 14:04:13 +04:00
Narek Zakarian
b1c1c23d91
Merge branch 'trunk' into PCP-5166-phase-2-add-wc-inbox-item
# Conflicts:
#	modules/ppcp-settings/resources/js/switchSettingsUi.js
2025-08-19 14:02:19 +04:00
Emili Castells
27412ebadd
Merge pull request #3189 from woocommerce/PCP-4235-no-pay-pal-order-id-found-in-order-meta-after-using-proceed-to-pay-pal-on-block-checkout
No PayPal order ID found in order # meta" after using "Proceed to PayPal" on block checkout (4235)
2025-08-19 11:49:48 +02:00
Niklas Gutberlet
e3d8cf0b8a
Merge pull request #3600 from woocommerce/PCP-5158-global-pay-later-messaging-enablement
Auto-enable Pay Later messaging for eligible stores on update (5158)
2025-08-19 11:05:39 +02:00
Emili Castells
184b5d1caa
Merge pull request #3553 from woocommerce/PCP-4976-new-ui-on-goole-pay-popup-no-shipping-methods-visible-when-pay-now-is-enabled
New UI - On Goole Pay popup no shipping methods visible when Pay Now is enabled (4976)
2025-08-19 09:16:39 +02:00
Narek Zakarian
49f4c5b162
Fix the CS 2025-08-18 20:39:56 +04:00
Narek Zakarian
0572f674ca
Add Inbox note about Pay Later messaging 2025-08-18 20:32:46 +04:00
Narek Zakarian
671bfb5e3a
Add ID for "Stay Updated" field to navigate with URL 2025-08-18 20:31:20 +04:00
Narek Zakarian
6ebda200b7
fix: target action buttons by text content due to DOM mutation
Action buttons change/get replaced after click, also there is no way to pass a `class` or any other attribute, making class-based targeting unreliable.
Use text content check to identify "Switch to New Settings" buttons specifically.
2025-08-18 20:30:29 +04:00
Emili Castells
6b84fa00f7
Merge pull request #3595 from woocommerce/PCP-4966-phase-2-change-banner-text
Some checks are pending
CI / PHP 7.4 (push) Waiting to run
CI / PHP 8.0 (push) Waiting to run
CI / PHP 8.1 (push) Waiting to run
CI / PHP 8.2 (push) Waiting to run
CI / PHP 8.3 (push) Waiting to run
CI / PHP 8.4 (push) Waiting to run
PR Playground Demo / prepare_version (push) Waiting to run
PR Playground Demo / build_plugin (push) Blocked by required conditions
PR Playground Demo / create_archive (push) Blocked by required conditions
PR Playground Demo / Comment on PR with Playground details (push) Blocked by required conditions
Phase 2: Settings Migration - Change banner text
2025-08-18 17:22:30 +02:00
Emili Castells
f1433f94e8
Merge pull request #3594 from woocommerce/PCP-4491-bcdc-item-does-not-persist-when-saving-vietnam-also-does-not-show-for-mexico
Use `is_acdc_enabled()` to prevent ACDC interference with BCDC in non-ACDC countries (4491)
2025-08-18 17:18:21 +02:00
Emili Castells Guasch
63426a67f3
Fix phpunit
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
PR Playground Demo / prepare_version (push) Has been cancelled
PR Playground Demo / build_plugin (push) Has been cancelled
PR Playground Demo / create_archive (push) Has been cancelled
PR Playground Demo / Comment on PR with Playground details (push) Has been cancelled
2025-08-18 17:13:37 +02:00
Emili Castells Guasch
d84a7dd5bb
Preserve shipping data after emptying the cart 2025-08-18 16:54:06 +02:00
Narek Zakarian
56283c6afb
feat: auto-enable Pay Later messaging for eligible stores on update
Automatically enables Pay Later messaging during plugin updates for stores with
"Stay updated" enabled and Pay Later support in their country.
2025-08-18 17:51:17 +04:00
Narek Zakarian
8d5b2b7930
Fix the cs
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
PR Playground Demo / prepare_version (push) Has been cancelled
PR Playground Demo / build_plugin (push) Has been cancelled
PR Playground Demo / create_archive (push) Has been cancelled
PR Playground Demo / Comment on PR with Playground details (push) Has been cancelled
2025-08-18 16:19:16 +04:00
Narek Zakarian
619a2468df
Completely delete the note from DB if is not enabled 2025-08-18 16:12:27 +04:00
Narek Zakarian
ea88086170
Use a better way to delete the note 2025-08-18 15:53:30 +04:00
Narek Zakarian
362cb40e30
Suppress Psalm error 2025-08-18 15:40:50 +04:00
Narek Zakarian
3d7654b2ea
And try/catch if cannot unregister the note 2025-08-18 15:35:29 +04:00
Narek Zakarian
ef86785d43
Remove the unnecessary debug logs
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
PR Playground Demo / prepare_version (push) Has been cancelled
PR Playground Demo / build_plugin (push) Has been cancelled
PR Playground Demo / create_archive (push) Has been cancelled
PR Playground Demo / Comment on PR with Playground details (push) Has been cancelled
2025-08-15 20:34:30 +04:00
Narek Zakarian
1179a17ae3
Update the JS logic to also work with new note 2025-08-15 20:33:18 +04:00
Narek Zakarian
628c83b7b9
Add settings page URL to JS config 2025-08-15 20:32:46 +04:00
Narek Zakarian
95e85ec51c
Update the registration logic
When notes are not enabled, we need to unregister/delete them if they were previously registered
2025-08-15 20:32:22 +04:00
Narek Zakarian
63ecb23039
Add config for new note with conditional registration
Adds condition logic for registering/unregistering notes during creation.
Includes new note for settings migration.
2025-08-15 20:30:22 +04:00
Narek Zakarian
e8c01c216c
Remove the notes registration condition from action callback
We will use the `is_enabled` method for dynamic note registration
2025-08-15 20:26:16 +04:00
Narek Zakarian
6d926b9dcc
Add is_enabled method for dynamic note registration
Allows registrar to dynamically register/unregister notes based on conditions
2025-08-15 20:24:14 +04:00
Narek Zakarian
0d9a40b851
Remove the unnecessary debug log 2025-08-15 10:53:47 +04:00
Narek Zakarian
e2402ccf97
Change the button label 2025-08-14 18:20:34 +04:00
Narek Zakarian
5642c6ec0a
Fix the link click 2025-08-14 18:18:33 +04:00
Narek Zakarian
7efb0dfcf1
Update the banner wording 2025-08-14 18:18:18 +04:00
Emili Castells Guasch
9443aecfaf
Merge branch 'trunk' into PCP-4976-new-ui-on-goole-pay-popup-no-shipping-methods-visible-when-pay-now-is-enabled 2025-08-14 13:56:30 +02:00
Emili Castells
3467d39eca
Merge pull request #3588 from woocommerce/PCP-2908-create-snippet-to-automatically-add-tracking-details-for-sendle-shipping-plugin
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
PR Playground Demo / prepare_version (push) Has been cancelled
PR Playground Demo / build_plugin (push) Has been cancelled
PR Playground Demo / create_archive (push) Has been cancelled
PR Playground Demo / Comment on PR with Playground details (push) Has been cancelled
Create an API function for adding tracking info. (2908)
2025-08-14 12:25:17 +02:00
Narek Zakarian
0ece1fb9b2
fix: fix psalm 2025-08-14 12:49:00 +04:00
Narek Zakarian
3f3c43ed2e
fix: add numeric check before converting items to integers
Prevents "Object could not be converted to int" notice when items array contains objects.
2025-08-14 12:43:04 +04:00
Emili Castells
728ea64b0c
Merge pull request #3556 from woocommerce/PCP-4520-apple-pay-visible-on-chrome-and-fire-fox-as-payment-gateway-on-classic-checkout
Remove the specified gateway when the button is disabled (4520)
2025-08-14 09:27:43 +02:00
Narek Zakarian
bc33c726f2
fix: use is_acdc_enabled()` to prevent ACDC interference with BCDC in non-ACDC countries
Previously, having ACDC enabled would block BCDC configuration even after
switching to non-ACDC countries. Now properly scopes the check to ACDC-specific state.
2025-08-13 19:16:11 +04:00
Emili Castells Guasch
5c3afb574a
Ensure PayPal order exist
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
PR Playground Demo / prepare_version (push) Has been cancelled
PR Playground Demo / build_plugin (push) Has been cancelled
PR Playground Demo / create_archive (push) Has been cancelled
PR Playground Demo / Comment on PR with Playground details (push) Has been cancelled
2025-08-13 16:18:58 +02:00
Narek Zakarian
e04761028d
Merge branch 'trunk' into PCP-4520-apple-pay-visible-on-chrome-and-fire-fox-as-payment-gateway-on-classic-checkout
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
PR Playground Demo / prepare_version (push) Has been cancelled
PR Playground Demo / build_plugin (push) Has been cancelled
PR Playground Demo / create_archive (push) Has been cancelled
PR Playground Demo / Comment on PR with Playground details (push) Has been cancelled
2025-08-13 14:18:50 +04:00
Emili Castells Guasch
9c0bc66369
Merge branch 'trunk' into PCP-4976-new-ui-on-goole-pay-popup-no-shipping-methods-visible-when-pay-now-is-enabled 2025-08-13 11:45:04 +02:00
Daniel Dudzic
40f08f4cfc
🧹 Update the code comment 2025-08-12 20:09:56 +02:00
Danny Dudzic
9bd47e8948
Merge pull request #3593 from woocommerce/wordpress-playground-update-paths-part-4
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
PR Playground Demo / prepare_version (push) Has been cancelled
PR Playground Demo / build_plugin (push) Has been cancelled
PR Playground Demo / create_archive (push) Has been cancelled
PR Playground Demo / Comment on PR with Playground details (push) Has been cancelled
Fix context object construction in Playground workflow script
2025-08-12 20:09:00 +02:00
Daniel Dudzic
0456f5bbbd
🔧 Fix context object construction in Playground workflow script 2025-08-12 20:04:48 +02:00
Daniel Dudzic
714c42aafc
🧹 Update the code comment 2025-08-12 19:47:24 +02:00
Danny Dudzic
527f414378
Merge pull request #3592 from woocommerce/wordpress-playground-update-paths-part-3
Fix context.repo undefined error in the Playground workflow
2025-08-12 19:44:50 +02:00
Daniel Dudzic
24009daa16
🐛 Fix context.repo undefined error in playground workflow 2025-08-12 19:39:07 +02:00
Daniel Dudzic
249bd383db
🧹Update code comments 2025-08-12 19:22:33 +02:00
Danny Dudzic
41f8f1c0c8
Merge pull request #3591 from woocommerce/wordpress-playground-update-paths-part2
Add modules to push paths being tracked for WordPress Playground Demo
2025-08-12 19:08:44 +02:00
Daniel Dudzic
6e81a4cb62
🗂️ Add modules to push paths being tracked for WordPress Playground Demo workflow 2025-08-12 18:50:02 +02:00
Danny Dudzic
5053adf8d8
Merge pull request #3590 from woocommerce/wordpress-playground-update-paths
Add modules to paths being tracked for WordPress Playground Demo
2025-08-12 18:20:38 +02:00
Daniel Dudzic
1dcf6884c0
🗂️ Add modules to paths being tracked for WordPress Playground Demo workflow 2025-08-12 18:11:57 +02:00
Daniel Dudzic
804f210f1f
Add Fastlane support for AU/AUD 2025-08-12 17:03:19 +02:00
Daniel Dudzic
9d6d36ee1d
🧹 Fix linting errors
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
PR Playground Demo / prepare_version (push) Has been cancelled
PR Playground Demo / build_plugin (push) Has been cancelled
PR Playground Demo / create_archive (push) Has been cancelled
PR Playground Demo / Comment on PR with Playground details (push) Has been cancelled
2025-08-12 15:59:34 +02:00
Daniel Dudzic
5224cd46e3
📒 Re-add the warning logging conditionally 2025-08-12 15:38:46 +02:00
Daniel Dudzic
84f926eafa
Merge branch 'trunk' of github.com:woocommerce/woocommerce-paypal-payments into PCP-4235-no-pay-pal-order-id-found-in-order-meta-after-using-proceed-to-pay-pal-on-block-checkout 2025-08-12 15:36:37 +02:00
Emili Castells Guasch
f0ab438208
Merge branch 'trunk' into PCP-4976-new-ui-on-goole-pay-popup-no-shipping-methods-visible-when-pay-now-is-enabled 2025-08-12 15:28:28 +02:00
Emili Castells
e2839f63f6
Merge pull request #3445 from woocommerce/PCP-4791-vaulting-my-account-pay-pal-gateway-and-button-should-not-be-displayed-when-customer-already-has-vaulted-pay-pal-account
PayPal button should not be displayed when customer already has vaulted PayPal account (4791)
2025-08-12 15:21:08 +02:00
Emili Castells Guasch
4c59a81e22
Improve comments 2025-08-12 15:12:34 +02:00
Emili Castells Guasch
efb5c4afd3
Merge branch 'trunk' into PCP-4791-vaulting-my-account-pay-pal-gateway-and-button-should-not-be-displayed-when-customer-already-has-vaulted-pay-pal-account 2025-08-12 15:08:47 +02:00
Narek Zakarian
afd39b28df
Use a class that uses the trait instead of calling it directly
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
2025-08-12 16:07:53 +04:00
Niklas Gutberlet
0d3eddfe3d
Merge pull request #3574 from woocommerce/PCP-5039-pull-request-playground-testing
Add PR preview via WordPress Playground (5039)
2025-08-12 13:53:58 +02:00
Daniel Dudzic
1ad92bb26d
🧹 Remove the #3574 PR-specific code 2025-08-12 13:19:11 +02:00
Narek Zakarian
fbd7d843ff
Make the method static to use from API functions 2025-08-11 19:58:41 +04:00
Narek Zakarian
9ed0c49bc9
Create an API function for adding tracking info.
This function retrieves the PayPal capture ID associated with the given WooCommerce order, creates a shipment instance, and sends it to the PayPal Order Tracking API.
If tracking information for the given tracking number already exists, it will be updated; otherwise, it will be added as new tracking information.
2025-08-11 19:58:22 +04:00
Emili Castells Guasch
dad1d4bb35
Merge trunk 2025-08-11 11:32:42 +02:00
Emili Castells Guasch
2468f4d918
Merge branch 'trunk' into PCP-4976-new-ui-on-goole-pay-popup-no-shipping-methods-visible-when-pay-now-is-enabled
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
2025-08-11 11:09:11 +02:00
Emili Castells
2ec6daca38
Merge pull request #3557 from woocommerce/PCP-4974-contact-module-virtual-product-from-block-pages-and-classic-checkout-always-return-default-contact-information-from-pay-pal-window
Some checks failed
CI / PHP 7.4 (push) Has been cancelled
CI / PHP 8.0 (push) Has been cancelled
CI / PHP 8.1 (push) Has been cancelled
CI / PHP 8.2 (push) Has been cancelled
CI / PHP 8.3 (push) Has been cancelled
CI / PHP 8.4 (push) Has been cancelled
Contact module -does not work for virtual products in block pages and classic checkout
2025-08-08 15:06:21 +02:00
Emili Castells Guasch
f93cfaa3e1
Refactor conditionals 2025-08-08 15:05:43 +02:00
Emili Castells
522e5a05f8
Merge pull request #3579 from woocommerce/PCP-5051-working-capital-things-to-do-next-wc-home-inbox
Add Working Capital notes (5001)
2025-08-08 14:59:05 +02:00
Emili Castells Guasch
e1c8b223d2
Do not add shipping if has all properties are empty 2025-08-08 11:45:13 +02:00
Emili Castells
bb0fc025e0
Merge pull request #3560 from woocommerce/PCP-4996-fastlane-add-classic-checkout-card-design-to-block-checkout
Fastlane: Add Classic Checkout card design to Block Checkout (4996)
2025-08-08 09:09:38 +02:00
Emili Castells
b8a979fc5f
Merge pull request #3584 from woocommerce/PCP-4995-remove-firefox-onboarding-notice
Remove the Firefox onboarding notice (4995)
2025-08-08 09:03:23 +02:00
Emili Castells
f3598eb1da
Merge pull request #3583 from woocommerce/PCP-4711
Prevent early is_enabled check for Apple Pay button in new UI (4711)
2025-08-07 10:56:19 +02:00
Emili Castells Guasch
64c140ab23
Make internal method private 2025-08-06 14:21:09 +02:00
Emili Castells Guasch
0ed05ac1ea
Fix phpunit 2025-08-06 14:03:37 +02:00
Emili Castells Guasch
03f68dc545
Fix psalm 2025-08-06 13:54:57 +02:00
Narek Zakarian
0d1fdb11e4
Remove the Firefox onboarding notice 2025-08-06 15:42:43 +04:00
Emili Castells Guasch
38f7355db1
Merge branch 'trunk' into PCP-4974-contact-module-virtual-product-from-block-pages-and-classic-checkout-always-return-default-contact-information-from-pay-pal-window 2025-08-06 12:22:18 +02:00
Himad M
f412c507e5
Prevent early is_enabled check for Apple Pay button in new UI 2025-08-05 19:20:14 -04:00
Niklas Gutberlet
8e1307cae6
Merge pull request #3540 from woocommerce/PCP-5011-remove-exposure-of-data-sdk-client-token-when-not-needed
Remove data-sdk-client-token field when not needed (5011)
2025-08-05 20:07:07 +02:00
Niklas Gutberlet
a3313148eb
Merge pull request #3536 from woocommerce/PCP-5009-phase-1-things-to-do-next-action-item
Add "Things to do next" item after settings migration (5009)
2025-08-05 18:36:26 +02:00
Narek Zakarian
0eb932a73e
use boolean as option value 2025-08-05 11:45:23 +04:00
Narek Zakarian
ca86cb83ae
Merge branch 'trunk' into PCP-5009-phase-1-things-to-do-next-action-item 2025-08-05 11:37:27 +04:00
Niklas Gutberlet
4a985fcb2c
Merge pull request #3505 from woocommerce/PCP-4970-add-defensive-code-for-realpath-failures-in-module-service-files
Create a centralized service for a path to the main plugin file (4970)
2025-08-04 19:42:12 +02:00
Niklas Gutberlet
f42721fc86
Merge pull request #3577 from woocommerce/PCP-4990
Add polling mechanism for renderer wrapper (4990)
2025-08-04 19:41:27 +02:00
Niklas Gutberlet
638ea5e141
Merge pull request #3532 from woocommerce/PCP-4989-disabling-credit-and-debit-card-bcdc-payments-does-not-work-in-the-new-ui
Disable the card button if BCDC is disabled (4989)
2025-08-04 19:40:28 +02:00
Niklas Gutberlet
e52866bae0
Merge pull request #3549 from woocommerce/PCP-5026-subscriptions-migrated-from-ppec-to-ppc-lose-gateway-id-during-renewal
Subscriptions migrated from PPEC to PPC fails on renewal
2025-08-04 19:37:07 +02:00
Niklas Gutberlet
ea1a9a0951
Merge pull request #3566 from woocommerce/PCP-5021-new-ui-google-pay-dependencies-between-locations-for-payment-method-availability
Prevent early `is_enabled()` check for Google Pay button in new UI (5021)
2025-08-04 19:35:59 +02:00
Niklas Gutberlet
af18a7bcdd
Merge pull request #3547 from woocommerce/PCP-4425-react-onboarding-wizard-step-4-should-be-skipped
Skip "Payment Methods" step for branded-only + BCDC and casual sellers (4425)
2025-08-04 19:34:36 +02:00
Niklas Gutberlet
7c8532da62
Merge pull request #3561 from woocommerce/PCP-4483-add-a-button-to-copy-merchant-credentials-in-settings-tab
Add buttons to copy merchant credentials in the Settings tab (4483)
2025-08-04 19:33:44 +02:00
Niklas Gutberlet
0b6046ad73
Merge pull request #3524 from woocommerce/PCP-4484-apple-pay-does-not-reflect-updated-shipping-method-or-address-changes-on-checkout
Apple Pay does not update shipping method or address changes on Classic Checkout (4484)
2025-08-04 19:31:40 +02:00
Niklas Gutberlet
95e579525e
Merge pull request #3432 from woocommerce/PCP-4590-verify-google-pay-3-ds
New test files for Google Pay and Fastlane (4590)
2025-08-04 19:30:24 +02:00
Niklas Gutberlet
ff38eb06ec
Merge pull request #3535 from woocommerce/PCP-5012-address-mapping-for-non-us-non-ca-countries-fails-in-fastlane-ryan-flow
Axo: Make the state address data optional to fix non US-CA compatibility (5012)
2025-08-04 19:26:23 +02:00
Niklas Gutberlet
4f64b4fa0b
Merge pull request #3543 from woocommerce/PCP-1575-fix-already-authorized
Remove redundant authorize call in subscription renewal handler (1575)
2025-08-04 19:25:00 +02:00
Niklas Gutberlet
5e1148bd0d
Merge pull request #3550 from woocommerce/PCP-3957-when-apple-pay-is-enabled-empty-space-for-apple-pay-button-is-showing-on-all-pages
When Apple Pay is enabled empty space for Apple Pay button is showing on all pages  (3957)
2025-08-04 19:24:14 +02:00
Niklas Gutberlet
22a207913b
Merge pull request #3542 from woocommerce/PCP-5007-google-pay-transaction-isnt-possible-from-express-checkout-and-block-cart
Fix Google Pay transaction from block pages (5007)
2025-08-04 19:23:18 +02:00
Niklas Gutberlet
206804cab0
Merge pull request #3527 from woocommerce/PCP-4999-pay-pal-configuration-description-logo-not-working-as-expected
Fix empty description handling and logo display for PayPal gateway (4999)
2025-08-04 19:22:40 +02:00
Niklas Gutberlet
70271d53b4
Merge pull request #3539 from woocommerce/PCP-4941-add-branded-only-system-report-entry
Fix the "New UI active" status report, add "Branded only" status report (4941)
2025-08-04 19:21:39 +02:00
Narek Zakarian
c48f3e6d2a
Add the plugin base name 2025-08-04 19:19:53 +04:00
Narek Zakarian
9bf1042a01
Fix the wording 2025-08-04 19:05:49 +04:00
Narek Zakarian
f0e88a65d8
Fix the wording 2025-08-04 19:05:16 +04:00
Narek Zakarian
d975a552a2
Add feature-flag for the working capital.
Is turned off by default
2025-08-04 18:48:15 +04:00
Narek Zakarian
1fdff2c44e
Add feature-flag for the working capital.
Is turned off by default
2025-08-04 18:48:12 +04:00
Narek Zakarian
05e24ea0a9
Add feature-flag for the working capital.
Is turned off by default
2025-08-04 18:48:04 +04:00
Narek Zakarian
d7a45138ff
Add method to get and set the stay_updated setting 2025-08-04 18:47:11 +04:00
Narek Zakarian
f32c013677
Apply services 2025-08-04 18:12:49 +04:00
Narek Zakarian
cdf43b5023
Add TodoEligibility for working capital 2025-08-04 18:12:36 +04:00
Narek Zakarian
b97da8dda0
Add TodoDefinition for working capital 2025-08-04 18:12:16 +04:00
Narek Zakarian
3587074bab
Simplify the code 2025-08-04 18:11:49 +04:00
Narek Zakarian
7e43288f74
Register the notes in 'Inbox' Woo section 2025-08-04 17:50:56 +04:00
Narek Zakarian
0108779972
Add appropriate services 2025-08-04 17:50:08 +04:00
Narek Zakarian
96c7f8a6e9
Create a registrar for inbox notes 2025-08-04 17:49:56 +04:00
Narek Zakarian
fc9c350e80
Create a factory for inbox notes 2025-08-04 17:49:48 +04:00
Narek Zakarian
3be262a1f4
Create the type for inbox note action 2025-08-04 17:49:33 +04:00
Narek Zakarian
80b4969555
Create the type for inbox note 2025-08-04 17:49:23 +04:00
Narek Zakarian
1a4697a31b
Add the task inside 'Things To Do Next' Woo section 2025-08-04 17:49:02 +04:00
Narek Zakarian
31e46a8e17
Add mapping for the stay_updated setting 2025-08-04 17:45:54 +04:00
Niklas Gutberlet
8a48732966
Merge pull request #3551 from woocommerce/PCP-5027-checkout-error
Fix undefined properties error in checkout block (5027)
2025-08-04 11:44:39 +02:00
Himad M
b631692bc6
Add polling mechanism for renderer wrapper 2025-08-01 15:31:46 -04:00
Daniel Dudzic
4a549f6625
Add Coming Soon mode to WordPress Playground blueprint 2025-08-01 15:25:31 +02:00
Daniel Dudzic
425e4c5ecc
🐛 Fix undefined context.repo error in playground comment workflow 2025-08-01 15:09:26 +02:00
Daniel Dudzic
d62bbae893
🔧 Add workflow and script files to trigger paths 2025-08-01 13:04:52 +02:00
Daniel Dudzic
05ba1d49eb
🔧 Resolve workflow validation errors and add playground testing 2025-08-01 13:00:58 +02:00
Daniel Dudzic
14cf824a13
🔧 Fix: resolve workflow validation errors and restructure jobs 2025-08-01 12:48:41 +02:00
Daniel Dudzic
e2a19b025d
🔧 Fix: move reusable workflow call to job level 2025-08-01 12:32:07 +02:00
Daniel Dudzic
fd87f93673
🧪 Simplify temporary testing to directly target PR #3574 2025-08-01 12:16:49 +02:00
Daniel Dudzic
a1902902a8
💰 Add path filtering to Playground workflow to optimize CI costs 2025-08-01 11:45:39 +02:00
Daniel Dudzic
37a465ca89
🔄 Add auto-update functionality to playground comments 2025-08-01 03:14:07 +02:00
Daniel Dudzic
bd51ff22aa
Merge branch 'PCP-5043-playground-blueprint-configuration' into PCP-5042-playground-artifact-integration 2025-08-01 00:07:15 +02:00
Daniel Dudzic
8db2078d5a
⚙️ Enhance playground blueprint with optimized WordPress configuration 2025-07-31 23:59:19 +02:00
Daniel Dudzic
2c4c2e0eac
🔗 Integrate artifact handling with playground demo workflow 2025-07-31 23:53:30 +02:00
Daniel Dudzic
11741da4c0
Merge branch 'PCP-5041-playground-comment-script' into PCP-5042-playground-artifact-integration 2025-07-31 23:45:51 +02:00
Daniel Dudzic
392bc75d6d
💬 Add playground comment script for PR demo links 2025-07-31 23:41:27 +02:00
Daniel Dudzic
094263a928
🚀 Add GitHub workflow for PR playground demo artifact creation 2025-07-31 23:35:21 +02:00
Diego Curbelo
69765ed47b
Merge pull request #3572 from woocommerce/release/3.0.9
3.0.9 release
2025-07-31 13:23:03 -03:00
Diego Curbelo
09ef2befd7
Update changelog 2025-07-31 13:19:09 -03:00
Danny Dudzic
f106645513
Merge pull request #3571 from woocommerce/release/3.0.9-rc1
Release 3.0.9-rc1
2025-07-31 16:34:08 +02:00
Daniel Dudzic
d8dbe206d2
📦 Prepare 3.0.9-rc1 release 2025-07-31 16:17:40 +02:00
Danny Dudzic
e75d7085e0
Merge pull request #3570 from woocommerce/PCP-5034-block-checkout-proceed-to-pay-pal-endless-loop-unable-to-complete-transaction
Fix endless redirect bug for the PayPal button (5034)
2025-07-31 12:18:37 +02:00
Daniel Dudzic
0b75c7fab8
🐛 Fix endless redirect bug for the PayPal button 2025-07-31 10:27:06 +02:00
Narek Zakarian
1062e548f2
Remove the watermark 2025-07-29 22:09:56 +04:00
Narek Zakarian
3c4114f84a
Fix the fonts 2025-07-29 22:09:45 +04:00
Narek Zakarian
bdaa16f3b8
Fix: Prevent early is_enabled() check for Google Pay button in new UI
In the new UI, the Google Pay button availability is determined by gateway enablement and location selection, not a global setting. The `is_enabled()` check depends on context functions (like `is_cart()`), which return `false` too early during block registration.

This commit skips the `is_enabled()` check when the new UI is active to avoid incorrect context evaluation and ensure the Google Pay button is properly registered.
2025-07-29 19:21:27 +04:00
Daniel Dudzic
9cfd827e27
Add buttons to copy merchant credentials in the Settings tab 2025-07-29 01:16:48 +02:00
Narek Zakarian
7835222484
Adjust component 2025-07-28 16:27:50 +04:00
Narek Zakarian
4fcefe3773
Adjust styles 2025-07-28 16:27:36 +04:00
Emili Castells Guasch
28adbcd649
Fix not adding address to result 2025-07-25 16:12:07 +02:00
Emili Castells Guasch
efe753de42
Ensure shipping address exist before using it 2025-07-25 15:24:41 +02:00
Emili Castells Guasch
416ce86973
Ensure shipping address exist before using it 2025-07-25 15:21:20 +02:00
Narek Zakarian
f3c3cac111
Fix the cs 2025-07-25 16:26:15 +04:00
Narek Zakarian
70336ea458
Add return type 2025-07-25 16:07:18 +04:00
Narek Zakarian
6642e7d208
Remove the specified gateway when the button is disabled.
Filter is added to remove the available payment gateways (e.g., Google Pay or Apple Pay) when the button is disabled for the current location (e.g., classic checkout) in the styling settings.
This is necessary because WooCommerce automatically includes the gateway when it is enabled,even if the button is hidden via settings.
2025-07-25 16:03:13 +04:00
Emili Castells Guasch
41344aa57d
Fix phpcs 2025-07-24 14:45:45 +02:00
Emili Castells Guasch
25ad8b4b9c
Get shipping enabled value from new settings model and fallback to legacy if not exist 2025-07-24 14:37:07 +02:00
Himad M
0385aad301
Add optional chaining to address properties 2025-07-23 16:32:22 -04:00
Emili Castells Guasch
33b27e5ee4
Add condition to display button if apple session and can make payments 2025-07-23 14:45:00 +02:00
Emili Castells Guasch
546a775c49
Remove should_mock_ppec_gateway logic 2025-07-23 12:42:44 +02:00
Narek Zakarian
ecb46a9ec7
Skip "Payment Methods" step for branded-only + BCDC and casual sellers. 2025-07-22 18:24:17 +04:00
Himad M
c4db1ac53a
Remove redundant authorize call in subscription renewal handler 2025-07-21 15:58:52 -04:00
Narek Zakarian
8fccfd637f
Add check for payer 2025-07-21 18:38:03 +04:00
Narek Zakarian
086fe17983
Add check for purchase_units 2025-07-21 18:37:53 +04:00
Emili Castells Guasch
f1ed2233f3
Merge branch 'trunk' into PCP-5026-subscriptions-migrated-from-ppec-to-ppc-lose-gateway-id-during-renewal 2025-07-21 16:16:13 +02:00
Emili Castells Guasch
521099b16d
Add billing agreements fallback to existing method instead of new filter 2025-07-21 15:27:26 +02:00
Emili Castells Guasch
038ee9821b
Ensure billing agreement is used as payment token for ppec orders 2025-07-21 14:04:23 +02:00
Emili Castells Guasch
576b70c3c4
Exclude phpcs rule 2025-07-21 09:49:06 +02:00
Emili Castells Guasch
ac2bfa13af
Remove comments 2025-07-21 09:34:40 +02:00
Emili Castells Guasch
6cccb0c973
Fix psalm 2025-07-18 17:05:09 +02:00
Emili Castells Guasch
749ffe0f76
Add check for user logged in and is paypal continuation 2025-07-18 17:01:35 +02:00
Emili Castells Guasch
5a2b079f80
Rename service 2025-07-18 15:18:39 +02:00
Emili Castells Guasch
0d265f5aa1
Add validation to axo script attributes endpoint 2025-07-18 13:56:37 +02:00
Emili Castells Guasch
fbe13cb7dd
Fix phpcs 2025-07-18 11:29:12 +02:00
Emili Castells Guasch
06c000b42b
Add axo script attributes to blocks checkout 2025-07-18 11:09:23 +02:00
Emili Castells Guasch
57e36ed954
Get sdk token from paypal 2025-07-17 17:36:06 +02:00
Emili Castells Guasch
450e4eb6aa
Add axo script attributes boilerplate to classic checkout 2025-07-17 16:44:25 +02:00
Emili Castells Guasch
fff4f5bb84
Add sdk client token when loading PayPal script (WIP) 2025-07-17 16:04:31 +02:00
Narek Zakarian
86696e91b5
Add "Branded only" status report 2025-07-17 17:39:52 +04:00
Emili Castells Guasch
bea875a83b
Add sdk client token when loading PayPal script (WIP) 2025-07-17 15:07:13 +02:00
Narek Zakarian
efed7b793a
Fix the 'New UI active' status report.
After making the new settings module always active, we need to rely on `SettingsModule::should_use_the_old_ui()` to determine whether the new UI is being used.
2025-07-17 16:21:38 +04:00
Emili Castells Guasch
c9795e8b4d
Remove all sdk_client_token references in the code 2025-07-16 17:56:11 +02:00
Emili Castells Guasch
7973fd433e
Remove all sdk_client_token references in the code 2025-07-16 17:40:53 +02:00
Narek Zakarian
0fc54d1df5
Remove the option on plugin uninstall 2025-07-16 19:14:46 +04:00
Narek Zakarian
4e93aca57a
Add appropriate services 2025-07-16 19:14:24 +04:00
Narek Zakarian
e3eee0970d
Add the todo based on option 2025-07-16 19:14:11 +04:00
Narek Zakarian
3001245c3b
Update option if the migration is done. 2025-07-16 19:13:53 +04:00
Daniel Dudzic
bb04e0ebe8
🐛 Make the state data optional in the Fastlane Classic Checkout to fix non US-CA compatibility 2025-07-16 12:45:59 +02:00
Daniel Dudzic
0be7304b11
🐛 Make the state shipping address data optional in the Fastlane Block Checkout to fix non US-CA compatibility 2025-07-16 12:36:53 +02:00
Narek Zakarian
13f8a63e8f
Check for Card button gateway enabled 2025-07-15 19:09:55 +04:00
Narek Zakarian
fadd2f5b40
Disable the card button if BCDC is disabled 2025-07-15 19:09:22 +04:00
Daniel Dudzic
3ba91e8f44
🐛 Fix the empty description for the block checkout 2025-07-14 20:30:17 +02:00
Daniel Dudzic
bd9343c893
🐛 Fix empty description handling and logo display for PayPal gateway
- Override `get_description()` to properly handle saved empty descriptions using `array_key_exists()` instead of `get_option()` fallbacks
- Fix issue where empty descriptions reverted to defaults
- Utilize the `woocommerce_paypal_payments_paypal_gateway_icon` filter to handle PayPal logo display correctly
2025-07-14 19:31:01 +02:00
Emili Castells Guasch
fb35981e49
Add transaction info call when cart or checkout update event is triggered 2025-07-11 17:30:33 +02:00
Emili Castells Guasch
7744355751
Add transaction info call when cart or checkout update event is triggered 2025-07-11 17:21:11 +02:00
Narek Zakarian
b0e9d8fb2c
Update the lock file 2025-07-07 18:47:58 +04:00
Narek Zakarian
709e014db4
Merge branch 'trunk' into PCP-4970-add-defensive-code-for-realpath-failures-in-module-service-files
# Conflicts:
#	composer.lock
2025-07-07 18:45:41 +04:00
Narek Zakarian
579f9989c0
Fix Psalm 2025-07-04 22:19:39 +04:00
Narek Zakarian
95239b51ae
Fix Psalm 2025-07-04 22:17:15 +04:00
Narek Zakarian
b483115883
Update lib packages 2025-07-04 22:05:58 +04:00
Narek Zakarian
238ebc0949
Fix the plugin main file path 2025-07-04 20:18:23 +04:00
Narek Zakarian
86cae6ca4a
Use the 'ppcp.path-to-plugin-folder' config 2025-07-04 19:27:26 +04:00
Narek Zakarian
943a0d197c
Use the 'ppcp.path-to-plugin-main-file' config 2025-07-04 19:27:13 +04:00
Narek Zakarian
235a9f45cf
Add services to use Package::PROPERTIES of inpsyde/modularity 2025-07-04 19:26:10 +04:00
Narek Zakarian
1cf46c356d
Fix the psalm 2025-07-04 17:37:49 +04:00
Narek Zakarian
e713abfd75
Just return __FILE__ when the realpath fails. 2025-07-04 17:11:20 +04:00
Narek Zakarian
0caa74c021
Refactor the code to use the new service 2025-07-04 17:10:37 +04:00
Narek Zakarian
30b7a592c5
Create a centralized service for a path to the main plugin file. 2025-07-04 17:09:36 +04:00
Emili Castells Guasch
6c6a047a68
Merge branch 'trunk' into PCP-4791-vaulting-my-account-pay-pal-gateway-and-button-should-not-be-displayed-when-customer-already-has-vaulted-pay-pal-account 2025-06-23 14:27:01 +02:00
Emili Castells Guasch
8d34d34517
Merge branch 'trunk' into PCP-4791-vaulting-my-account-pay-pal-gateway-and-button-should-not-be-displayed-when-customer-already-has-vaulted-pay-pal-account 2025-06-23 13:12:07 +02:00
Emili Castells Guasch
fa33db8191
Ensure customer id exist before using it 2025-06-23 11:40:29 +02:00
Emili Castells Guasch
dc850fca7d
Merge branch 'trunk' into PCP-4791-vaulting-my-account-pay-pal-gateway-and-button-should-not-be-displayed-when-customer-already-has-vaulted-pay-pal-account 2025-06-23 11:17:36 +02:00
Emili Castells Guasch
7dd40dfeec
Fix phpcs 2025-06-09 14:01:00 +02:00
Emili Castells Guasch
2f625873f8
Do not display PayPal button is payment already exist 2025-06-06 12:26:38 +02:00
Philipp Stracker
1203d6e8c5
♻️ Improve the Google Pay test script
Main change: The SCA selector is always visible and can be changed independently of the client credentials
2025-05-16 15:39:48 +02:00
Philipp Stracker
3e84cd9e6c
💡 Document the URL to the test page 2025-05-08 12:25:58 +02:00
Philipp Stracker
98ceefd296
🧑‍💻 Add test script for 3DS in AXO 2025-05-08 12:22:30 +02:00
Philipp Stracker
7bcc18bdc1
💡 Document the URL to the test page 2025-05-06 19:06:39 +02:00
Philipp Stracker
676306f793
💄 Add form to enter client ID and secret 2025-05-06 19:01:46 +02:00
Philipp Stracker
41f3cd0a61
🧑‍💻 Add Google Pay 3DS test page 2025-05-06 18:40:42 +02:00
Daniel Hüsken
535f5f2d96
Remove additional logging for exception, becuse it irritates in the logs when exception is handed correctly. 2025-03-07 08:36:15 +01:00
335 changed files with 6293 additions and 2528 deletions

160
.github/scripts/playground-comment.js vendored Normal file
View file

@ -0,0 +1,160 @@
const generateWordpressPlaygroundBlueprint = (runId, prNumber, artifactName) => {
const defaultSchema = {
landingPage: '/wp-admin/admin.php?page=wc-settings&tab=advanced&section=blueprint&activate-multi=true',
preferredVersions: {
php: '8.0',
wp: 'latest',
},
phpExtensionBundles: ['kitchen-sink'],
// Enable networking for API calls and external connections
features: {
networking: true
},
steps: [
// Step 1: Install and activate WooCommerce
{
step: 'installPlugin',
pluginData: {
resource: 'wordpress.org/plugins',
slug: 'woocommerce'
},
options: {
activate: true
}
},
// Step 2: Install PayPal Payments plugin from PR artifact
{
step: 'installPlugin',
pluginZipFile: {
resource: 'url',
url: `https://playground.wordpress.net/plugin-proxy.php?org=woocommerce&repo=woocommerce-paypal-payments&workflow=PR%20Playground%20Demo&artifact=${artifactName}&pr=${prNumber}`,
},
options: {
activate: true,
},
},
// Step 3: Skip WooCommerce onboarding wizard
{
step: 'setSiteOptions',
options: {
woocommerce_onboarding_profile: {
skipped: true,
},
},
},
// Step 4: Enable Coming Soon mode
{
step: 'setSiteOptions',
options: {
woocommerce_coming_soon: 'yes',
},
},
// Step 5: Set up admin user login
{
step: 'login',
username: 'admin',
password: 'password',
},
],
// Initialize empty plugins array (can be extended later)
plugins: [],
};
return defaultSchema;
};
async function run({ github, context, core }) {
try {
const commentInfo = {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
};
// Validate required environment variables
if (!process.env.PLUGIN_VERSION || !process.env.ARTIFACT_NAME) {
core.setFailed('Missing required environment variables: PLUGIN_VERSION or ARTIFACT_NAME');
return;
}
// Check for existing playground comment to update instead of creating duplicate
const comments = await github.rest.issues.listComments(commentInfo);
const existingComment = comments.data.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('Test using WordPress Playground')
);
const defaultSchema = generateWordpressPlaygroundBlueprint(
context.runId,
context.issue.number,
process.env.ARTIFACT_NAME
);
const url = `https://playground.wordpress.net/#${JSON.stringify(defaultSchema)}`;
const body = `## Test using WordPress Playground
The changes in this pull request can be previewed and tested using a [WordPress Playground](https://developer.wordpress.org/playground/) instance.
[WordPress Playground](https://developer.wordpress.org/playground/) is an experimental project that creates a full WordPress instance entirely within the browser.
**🔗 [Test this pull request with WordPress Playground](${url})**
### What's included:
- WordPress (latest)
- WooCommerce (latest)
- PayPal Payments plugin v${process.env.PLUGIN_VERSION} (built from this PR)
### Login credentials:
- **Username:** \`admin\`
- **Password:** \`password\`
### Plugin Details:
- **Version:** ${process.env.PLUGIN_VERSION}
- **Commit:** ${context.payload.pull_request.head.sha}
- **Artifact:** ${process.env.ARTIFACT_NAME}
> 💡 The demo environment resets each time you refresh. Perfect for testing!
>
> 🔄 This link updates automatically with each new commit to the PR.
>
> This URL is valid for 30 days from when this comment was last updated.
---
<sub>🤖 Auto-generated for commit ${context.payload.pull_request.head.sha} Last updated: ${new Date().toISOString()}</sub>`;
if (existingComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: body
});
core.info(`Successfully updated existing playground comment #${existingComment.id} on PR #${context.issue.number}`);
} else {
// Create new comment
await github.rest.issues.createComment({
...commentInfo,
body: body
});
core.info(`Successfully created new playground comment on PR #${context.issue.number}`);
}
} catch (error) {
core.setFailed(`Failed to create/update playground comment: ${error.message}`);
core.error(`Error details: ${error.stack}`);
}
}
module.exports = { run };

163
.github/workflows/pr-playground-demo.yml vendored Normal file
View file

@ -0,0 +1,163 @@
name: PR Playground Demo
on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- "src/**"
- "assets/**"
- "modules/**"
- "package.json"
- "composer.json"
push:
paths:
- "src/**"
- "assets/**"
- "modules/**"
- "package.json"
- "composer.json"
# Cancels all previous workflow runs for pull requests that have not completed.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
cancel-in-progress: true
# Disable permissions for all available scopes by default.
permissions: {}
jobs:
prepare_version:
runs-on: ubuntu-latest
# Only run for the main repository, not forks
if: github.repository == 'woocommerce/woocommerce-paypal-payments' && github.event_name == 'pull_request'
outputs:
artifact_name: ${{ steps.version.outputs.artifact_name }}
plugin_version: ${{ steps.version.outputs.plugin_version }}
steps:
- uses: actions/checkout@v4
- name: Set plugin version and artifact name
id: version
run: |
BASE_VERSION=$(sed -nE '/Version:/s/.* ([0-9.]+).*/\1/p' woocommerce-paypal-payments.php)
VERSUFFIX="${GITHUB_RUN_ID}-g$(git rev-parse --short HEAD)"
PR_VERSION="${BASE_VERSION}-pr${{ github.event.pull_request.number }}-${VERSUFFIX}"
ARTIFACT_NAME="woocommerce-paypal-payments-${PR_VERSION}"
echo "plugin_version=$PR_VERSION" >> $GITHUB_OUTPUT
echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
build_plugin:
needs: prepare_version
uses: inpsyde/reusable-workflows/.github/workflows/build-plugin-archive.yml@a9af34f34e95cbe18703198c7e972e97ebcd7473
with:
PHP_VERSION: 7.4
NODE_VERSION: 22
PLUGIN_MAIN_FILE: ./woocommerce-paypal-payments.php
PLUGIN_VERSION: ${{ needs.prepare_version.outputs.plugin_version }}
PLUGIN_FOLDER_NAME: woocommerce-paypal-payments
ARCHIVE_NAME: ${{ needs.prepare_version.outputs.artifact_name }}
COMPILE_ASSETS_ARGS: "-vv --env=root"
create_archive:
runs-on: ubuntu-latest
needs: [prepare_version, build_plugin]
outputs:
artifact_name: ${{ needs.prepare_version.outputs.artifact_name }}
plugin_version: ${{ needs.prepare_version.outputs.plugin_version }}
steps:
- uses: actions/checkout@v4
- name: Save PR details
run: |
mkdir -p ./pr
echo "${{ github.event.pull_request.number }}" > ./pr/NR
echo "${{ needs.prepare_version.outputs.plugin_version }}" > ./pr/VERSION
echo "${{ needs.prepare_version.outputs.artifact_name }}" > ./pr/ARTIFACT
- name: Upload PR number
uses: actions/upload-artifact@v4
with:
name: pr-details
path: pr/
playground_demo:
name: Comment on PR with Playground details
runs-on: ubuntu-latest
needs: create_archive
permissions:
issues: write
pull-requests: write
# Only run for pull requests in the main repository
if: github.repository == 'woocommerce/woocommerce-paypal-payments' && github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Download PR details artifact
uses: actions/github-script@v7
with:
script: |
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
});
const matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name === 'pr-details'
})[0];
if (!matchArtifact) {
core.setFailed('No pr-details artifact found!');
return;
}
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
const fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr-details.zip', Buffer.from(download.data));
- name: Unzip PR details
run: unzip pr-details.zip
- name: Create or update PR playground comment
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const pr_number = fs.readFileSync('./NR', 'utf8').trim();
const plugin_version = fs.readFileSync('./VERSION', 'utf8').trim();
const artifact_name = fs.readFileSync('./ARTIFACT', 'utf8').trim();
// Load the comment script
const script = require('./.github/scripts/playground-comment.js');
// Set environment variables for the script
process.env.PLUGIN_VERSION = plugin_version;
process.env.ARTIFACT_NAME = artifact_name;
const updatedContext = {
repo: {
owner: context.repo.owner,
repo: context.repo.repo
},
issue: {
number: parseInt(pr_number, 10)
},
runId: context.runId,
payload: {
pull_request: {
number: parseInt(pr_number, 10),
head: {
sha: context.sha
}
}
}
};
await script.run({ github, context: updatedContext, core });

View file

@ -52,26 +52,56 @@ Visit our [official documentation](https://woocommerce.com/document/woocommerce-
## Development
### Install dependencies & build
### Setup using DDEV (recommended)
You can install WooCommerce PayPal Payments locally using the dev environment of your preference, or you can use the DDEV setup provided in this repository. Which includes WordPress, WooCommerce and all development tools.
To set up the DDEV environment, follow these steps:
0. Install Docker and [DDEV](https://ddev.readthedocs.io/en/stable/).
1. Edit the [configuration](https://docs.ddev.com/en/stable/users/configuration/config/#managing-configuration) in the [`.ddev/config.yml`](.ddev/config.yaml) file if needed.
2. `$ ddev setup` to setup and orchestrate the plugin, WooCommerce and WordPress
3. Open https://woocommerce-paypal-payments.ddev.site
Use `$ ddev reset` for reinstallation (will destroy all site data).
You may also need `$ ddev restart` to apply the config changes.
#### Running tests and other tasks in the DDEV environment
Tests and code style:
- `$ yarn ddev:unit-tests`
- `$ yarn ddev:lint`
- `$ yarn ddev:fix-lint`
- `$ yarn ddev:lint-js`
See [package.json](/package.json) for other useful commands.
For debugging, see [the DDEV docs](https://ddev.readthedocs.io/en/stable/users/step-debugging/).
Enable xdebug via `$ ddev xdebug enable`, and press `Start Listening for PHP Debug Connections` in PHPStorm.
After creating the server in the PHPStorm dialog, you need to set the local project path for the server plugin path.
Check [this article](https://docs.ddev.com/en/stable/users/debugging-profiling/step-debugging/#phpstorm-debugging-setup) for a detailed guide.
## Setup in other environments
#### Install dependencies & build
- `$ composer install`
- `$ yarn install`
Optionally, change the `PAYPAL_INTEGRATION_DATE` constant to `gmdate( 'Y-m-d' )` to run the latest PayPal JavaScript SDK
### Unit tests and code style
#### Unit tests and code style
1. `$ composer install`
2. `$ ./vendor/bin/phpunit`
3. `$ ./vendor/bin/phpcs`
4. `$ ./vendor/bin/psalm`
5. `$ wp-scripts lint-js`
6. `$ yarn run test:unit-js` - Ensure node version is `18` or above
1. `$ ./vendor/bin/phpunit`
2. `$ ./vendor/bin/phpcs`
3. `$ ./vendor/bin/psalm`
4. `$ yarn run lint-js`
5. `$ yarn run test:unit-js` - Ensure node version is `18` or above
### Building a release package
If you want to build a release package
(that can be used for deploying a new version on wordpress.org or manual installation on a WP website via ZIP uploading),
(that can be used for deploying a new version on wordpress.org or manual installation on a WordPress website via ZIP uploading),
follow these steps:
1. Clone the repository and `cd` into it.
@ -88,38 +118,6 @@ or if using the DDEV setup:
$ yarn run ddev:build-package
```
## Setup
You can install WooCommerce PayPal Payments locally using the dev environment of your preference, or you can use the DDEV setup provided in this repository which includes WP, WC and all developments tools.
To set up the DDEV environment, follow these steps:
0. Install Docker and [DDEV](https://ddev.readthedocs.io/en/stable/).
1. Edit the configuration in the [`.ddev/config.yml`](.ddev/config.yaml) file if needed.
2. `$ ddev start`
3. `$ ddev orchestrate` to install WP/WC.
4. Open https://wc-pp.ddev.site
Use `$ ddev orchestrate -f` for reinstallation (will destroy all site data).
You may also need `$ ddev restart` to apply the config changes.
### Running tests and other tasks in the DDEV environment
Tests and code style:
- `$ yarn ddev:test`
- `$ yarn ddev:lint`
- `$ yarn ddev:fix-lint`
- `$ yarn ddev:lint-js`
See [package.json](/package.json) for other useful commands.
For debugging, see [the DDEV docs](https://ddev.readthedocs.io/en/stable/users/step-debugging/).
Enable xdebug via `$ ddev xdebug`, and press `Start Listening for PHP Debug Connections` in PHPStorm.
After creating the server in the PHPStorm dialog, you need to set the local project path for the server plugin path.
It should look [like this](https://i.imgur.com/ofsF1Mc.png).
See [tests/playwright](tests/playwright) for e2e (browser-based) tests.
## Test account setup
You will need a PayPal sandbox merchant and customer accounts to configure the plugin and make test purchases with it.
@ -133,7 +131,7 @@ For testing webhooks locally, follow these steps to set up ngrok:
0. Install [ngrok](https://ngrok.com/).
1.
- If using DDEV, run our wrapper Bash script which will start `ddev share` and replace the URLs in the WP database:
- If using DDEV, run our wrapper Bash script which will start `ddev share` and replace the URLs in the WordPress database:
```
$ .ddev/bin/share
```

View file

@ -17,6 +17,8 @@ use RuntimeException;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
use WooCommerce\PayPalCommerce\OrderTracking\Shipment\ShipmentFactoryInterface;
use WooCommerce\PayPalCommerce\PPCP;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
@ -159,3 +161,53 @@ function ppcp_update_order_refund_fees( WC_Order $wc_order ): void {
assert( $updater instanceof RefundFeesUpdater );
$updater->update( $wc_order );
}
/**
* Creates or updates PayPal order tracking information for a WooCommerce order.
*
* Retrieves the PayPal capture ID associated with the given WooCommerce order,
* creates a shipment instance, and sends it to the PayPal Order Tracking API.
* If tracking information for the given tracking number already exists, it will
* be updated; otherwise, it will be added as new tracking information.
*
* @param WC_Order $wc_order WooCommerce order instance to attach tracking to.
* @param string $tracking_number Tracking number provided by the carrier.
* @param string $carrier Name of the shipping carrier (must be PayPal-approved).
* @param string $status Shipment status (must be PayPal-approved). Defaults to 'SHIPPED'.
*
* @see https://developer.paypal.com/docs/tracking/reference/carriers/ List of PayPal-approved carriers.
* @see https://developer.paypal.com/docs/tracking/reference/shipping-status/ List of PayPal-approved shipment statuses.
*
* @return void
*/
function ppcp_create_order_tracking( WC_Order $wc_order, string $tracking_number, string $carrier, string $status = 'SHIPPED' ): void {
$shipment_factory = PPCP::container()->get( 'order-tracking.shipment.factory' );
assert( $shipment_factory instanceof ShipmentFactoryInterface );
$endpoint = PPCP::container()->get( 'order-tracking.endpoint.controller' );
assert( $endpoint instanceof OrderTrackingEndpoint );
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $endpoint->get_paypal_order_transaction_id( $paypal_order );
if ( is_null( $capture_id ) ) {
throw new RuntimeException( 'Could not retrieve transaction ID from PayPal order' );
}
$wc_order_id = $wc_order->get_id();
$ppcp_shipment = $shipment_factory->create_shipment(
$wc_order_id,
$capture_id,
$tracking_number,
$status,
'OTHER',
$carrier,
array()
);
$tracking_information = $endpoint->get_tracking_information( $wc_order_id, $tracking_number );
$tracking_information
? $endpoint->update_tracking_information( $ppcp_shipment, $wc_order_id )
: $endpoint->add_tracking_information( $ppcp_shipment, $wc_order_id );
}

View file

@ -32,7 +32,7 @@ return function (
$modules = apply_filters( 'woocommerce_paypal_payments_modules', $modules );
// Initialize plugin.
$properties = PluginProperties::new( __FILE__ );
$properties = PluginProperties::new( "$root_dir/woocommerce-paypal-payments.php" );
$bootstrap = Package::new( $properties );
foreach ( $modules as $module ) {

View file

@ -1,5 +1,8 @@
*** Changelog ***
= 3.0.9 - 2025-07-31 =
* Fix - Payment via "Proceed to PayPal" may result in a redirect loop #3570
= 3.0.8 - 2025-07-28 =
* Enhancement - Migration from Legacy Settings to New Settings as opt-in via banner & button #3491
* Enhancement - Replace call to `billing-agreements/agreement-tokens` with checking the capabilities for Reference Transactions #3495

View file

@ -20,13 +20,14 @@
"container-interop/service-provider": "^0.4.0",
"dhii/containers": "^0.1.0-alpha1",
"inpsyde/modularity": "^1.7",
"woocommerce/woocommerce-sniffs": "^0.1.0",
"woocommerce/woocommerce-sniffs": "^1.0.0",
"phpunit/phpunit": "^7.0 | ^8.0 | ^9.0",
"brain/monkey": "^2.4",
"php-stubs/wordpress-stubs": "^5.0@stable",
"php-stubs/woocommerce-stubs": "^8.0@stable",
"vimeo/psalm": "^4.0",
"vlucas/phpdotenv": "^5"
"vlucas/phpdotenv": "^5",
"coenjacobs/mozart": "^0.7.1"
},
"autoload": {
"psr-4": {

585
composer.lock generated
View file

@ -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": "2fa610ed883c0868838d3008b7127cbf",
"content-hash": "fd277e5a82374078694b99b0e6aef07c",
"packages": [
{
"name": "container-interop/service-provider",
@ -904,6 +904,64 @@
},
"time": "2024-08-29T20:15:04+00:00"
},
{
"name": "coenjacobs/mozart",
"version": "0.7.1",
"source": {
"type": "git",
"url": "https://github.com/coenjacobs/mozart.git",
"reference": "dbcdeb992d20d9c8914eef090f9a0d684bb1102c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/coenjacobs/mozart/zipball/dbcdeb992d20d9c8914eef090f9a0d684bb1102c",
"reference": "dbcdeb992d20d9c8914eef090f9a0d684bb1102c",
"shasum": ""
},
"require": {
"league/flysystem": "^1.0",
"php": "^7.3|^8.0",
"symfony/console": "^4|^5",
"symfony/finder": "^4|^5"
},
"require-dev": {
"mheap/phpunit-github-actions-printer": "^1.4",
"phpunit/phpunit": "^8.5",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^4.4"
},
"bin": [
"bin/mozart"
],
"type": "library",
"autoload": {
"psr-4": {
"CoenJacobs\\Mozart\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Coen Jacobs",
"email": "coenjacobs@gmail.com"
}
],
"description": "Composes all dependencies as a package inside a WordPress plugin",
"support": {
"issues": "https://github.com/coenjacobs/mozart/issues",
"source": "https://github.com/coenjacobs/mozart/tree/0.7.1"
},
"funding": [
{
"url": "https://github.com/coenjacobs",
"type": "github"
}
],
"time": "2021-02-02T21:37:03+00:00"
},
{
"name": "composer/package-versions-deprecated",
"version": "1.11.99.5",
@ -1205,35 +1263,38 @@
},
{
"name": "dealerdirect/phpcodesniffer-composer-installer",
"version": "v0.7.2",
"version": "v1.1.2",
"source": {
"type": "git",
"url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
"reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db"
"url": "https://github.com/PHPCSStandards/composer-installer.git",
"reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db",
"reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db",
"url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1",
"reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0 || ^2.0",
"php": ">=5.3",
"composer-plugin-api": "^2.2",
"php": ">=5.4",
"squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0"
},
"require-dev": {
"composer/composer": "*",
"php-parallel-lint/php-parallel-lint": "^1.3.1",
"phpcompatibility/php-compatibility": "^9.0"
"composer/composer": "^2.2",
"ext-json": "*",
"ext-zip": "*",
"php-parallel-lint/php-parallel-lint": "^1.4.0",
"phpcompatibility/php-compatibility": "^9.0",
"yoast/phpunit-polyfills": "^1.0"
},
"type": "composer-plugin",
"extra": {
"class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
"class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
},
"autoload": {
"psr-4": {
"Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
"PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -1243,17 +1304,16 @@
"authors": [
{
"name": "Franck Nijhof",
"email": "franck.nijhof@dealerdirect.com",
"homepage": "http://www.frenck.nl",
"role": "Developer / IT Manager"
"email": "opensource@frenck.dev",
"homepage": "https://frenck.dev",
"role": "Open source developer"
},
{
"name": "Contributors",
"homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors"
"homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors"
}
],
"description": "PHP_CodeSniffer Standards Composer Installer Plugin",
"homepage": "http://www.dealerdirect.com",
"keywords": [
"PHPCodeSniffer",
"PHP_CodeSniffer",
@ -1273,10 +1333,29 @@
"tests"
],
"support": {
"issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues",
"source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer"
"issues": "https://github.com/PHPCSStandards/composer-installer/issues",
"security": "https://github.com/PHPCSStandards/composer-installer/security/policy",
"source": "https://github.com/PHPCSStandards/composer-installer"
},
"time": "2022-02-04T12:51:07+00:00"
"funding": [
{
"url": "https://github.com/PHPCSStandards",
"type": "github"
},
{
"url": "https://github.com/jrfnl",
"type": "github"
},
{
"url": "https://opencollective.com/php_codesniffer",
"type": "open_collective"
},
{
"url": "https://thanks.dev/u/gh/phpcsstandards",
"type": "thanks_dev"
}
],
"time": "2025-07-17T20:45:56+00:00"
},
{
"name": "dhii/collections-interface",
@ -1888,6 +1967,156 @@
},
"time": "2025-05-09T12:13:17+00:00"
},
{
"name": "league/flysystem",
"version": "1.1.10",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
"reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3239285c825c152bcc315fe0e87d6b55f5972ed1",
"reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1",
"shasum": ""
},
"require": {
"ext-fileinfo": "*",
"league/mime-type-detection": "^1.3",
"php": "^7.2.5 || ^8.0"
},
"conflict": {
"league/flysystem-sftp": "<1.0.6"
},
"require-dev": {
"phpspec/prophecy": "^1.11.1",
"phpunit/phpunit": "^8.5.8"
},
"suggest": {
"ext-ftp": "Allows you to use FTP server storage",
"ext-openssl": "Allows you to use FTPS server storage",
"league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
"league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
"league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
"league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
"league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
"league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
"league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
"league/flysystem-webdav": "Allows you to use WebDAV storage",
"league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
"spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
"srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"autoload": {
"psr-4": {
"League\\Flysystem\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frenky.net"
}
],
"description": "Filesystem abstraction: Many filesystems, one API.",
"keywords": [
"Cloud Files",
"WebDAV",
"abstraction",
"aws",
"cloud",
"copy.com",
"dropbox",
"file systems",
"files",
"filesystem",
"filesystems",
"ftp",
"rackspace",
"remote",
"s3",
"sftp",
"storage"
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
"source": "https://github.com/thephpleague/flysystem/tree/1.1.10"
},
"funding": [
{
"url": "https://offset.earth/frankdejonge",
"type": "other"
}
],
"time": "2022-10-04T09:16:37+00:00"
},
{
"name": "league/mime-type-detection",
"version": "1.16.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/mime-type-detection.git",
"reference": "2d6702ff215bf922936ccc1ad31007edc76451b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9",
"reference": "2d6702ff215bf922936ccc1ad31007edc76451b9",
"shasum": ""
},
"require": {
"ext-fileinfo": "*",
"php": "^7.4 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.2",
"phpstan/phpstan": "^0.12.68",
"phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0"
},
"type": "library",
"autoload": {
"psr-4": {
"League\\MimeTypeDetection\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
],
"description": "Mime-type detection for Flysystem",
"support": {
"issues": "https://github.com/thephpleague/mime-type-detection/issues",
"source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0"
},
"funding": [
{
"url": "https://github.com/frankdejonge",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/league/flysystem",
"type": "tidelift"
}
],
"time": "2024-09-21T08:32:55+00:00"
},
{
"name": "mockery/mockery",
"version": "1.6.12",
@ -1973,16 +2202,16 @@
},
{
"name": "myclabs/deep-copy",
"version": "1.13.1",
"version": "1.13.3",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36",
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36",
"shasum": ""
},
"require": {
@ -2021,7 +2250,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.3"
},
"funding": [
{
@ -2029,7 +2258,7 @@
"type": "tidelift"
}
],
"time": "2025-04-29T12:36:36+00:00"
"time": "2025-07-05T12:25:42+00:00"
},
{
"name": "netresearch/jsonmapper",
@ -2609,6 +2838,181 @@
],
"time": "2025-05-12T16:38:37+00:00"
},
{
"name": "phpcsstandards/phpcsextra",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHPCSExtra.git",
"reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/fa4b8d051e278072928e32d817456a7fdb57b6ca",
"reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca",
"shasum": ""
},
"require": {
"php": ">=5.4",
"phpcsstandards/phpcsutils": "^1.1.0",
"squizlabs/php_codesniffer": "^3.13.0 || ^4.0"
},
"require-dev": {
"php-parallel-lint/php-console-highlighter": "^1.0",
"php-parallel-lint/php-parallel-lint": "^1.4.0",
"phpcsstandards/phpcsdevcs": "^1.1.6",
"phpcsstandards/phpcsdevtools": "^1.2.1",
"phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
},
"type": "phpcodesniffer-standard",
"extra": {
"branch-alias": {
"dev-stable": "1.x-dev",
"dev-develop": "1.x-dev"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "Juliette Reinders Folmer",
"homepage": "https://github.com/jrfnl",
"role": "lead"
},
{
"name": "Contributors",
"homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors"
}
],
"description": "A collection of sniffs and standards for use with PHP_CodeSniffer.",
"keywords": [
"PHP_CodeSniffer",
"phpcbf",
"phpcodesniffer-standard",
"phpcs",
"standards",
"static analysis"
],
"support": {
"issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues",
"security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy",
"source": "https://github.com/PHPCSStandards/PHPCSExtra"
},
"funding": [
{
"url": "https://github.com/PHPCSStandards",
"type": "github"
},
{
"url": "https://github.com/jrfnl",
"type": "github"
},
{
"url": "https://opencollective.com/php_codesniffer",
"type": "open_collective"
},
{
"url": "https://thanks.dev/u/gh/phpcsstandards",
"type": "thanks_dev"
}
],
"time": "2025-06-14T07:40:39+00:00"
},
{
"name": "phpcsstandards/phpcsutils",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHPCSUtils.git",
"reference": "f7eb16f2fa4237d5db9e8fed8050239bee17a9bd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/f7eb16f2fa4237d5db9e8fed8050239bee17a9bd",
"reference": "f7eb16f2fa4237d5db9e8fed8050239bee17a9bd",
"shasum": ""
},
"require": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0",
"php": ">=5.4",
"squizlabs/php_codesniffer": "^3.13.0 || ^4.0"
},
"require-dev": {
"ext-filter": "*",
"php-parallel-lint/php-console-highlighter": "^1.0",
"php-parallel-lint/php-parallel-lint": "^1.4.0",
"phpcsstandards/phpcsdevcs": "^1.1.6",
"yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0"
},
"type": "phpcodesniffer-standard",
"extra": {
"branch-alias": {
"dev-stable": "1.x-dev",
"dev-develop": "1.x-dev"
}
},
"autoload": {
"classmap": [
"PHPCSUtils/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "Juliette Reinders Folmer",
"homepage": "https://github.com/jrfnl",
"role": "lead"
},
{
"name": "Contributors",
"homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors"
}
],
"description": "A suite of utility functions for use with PHP_CodeSniffer",
"homepage": "https://phpcsutils.com/",
"keywords": [
"PHP_CodeSniffer",
"phpcbf",
"phpcodesniffer-standard",
"phpcs",
"phpcs3",
"phpcs4",
"standards",
"static analysis",
"tokens",
"utility"
],
"support": {
"docs": "https://phpcsutils.com/",
"issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues",
"security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy",
"source": "https://github.com/PHPCSStandards/PHPCSUtils"
},
"funding": [
{
"url": "https://github.com/PHPCSStandards",
"type": "github"
},
{
"url": "https://github.com/jrfnl",
"type": "github"
},
{
"url": "https://opencollective.com/php_codesniffer",
"type": "open_collective"
},
{
"url": "https://thanks.dev/u/gh/phpcsstandards",
"type": "thanks_dev"
}
],
"time": "2025-08-10T01:04:45+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
@ -4549,6 +4953,69 @@
],
"time": "2024-09-25T14:11:13+00:00"
},
{
"name": "symfony/finder",
"version": "v5.4.45",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "63741784cd7b9967975eec610b256eed3ede022b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b",
"reference": "63741784cd7b9967975eec610b256eed3ede022b",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/polyfill-php80": "^1.16"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Finder\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v5.4.45"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-28T13:32:08+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.32.0",
@ -5466,74 +5933,77 @@
},
{
"name": "woocommerce/woocommerce-sniffs",
"version": "0.1.3",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-sniffs.git",
"reference": "4576d54595614d689bc4436acff8baaece3c5bb0"
"reference": "3a65b917ff5ab5e65609e5dcb7bc62f9455bbef8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-sniffs/zipball/4576d54595614d689bc4436acff8baaece3c5bb0",
"reference": "4576d54595614d689bc4436acff8baaece3c5bb0",
"url": "https://api.github.com/repos/woocommerce/woocommerce-sniffs/zipball/3a65b917ff5ab5e65609e5dcb7bc62f9455bbef8",
"reference": "3a65b917ff5ab5e65609e5dcb7bc62f9455bbef8",
"shasum": ""
},
"require": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"dealerdirect/phpcodesniffer-composer-installer": "^1.0.0",
"php": ">=7.0",
"phpcompatibility/phpcompatibility-wp": "^2.1.0",
"wp-coding-standards/wpcs": "^2.3.0"
"wp-coding-standards/wpcs": "^3.0.0"
},
"type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Claudio Sanches",
"email": "claudio@automattic.com"
}
],
"description": "WooCommerce sniffs",
"keywords": [
"phpcs",
"standards",
"static analysis",
"woocommerce",
"wordpress"
],
"support": {
"issues": "https://github.com/woocommerce/woocommerce-sniffs/issues",
"source": "https://github.com/woocommerce/woocommerce-sniffs/tree/0.1.3"
"source": "https://github.com/woocommerce/woocommerce-sniffs/tree/1.0.0"
},
"time": "2022-02-17T15:34:51+00:00"
"time": "2023-09-29T13:52:33+00:00"
},
{
"name": "wp-coding-standards/wpcs",
"version": "2.3.0",
"version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
"reference": "7da1894633f168fe244afc6de00d141f27517b62"
"reference": "d2421de7cec3274ae622c22c744de9a62c7925af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7da1894633f168fe244afc6de00d141f27517b62",
"reference": "7da1894633f168fe244afc6de00d141f27517b62",
"url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/d2421de7cec3274ae622c22c744de9a62c7925af",
"reference": "d2421de7cec3274ae622c22c744de9a62c7925af",
"shasum": ""
},
"require": {
"ext-filter": "*",
"ext-libxml": "*",
"ext-tokenizer": "*",
"ext-xmlreader": "*",
"php": ">=5.4",
"squizlabs/php_codesniffer": "^3.3.1"
"phpcsstandards/phpcsextra": "^1.4.0",
"phpcsstandards/phpcsutils": "^1.1.0",
"squizlabs/php_codesniffer": "^3.13.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6",
"php-parallel-lint/php-console-highlighter": "^1.0.0",
"php-parallel-lint/php-parallel-lint": "^1.4.0",
"phpcompatibility/php-compatibility": "^9.0",
"phpcsstandards/phpcsdevtools": "^1.0",
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
"phpcsstandards/phpcsdevtools": "^1.2.0",
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
},
"suggest": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.6 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
"ext-iconv": "For improved results",
"ext-mbstring": "For improved results"
},
"type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
@ -5550,6 +6020,7 @@
"keywords": [
"phpcs",
"standards",
"static analysis",
"wordpress"
],
"support": {
@ -5557,14 +6028,20 @@
"source": "https://github.com/WordPress/WordPress-Coding-Standards",
"wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki"
},
"time": "2020-05-13T23:57:56+00:00"
"funding": [
{
"url": "https://opencollective.com/php_codesniffer",
"type": "custom"
}
],
"time": "2025-07-24T20:08:31+00:00"
}
],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {
"php-stubs/wordpress-stubs": 0,
"php-stubs/woocommerce-stubs": 0
"php-stubs/woocommerce-stubs": 0,
"php-stubs/wordpress-stubs": 0
},
"prefer-stable": true,
"prefer-lowest": false,
@ -5572,7 +6049,7 @@
"php": "^7.4 | ^8.0",
"ext-json": "*"
},
"platform-dev": [],
"platform-dev": {},
"platform-overrides": {
"php": "7.4"
},

View file

@ -64,6 +64,8 @@ class AliasingContainer implements ContainerInterface
*/
public function has($key)
{
$key = (string) $key;
return $this->inner->has($this->getInnerKey($key));
}

View file

@ -64,7 +64,7 @@ class CachingContainer implements ContainerInterface
throw new NotFoundException($this->__('Key "%1$s" not found in inner container', [$key]), 0, $e);
} catch (Exception $e) {
throw new ContainerException(
$this->__('Could not retrieve value for key "%1$s from inner container', [$key]),
$this->__('Could not retrieve value for key "%1$s" from inner container', [$key]),
0,
$e
);

View file

@ -38,7 +38,8 @@ class CompositeCachingServiceProvider implements ServiceProviderInterface
}
/**
* {@inheritDoc}
* @inheritDoc
*
* @psalm-suppress InvalidNullableReturnType
* It isn't actually going to return null ever, because $factories will be filled during indexing.
*/
@ -56,7 +57,8 @@ class CompositeCachingServiceProvider implements ServiceProviderInterface
}
/**
* {@inheritDoc}
* @inheritDoc
*
* @psalm-suppress InvalidNullableReturnType
* It isn't actually going to return null ever, because $factories will be filled during indexing.
*/
@ -78,7 +80,7 @@ class CompositeCachingServiceProvider implements ServiceProviderInterface
*
* Caches them internally.
*
* @param iterable|ServiceProviderInterface[] $providers The providers to index.
* @param iterable<ServiceProviderInterface> $providers The providers to index.
*/
protected function indexProviderDefinitions(iterable $providers): void
{

View file

@ -89,6 +89,7 @@ class DelegatingContainer implements ContainerInterface
public function has($id)
{
$services = $this->provider->getFactories();
$id = (string) $id;
return array_key_exists($id, $services);
}

View file

@ -84,7 +84,10 @@ class DeprefixingContainer implements ContainerInterface
*/
public function has($key)
{
return $this->inner->has($this->getInnerKey($key)) || (!$this->strict && $this->inner->has($key));
$key = (string) $key;
$realKey = $this->getInnerKey($key);
return $this->inner->has($realKey) || (!$this->strict && $this->inner->has($key));
}
/**

View file

@ -5,13 +5,13 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Dhii\Container;
use ArrayIterator;
use Traversable;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Collection\WritableContainerInterface;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Collection\WritableMapInterface;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Util\StringTranslatingTrait;
use IteratorAggregate;
use RangeException;
use Traversable;
/**
* A simple mutable dictionary, i.e. an enumerable key-value map.
@ -54,6 +54,8 @@ class Dictionary implements
*/
public function has($key)
{
$key = (string) $key;
$isHas = array_key_exists($key, $this->data);
return $isHas;

View file

@ -61,6 +61,8 @@ class FlashContainer implements
*/
public function has($key)
{
$key = (string) $key;
return array_key_exists($key, $this->flashData);
}

View file

@ -88,6 +88,8 @@ class HierarchyContainer implements ContainerInterface
*/
public function has($key)
{
$key = (string) $key;
return array_key_exists($key, $this->data);
}
}

View file

@ -85,6 +85,8 @@ class MappingContainer implements ContainerInterface
*/
public function has($key)
{
$key = (string) $key;
return $this->inner->has($key);
}
}

View file

@ -80,6 +80,8 @@ class MaskingContainer implements ContainerInterface
*/
public function has($key)
{
$key = (string) $key;
return $this->isExposed($key) && $this->inner->has($key);
}

View file

@ -12,6 +12,7 @@ use WooCommerce\PayPalCommerce\Vendor\Dhii\Collection\WritableMapInterface;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Exception\ContainerException;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Exception\NotFoundException;
use IteratorAggregate;
use Traversable;
/**
* A container that does nothing.
@ -92,7 +93,7 @@ class NoOpContainer implements
/**
* @inheritDoc
*/
public function getIterator()
public function getIterator(): Traversable
{
return new ArrayIterator([]);
}

View file

@ -127,6 +127,8 @@ class PathContainer implements ContainerInterface
*/
public function has($key)
{
$key = (string) $key;
/**
* @psalm-suppress InvalidCatch
* The base interface does not extend Throwable, but in fact everything that is possible

View file

@ -5,9 +5,12 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Dhii\Container;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Collection\ContainerInterface;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Exception\ContainerException;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Exception\NotFoundException;
use Exception;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface as PsrContainerInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\NotFoundExceptionInterface;
use RuntimeException;
/**
* A container implementation that wraps around an inner container and prefixes its keys, requiring consumers to
@ -89,11 +92,18 @@ class PrefixingContainer implements ContainerInterface
*/
public function has($key)
{
$key = (string) $key;
if (!$this->isPrefixed($key) && $this->strict) {
return false;
}
return $this->inner->has($this->unprefix($key)) || (!$this->strict && $this->inner->has($key));
try {
$realKey = $this->unprefix($key);
} catch (Exception $e) {
throw new ContainerException(sprintf('Could not unprefix key "%1$s"', $key), 0, $e);
}
return $this->inner->has($realKey) || (!$this->strict && $this->inner->has($key));
}
/**
@ -108,7 +118,7 @@ class PrefixingContainer implements ContainerInterface
protected function unprefix(string $key): string
{
return $this->isPrefixed($key)
? substr($key, strlen($this->prefix))
? $this->substring($key, strlen($this->prefix))
: $key;
}
@ -125,4 +135,35 @@ class PrefixingContainer implements ContainerInterface
{
return strlen($this->prefix) > 0 && strpos($key, $this->prefix) === 0;
}
/**
* Extracts a substring from the specified string.
*
* @see substr()
*
* @param string $string The string to extract from.
* @param int $offset The char position, at which to start extraction.
* @param int|null $length The char position, at which to end extraction; unlimited if `null`.
*
* @return string The extracted substring.
*
* @throws RuntimeException If unable to extract.
*/
protected function substring(string $string, int $offset = 0, ?int $length = null): string
{
$length = $length ?? strlen($string) - $offset;
$substring = substr($string, $offset, $length);
if ($substring === false) {
throw new RuntimeException(
sprintf(
'Could not extract substring starting at %1$d of length %2$s from string "%3$s"',
$offset,
$length ?: 'null',
$string
)
);
}
return $substring;
}
}

View file

@ -114,6 +114,8 @@ class SegmentingContainer implements ContainerInterface
*/
public function has($key)
{
$key = (string) $key;
return $this->inner->has($key);
}
}

View file

@ -5,22 +5,22 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Dhii\Container;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
/**
* A value object capable of providing services.
*
* @package Dhii\Di
* @psalm-type Factory = callable(ContainerInterface): mixed
* @psalm-type Extension = callable(ContainerInterface, mixed): mixed
*/
class ServiceProvider implements ServiceProviderInterface
{
/** @var callable[] */
protected array $factories;
/**
* @var callable[]
*/
protected $factories;
/**
* @var callable[]
*/
protected $extensions;
protected array $extensions;
/**
* @param callable[] $factories A map of service name to service factory.

View file

@ -55,6 +55,7 @@ class SimpleCacheContainer implements
*/
public function has($id)
{
$id = (string) $id;
$storage = $this->storage;
try {
@ -77,7 +78,11 @@ class SimpleCacheContainer implements
try {
$storage->set($key, $value, $ttl);
} catch (Exception $e) {
throw new ContainerException(sprintf('Could not set key "%1$s" with value "%2$s"', $key, $value), 0, $e);
throw new ContainerException(
sprintf('Could not set key "%1$s" with value "%2$s"', $key, (string) $value),
0,
$e
);
}
}
@ -105,7 +110,7 @@ class SimpleCacheContainer implements
try {
$storage->clear();
} catch (Exception $e) {
throw new ContainerException(sprintf('Could not clear container'), 0, $e);
throw new ContainerException('Could not clear container', 0, $e);
}
}
}

View file

@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Dhii\Container;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use ReflectionException;
use ReflectionFunction;
use ReflectionObject;
/**
* A service provider that detects tags in factory docBlocks, and exposes them as services.
*
* A service may have a docBlock. The docBlock may contain various docBlock tags, such as `@param` or `@return`.
* This class will detect `@tag {tagname}` tags in service docBlocks. `tagname` may be anything that a service
* key may be - they exist in the same namespace. In fact, a `tagname` corresponds to a service
* that returns a list of tagged services. To retrieve them, just resolve the tagname as a service.
*
* For each unique `tagname` in factory docBlocks, this service provider will create an extension with
* an identical name. This extension at resolution time will resolve each tagged service by key,
* and add resulting services to the list it is extending. To ensure there's always a list to extend,
* this service provider will also add a service with an identical name, which resolves to an empty list.
* All such "tag" services are empty list in the beginning of their resolution, so it doesn't matter
* if it gets overwritten by another module's identical empty list.
*
* @psalm-import-type Factory from ServiceProvider
* @psalm-import-type Extension from ServiceProvider
*/
class TaggingServiceProvider implements ServiceProviderInterface
{
/** @var array<Factory> */
protected array $factories;
/** @var array<Extension> */
protected array $extensions;
public function __construct(ServiceProviderInterface $inner)
{
$this->factories = $inner->getFactories();
$this->extensions = $inner->getExtensions();
$this->indexTags();
}
/**
* @inheritDoc
*/
public function getFactories()
{
return $this->factories;
}
/**
* @inheritDoc
*/
public function getExtensions()
{
return $this->extensions;
}
/**
* Indexes tagged factories, and creates factories and extensions for tags.
*
* @throws ReflectionException If problem obtaining factory reflection.
*/
protected function indexTags(): void
{
$tags = [];
foreach ($this->factories as $serviceName => $factory) {
if (is_string($factory)) {
continue;
}
$reflection = is_object($factory) && get_class($factory) === 'Closure'
? new ReflectionFunction($factory)
: new ReflectionObject($factory);
$docBlock = $reflection->getDocComment();
// No docblock
if ($docBlock === false) {
continue;
}
$factoryTags = $this->getTagsFromDocBlock($docBlock);
foreach ($factoryTags as $tag) {
if (!isset($tags[$tag]) || !is_array($tags[$tag])) {
$tags[$tag] = [];
}
$tags[$tag][] = $serviceName;
}
}
foreach ($tags as $tag => $taggedServiceNames) {
$this->factories[$tag] = fn (): array => [];
$this->extensions[$tag] = function (ContainerInterface $c, array $prev) use ($taggedServiceNames): array {
return array_merge(
$prev,
array_map(fn (string $serviceName) => $c->get($serviceName), $taggedServiceNames)
);
};
}
}
/**
* Retrieves tags names that are part of a docBlock.
*
* @link https://www.php.net/manual/en/reflectionclass.getdoccomment.php#118606
*
* @param string $docBlock The docBlock.
*
* @return array<string> A list of tag names.
*/
protected function getTagsFromDocBlock(string $docBlock): array
{
$regex = '#^\s*/?\**\s*(@tag\s*(?P<tags>[^\s]+))#m';
preg_match_all($regex, $docBlock, $matches);
return $matches['tags'];
}
}

View file

@ -27,6 +27,7 @@ trait StringTranslatingTrait
*/
protected function __(string $string, array $args = array(), $context = null): string
{
$context = (string) $context;
$string = $this->_translate($string, $context);
array_unshift($args, $string);
return call_user_func_array('sprintf', $args);

View file

@ -1,52 +1,38 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerExceptionInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
/**
* @phpstan-import-type Service from \WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule
* @phpstan-import-type ExtendingService from \WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule
*/
class ContainerConfigurator
{
/**
* @var array<string, callable(ContainerInterface $container):mixed>
*/
private $services = [];
/** @var array<string, Service> */
private array $services = [];
/** @var array<string, bool> */
private array $factoryIds = [];
private ServiceExtensions $extensions;
private ?ContainerInterface $compiledContainer = null;
/** @var ContainerInterface[] */
private array $containers = [];
/**
* @var array<string, bool>
*/
private $factoryIds = [];
/**
* @var array<string, array<callable(mixed $service, ContainerInterface $container):mixed>>
*/
private $extensions = [];
/**
* @var ContainerInterface[]
*/
private $containers = [];
/**
* @var null|ContainerInterface
*/
private $compiledContainer;
/**
* ContainerConfigurator constructor.
*
* @param ContainerInterface[] $containers
*/
public function __construct(array $containers = [])
public function __construct(array $containers = [], ?ServiceExtensions $extensions = null)
{
array_map([$this, 'addContainer'], $containers);
$this->extensions = $extensions ?? new ServiceExtensions();
}
/**
* Allowing to add child containers.
*
* @param ContainerInterface $container
* @return void
*/
public function addContainer(ContainerInterface $container): void
{
@ -55,7 +41,7 @@ class ContainerConfigurator
/**
* @param string $id
* @param callable(ContainerInterface $container):mixed $factory
* @param Service $factory
*/
public function addFactory(string $id, callable $factory): void
{
@ -67,8 +53,7 @@ class ContainerConfigurator
/**
* @param string $id
* @param callable(ContainerInterface $container):mixed $service
*
* @param Service $service
* @return void
*/
public function addService(string $id, callable $service): void
@ -89,7 +74,6 @@ class ContainerConfigurator
/**
* @param string $id
*
* @return bool
*/
public function hasService(string $id): bool
@ -109,37 +93,31 @@ class ContainerConfigurator
/**
* @param string $id
* @param callable(mixed $service, ContainerInterface $container):mixed $extender
*
* @param ExtendingService $extender
* @return void
*/
public function addExtension(string $id, callable $extender): void
{
if (!isset($this->extensions[$id])) {
$this->extensions[$id] = [];
}
$this->extensions[$id][] = $extender;
$this->extensions->add($id, $extender);
}
/**
* @param string $id
*
* @return bool
*/
public function hasExtension(string $id): bool
{
return isset($this->extensions[$id]);
return $this->extensions->has($id);
}
/**
* Returns a read only version of this Container.
*
* @return ContainerInterface
*
* @phpstan-assert ContainerInterface $this->compiledContainer
*/
public function createReadOnlyContainer(): ContainerInterface
{
if (!$this->compiledContainer) {
if ($this->compiledContainer === null) {
$this->compiledContainer = new ReadOnlyContainer(
$this->services,
$this->factoryIds,

View file

@ -10,15 +10,8 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
class PackageProxyContainer implements ContainerInterface
{
/**
* @var Package
*/
private $package;
/**
* @var ContainerInterface|null
*/
private $container;
private Package $package;
private ?ContainerInterface $container = null;
/**
* @param Package $package
@ -31,8 +24,6 @@ class PackageProxyContainer implements ContainerInterface
/**
* @param string $id
* @return mixed
*
* @throws \Exception
*/
public function get(string $id)
{
@ -44,8 +35,6 @@ class PackageProxyContainer implements ContainerInterface
/**
* @param string $id
* @return bool
*
* @throws \Exception
*/
public function has(string $id): bool
{
@ -55,33 +44,30 @@ class PackageProxyContainer implements ContainerInterface
/**
* @return bool
*
* @throws \Exception
* @psalm-assert-if-true ContainerInterface $this->container
* @phpstan-assert-if-true ContainerInterface $this->container
* @phpstan-assert-if-false null $this->container
*/
private function tryContainer(): bool
{
if ($this->container) {
if ($this->container !== null) {
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->package->hasContainer()
|| $this->package->hasReachedStatus(Package::STATUS_INITIALIZED)
) {
$this->container = $this->package->container();
}
return (bool)$this->container;
return $this->container !== null;
}
/**
* @param string $id
* @return void
*
* @throws \Exception
*
* @psalm-assert ContainerInterface $this->container
* @phpstan-assert ContainerInterface $this->container
*/
private function assertPackageBooted(string $id): void
{
@ -90,13 +76,11 @@ class PackageProxyContainer implements ContainerInterface
}
$name = $this->package->name();
$status = $this->package->statusIs(Package::STATUS_FAILED)
? 'is errored'
: 'is not ready yet';
$status = $this->package->hasFailed() ? 'is errored' : 'is not ready yet';
throw new class ("Error retrieving service {$id} because package {$name} {$status}.")
extends \Exception
implements ContainerExceptionInterface {
$error = "Error retrieving service {$id} because package {$name} {$status}.";
throw new class (esc_html($error)) extends \Exception implements ContainerExceptionInterface
{
};
}
}

View file

@ -7,58 +7,43 @@ namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\NotFoundExceptionInterface;
/**
* @phpstan-import-type Service from \WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule
* @phpstan-import-type ExtendingService from \WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule
*/
class ReadOnlyContainer implements ContainerInterface
{
/**
* @var array<string, callable(\WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface $container):mixed>
*/
private $services;
/** @var array<string, Service> */
private array $services;
/** @var array<string, bool> */
private array $factoryIds;
private ServiceExtensions $extensions;
/** @var ContainerInterface[] */
private array $containers;
/** @var array<string, mixed> */
private array $resolvedServices = [];
/**
* @var array<string, bool>
*/
private $factoryIds;
/**
* @var array<string, array<callable(mixed, ContainerInterface $container):mixed>>
*/
private $extensions;
/**
* Resolved factories.
*
* @var array<string, mixed>
*/
private $resolvedServices = [];
/**
* @var ContainerInterface[]
*/
private $containers;
/**
* ReadOnlyContainer constructor.
*
* @param array<string, callable(ContainerInterface $container):mixed> $services
* @param array<string, Service> $services
* @param array<string, bool> $factoryIds
* @param array<string, array<callable(mixed, ContainerInterface $container):mixed>> $extensions
* @param ServiceExtensions|array<string, ExtendingService> $extensions
* @param ContainerInterface[] $containers
*/
public function __construct(
array $services,
array $factoryIds,
array $extensions,
$extensions,
array $containers
) {
$this->services = $services;
$this->factoryIds = $factoryIds;
$this->extensions = $extensions;
$this->extensions = $this->configureServiceExtensions($extensions);
$this->containers = $containers;
}
/**
* @param string $id
*
* @return mixed
*/
public function get(string $id)
@ -69,7 +54,7 @@ class ReadOnlyContainer implements ContainerInterface
if (array_key_exists($id, $this->services)) {
$service = $this->services[$id]($this);
$resolved = $this->resolveExtensions($id, $service);
$resolved = $this->extensions->resolve($service, $id, $this);
if (!isset($this->factoryIds[$id])) {
$this->resolvedServices[$id] = $resolved;
@ -83,19 +68,18 @@ class ReadOnlyContainer implements ContainerInterface
if ($container->has($id)) {
$service = $container->get($id);
return $this->resolveExtensions($id, $service);
return $this->extensions->resolve($service, $id, $this);
}
}
throw new class ("Service with ID {$id} not found.")
extends \Exception
implements NotFoundExceptionInterface {
$error = "Service with ID {$id} not found.";
throw new class (esc_html($error)) extends \Exception implements NotFoundExceptionInterface
{
};
}
/**
* @param string $id
*
* @return bool
*/
public function has(string $id): bool
@ -118,21 +102,43 @@ class ReadOnlyContainer implements ContainerInterface
}
/**
* @param string $id
* @param mixed $service
* Support extensions as array or ServiceExtensions instance for backward compatibility.
*
* @return mixed
* With PHP 8+ we could use an actual union type, but when we bump to PHP 8 as min supported
* version, we will probably bump major version as well, so we can just get rid of support
* for array.
*
* @param mixed $extensions
* @return ServiceExtensions
*/
private function resolveExtensions(string $id, $service)
private function configureServiceExtensions($extensions): ServiceExtensions
{
if (!isset($this->extensions[$id])) {
return $service;
if ($extensions instanceof ServiceExtensions) {
return $extensions;
}
foreach ($this->extensions[$id] as $extender) {
$service = $extender($service, $this);
if (!is_array($extensions)) {
$type = is_object($extensions) ? get_class($extensions) : gettype($extensions);
throw new \TypeError(
sprintf(
'%s::%s(): Argument #3 ($extensions) must be of type %s|array, %s given',
__CLASS__,
'__construct',
ServiceExtensions::class,
esc_html($type)
)
);
}
return $service;
$servicesExtensions = new ServiceExtensions();
foreach ($extensions as $id => $callback) {
/**
* @var string $id
* @var ExtendingService $callback
*/
$servicesExtensions->add($id, $callback);
}
return $servicesExtensions;
}
}

View file

@ -0,0 +1,194 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface as Container;
/**
* @phpstan-import-type ExtendingService from \WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule
*/
class ServiceExtensions
{
private const SERVICE_TYPE_NOT_CHANGED = 1;
private const SERVICE_TYPE_CHANGED = 2;
private const SERVICE_TYPE_NOT_OBJECT = 0;
/** @var array<string, list<ExtendingService>> */
protected array $extensions = [];
/**
* @param string $type
*
* @return string
*/
final public static function typeId(string $type): string
{
return "@instanceof<{$type}>";
}
/**
* @param string $extensionId
* @param ExtendingService $extender
*
* @return static
*/
public function add(string $extensionId, callable $extender): ServiceExtensions
{
if (!isset($this->extensions[$extensionId])) {
$this->extensions[$extensionId] = [];
}
$this->extensions[$extensionId][] = $extender;
return $this;
}
/**
* @param string $extensionId
*
* @return bool
*/
public function has(string $extensionId): bool
{
return isset($this->extensions[$extensionId]);
}
/**
* @param mixed $service
* @param string $id
* @param Container $container
*
* @return mixed
*/
final public function resolve($service, string $id, Container $container)
{
$service = $this->resolveById($id, $service, $container);
return is_object($service)
? $this->resolveByType(get_class($service), $service, $container)
: $service;
}
/**
* @param string $id
* @param mixed $service
* @param Container $container
*
* @return mixed
*/
protected function resolveById(string $id, $service, Container $container)
{
foreach ($this->extensions[$id] ?? [] as $extender) {
$service = $extender($service, $container);
}
return $service;
}
/**
* @param string $className
* @param object $service
* @param Container $container
* @param string[] $extendedClasses
*
* @return mixed
*
* phpcs:disable SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh
* phpcs:disable Syde.Functions.ReturnTypeDeclaration.NoReturnType
*/
protected function resolveByType(
string $className,
object $service,
Container $container,
array $extendedClasses = []
) {
// phpcs:enable SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh
// phpcs:enable Syde.Functions.ReturnTypeDeclaration.NoReturnType
$extendedClasses[] = $className;
/** @var array<class-string, list<ExtendingService>> $allCallbacks */
$allCallbacks = [];
// 1st group of extensions: targeting exact class
$byClass = $this->extensions[self::typeId($className)] ?? null;
if (($byClass !== null) && ($byClass !== [])) {
$allCallbacks[$className] = $byClass;
}
// 2nd group of extensions: targeting parent classes
$parents = class_parents($service, false) ?: [];
foreach ($parents as $parentName) {
$byParent = $this->extensions[self::typeId($parentName)] ?? null;
if (($byParent !== null) && ($byParent !== [])) {
$allCallbacks[$parentName] = $byParent;
}
}
// 3rd group of extensions: targeting implemented interfaces
$interfaces = class_implements($service, false) ?: [];
foreach ($interfaces as $interfaceName) {
$byInterface = $this->extensions[self::typeId($interfaceName)] ?? null;
if (($byInterface !== null) && ($byInterface !== [])) {
$allCallbacks[$interfaceName] = $byInterface;
}
}
$resultType = self::SERVICE_TYPE_NOT_CHANGED;
/** @var class-string $type */
foreach ($allCallbacks as $type => $extenders) {
// When the previous group of callbacks resulted in a type change, we need to check
// type before processing next group.
if (($resultType === self::SERVICE_TYPE_CHANGED) && !is_a($service, $type)) {
continue;
}
/** @var object $service */
[$service, $resultType] = $this->extendByType($type, $service, $container, $extenders);
if ($resultType === self::SERVICE_TYPE_NOT_OBJECT) {
// Service is not an object anymore, let's return it.
return $service;
}
}
// If type changed since beginning, let's start over.
// We check if class was already extended to avoid infinite recursion. E.g. instead of:
// `-> extend(A): B -> extend(B): A -> *loop* ->`
// we have:
// `-> extend(A): B -> extend(B): A -> return A`.
$newClassName = get_class($service);
if (!in_array($newClassName, $extendedClasses, true)) {
return $this->resolveByType($newClassName, $service, $container, $extendedClasses);
}
return $service;
}
/**
* @param class-string $type
* @param object $service
* @param Container $container
* @param list<ExtendingService> $extenders
*
* @return list{mixed, int}
*/
private function extendByType(
string $type,
object $service,
Container $container,
array $extenders
): array {
foreach ($extenders as $extender) {
$service = $extender($service, $container);
if (!is_object($service)) {
return [$service, self::SERVICE_TYPE_NOT_OBJECT];
}
if (!is_a($service, $type)) {
return [$service, self::SERVICE_TYPE_CHANGED];
}
}
return [$service, self::SERVICE_TYPE_NOT_CHANGED];
}
}

View file

@ -8,7 +8,6 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
interface ExecutableModule extends Module
{
/**
* Perform actions with objects retrieved from the container. Usually, adding WordPress hooks.
* Return true to signal a success, false to signal a failure.

View file

@ -4,9 +4,13 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
/**
* @phpstan-type ExtendingService callable(mixed $service, ContainerInterface $container): mixed
*/
interface ExtendingModule extends Module
{
/**
* Return application services' extensions.
*
@ -18,7 +22,7 @@ interface ExtendingModule extends Module
* That is done by using as ID (array key in the `extensions` method) the target module ID
* and the service ID.
*
* @return array<string, callable(mixed $service, \WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface $container):mixed>
* @return array<string, ExtendingService>
*/
public function extensions(): array;
}

View file

@ -4,6 +4,9 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module;
/**
* @phpstan-import-type Service from ServiceModule
*/
interface FactoryModule extends Module
{
/**
@ -12,7 +15,7 @@ interface FactoryModule extends Module
* Similar to `services`, but object created by given factories are not "cached", but a *new*
* instance is returned everytime `get()` is called in the container.
*
* @return array<string, callable(\WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface $container):mixed>
* @return array<string, Service>
*/
public function factories(): array;
}

View file

@ -9,12 +9,10 @@ namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module;
*/
interface Module
{
/**
* Unique identifier for your Module.
*
* @return string
*/
public function id(): string;
}

View file

@ -4,16 +4,11 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module;
/**
* Trait ModuleClassNameIdTrait
*
* @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module
*/
trait ModuleClassNameIdTrait
{
/**
* @return string
*
* @see Module::id()
*/
public function id(): string

View file

@ -4,9 +4,13 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
/**
* @phpstan-type Service callable(ContainerInterface $container): mixed
*/
interface ServiceModule extends Module
{
/**
* Return application services' factories.
*
@ -15,7 +19,7 @@ interface ServiceModule extends Module
* Services are "cached", so the given factory is called once the first time `get()` is called
* in the container, and on subsequent `get()` the same instance is returned again and again.
*
* @return array<string, callable(\WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface $container):mixed>
* @return array<string, Service>
*/
public function services(): array;
}

View file

@ -6,19 +6,22 @@ namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container\ContainerConfigurator;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container\PackageProxyContainer;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\FactoryModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\Module;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties\Properties;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
/**
* @phpstan-import-type Service from \WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule
* @phpstan-import-type ExtendingService from \WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule
*/
class Package
{
/**
* All the hooks fired in this class use this prefix.
* @var string
*/
private const HOOK_PREFIX = 'inpsyde.modularity.';
@ -34,14 +37,13 @@ class Package
* $container->has(Package::PROPERTIES);
* $container->get(Package::PROPERTIES);
* </code>
*
* @var string
*/
public const PROPERTIES = 'properties';
/**
* Custom action to be used to add Modules to the package.
* Custom action to be used to add modules and connect other packages.
* It might also be used to access package properties.
* Access container is not possible at this stage.
*
* @example
* <code>
@ -49,67 +51,64 @@ class Package
*
* add_action(
* $package->hookName(Package::ACTION_INIT),
* $callback
* fn (Package $package) => // do something,
* );
* </code>
*/
public const ACTION_INIT = 'init';
/**
* Custom action which is triggered after the application
* is booted to access container and properties.
* Very similar to `ACTION_INIT`, but it is static, so not dependent on package name.
* It passes package name as first argument.
*
* @example
* <code>
* $package = Package::new();
*
* add_action(
* $package->hookName(Package::ACTION_READY),
* $callback
* );
* </code>
* <code>
* add_action(
* Package::ACTION_MODULARITY_INIT,
* fn (string $packageName, Package $package) => // do something,
* 10,
* 2
* );
* </code>
*/
public const ACTION_READY = 'ready';
public const ACTION_MODULARITY_INIT = self::HOOK_PREFIX . self::ACTION_INIT;
/**
* Custom action which is triggered when a failure happens during the building stage.
*
* @example
* <code>
* $package = Package::new();
*
* add_action(
* $package->hookName(Package::ACTION_FAILED_BUILD),
* $callback
* );
* </code>
* Action fired when it is safe to access container.
* Add more modules is not anymore possible at this stage.
*/
public const ACTION_INITIALIZED = 'initialized';
/**
* Action fired when plugin finished its bootstrapping process, all its hooks are added.
* Add more modules is not anymore possible at this stage.
*/
public const ACTION_BOOTED = 'ready';
/**
* Action fired when anything went wrong during the "build" procedure.
*/
public const ACTION_FAILED_BUILD = 'failed-build';
/**
* Custom action which is triggered when a failure happens during the booting stage.
*
* @example
* <code>
* $package = Package::new();
*
* add_action(
* $package->hookName(Package::ACTION_FAILED_BOOT),
* $callback
* );
* </code>
* Action fired when anything went wrong during the "boot" procedure.
*/
public const ACTION_FAILED_BOOT = 'failed-boot';
/**
* Custom action which is triggered when a package is connected.
* Action fired when adding a module failed.
*/
public const ACTION_PACKAGE_CONNECTED = 'package-connected';
public const ACTION_FAILED_ADD_MODULE = 'failed-add-module';
/**
* Custom action which is triggered when a package cannot be connected.
* Action fired when a package connection failed.
*/
public const ACTION_FAILED_CONNECTION = 'failed-connection';
public const ACTION_FAILED_CONNECT = 'failed-connection';
/**
* Action fired when a package is connected successfully.
*/
public const ACTION_PACKAGE_CONNECTED = 'package-connected';
/**
* Module states can be used to get information about your module.
@ -118,7 +117,7 @@ class Package
* <code>
* $package = Package::new();
* $package->moduleIs(SomeModule::class, Package::MODULE_ADDED); // false
* $package->boot(new SomeModule());
* $package->addModule(new SomeModule());
* $package->moduleIs(SomeModule::class, Package::MODULE_ADDED); // true
* </code>
*/
@ -137,90 +136,81 @@ class Package
* @example
* <code>
* $package = Package::new();
* $package->statusIs(Package::IDLE); // true
* $package->statusIs(Package::STATUS_IDLE); // true
* $package->build();
* $package->statusIs(Package::STATUS_INITIALIZED); // true
* $package->boot();
* $package->statusIs(Package::BOOTED); // true
* $package->statusIs(Package::STATUS_DONE); // true
* </code>
*/
public const STATUS_IDLE = 2;
public const STATUS_INITIALIZING = 3;
public const STATUS_INITIALIZED = 4;
public const STATUS_MODULES_ADDED = 5;
public const STATUS_READY = 7;
public const STATUS_BOOTED = 8;
public const STATUS_BOOTING = 5;
public const STATUS_BOOTED = 7;
public const STATUS_DONE = 8;
public const STATUS_FAILED = -8;
/**
* Current state of the application.
*
* @see Package::STATUS_*
*
* @var int
*/
private $status = self::STATUS_IDLE;
// Deprecated flags
/** @deprecated */
public const STATUS_MODULES_ADDED = self::STATUS_BOOTING;
/** @deprecated */
public const ACTION_READY = self::ACTION_BOOTED;
/** @deprecated */
public const ACTION_FAILED_CONNECTION = self::ACTION_FAILED_CONNECT;
/**
* Contains the progress of all modules.
*
* @see Package::moduleProgress()
*
* @var array<string, list<string>>
*/
private $moduleStatus = [self::MODULES_ALL => []];
// Map of status to package-specific and global hook, both optional (i..e, null).
private const STATUSES_ACTIONS_MAP = [
self::STATUS_INITIALIZING => [self::ACTION_INIT, self::ACTION_MODULARITY_INIT],
self::STATUS_INITIALIZED => [self::ACTION_INITIALIZED, null],
self::STATUS_BOOTED => [self::ACTION_BOOTED, null],
];
/**
* Hashmap of where keys are names of connected packages, and values are boolean, true
* if connection was successful.
*
* @see Package::connect()
*
* @var array<string, bool>
*/
private $connectedPackages = [];
private const SUCCESS_STATUSES = [
self::STATUS_IDLE => self::STATUS_IDLE,
self::STATUS_INITIALIZING => self::STATUS_INITIALIZING,
self::STATUS_INITIALIZED => self::STATUS_INITIALIZED,
self::STATUS_BOOTING => self::STATUS_BOOTING,
self::STATUS_BOOTED => self::STATUS_BOOTED,
self::STATUS_DONE => self::STATUS_DONE,
];
/**
* @var list<ExecutableModule>
*/
private $executables = [];
private const OPERATORS = [
'<' => '<',
'<=' => '<=',
'>' => '>',
'>=' => '>=',
'==' => '==',
'!=' => '!=',
];
/**
* @var Properties
*/
private $properties;
/**
* @var ContainerConfigurator
*/
private $containerConfigurator;
/**
* @var bool
*/
private $built = false;
/**
* @var bool
*/
private $hasContainer = false;
/**
* @var \Throwable|null
*/
private $lastError = null;
/** @var Package::STATUS_* */
private int $status = self::STATUS_IDLE;
/** @var array<string, list<string>> */
private array $moduleStatus = [self::MODULES_ALL => []];
/** @var array<string, bool> */
private array $connectedPackages = [];
/** @var list<ExecutableModule> */
private array $executables = [];
private Properties $properties;
private ContainerConfigurator $containerConfigurator;
private bool $built = false;
private bool $hasContainer = false;
private ?\Throwable $lastError = null;
/**
* @param Properties $properties
* @param ContainerInterface[] $containers
*
* @param ContainerInterface ...$containers
* @return Package
*/
public static function new(Properties $properties, ContainerInterface ...$containers): Package
public static function new(Properties $properties, ContainerInterface ...$containers): Package
{
return new self($properties, ...$containers);
}
/**
* @param Properties $properties
* @param ContainerInterface[] $containers
* @param ContainerInterface ...$containers
*/
private function __construct(Properties $properties, ContainerInterface ...$containers)
{
@ -229,7 +219,7 @@ class Package
$this->containerConfigurator = new ContainerConfigurator($containers);
$this->containerConfigurator->addService(
self::PROPERTIES,
static function () use ($properties) {
static function () use ($properties): Properties {
return $properties;
}
);
@ -237,14 +227,14 @@ class Package
/**
* @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()));
$reason = sprintf('add module %s', $module->id());
$this->assertStatus(self::STATUS_FAILED, $reason, '!=');
$this->assertStatus(self::STATUS_INITIALIZING, $reason, '<=');
$registeredServices = $this->addModuleServices(
$module,
@ -271,7 +261,7 @@ class Package
$status = $added ? self::MODULE_ADDED : self::MODULE_NOT_ADDED;
$this->moduleProgress($module->id(), $status);
} catch (\Throwable $throwable) {
$this->handleFailure($throwable, self::ACTION_FAILED_BUILD);
$this->handleFailure($throwable, self::ACTION_FAILED_ADD_MODULE);
}
return $this;
@ -280,7 +270,6 @@ class Package
/**
* @param Package $package
* @return bool
* @throws \Exception
*/
public function connect(Package $package): bool
{
@ -290,33 +279,17 @@ class Package
}
$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);
return $this->handleConnectionFailure($packageName, 'already connected', false);
}
// 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);
$failed = $this->hasFailed();
if ($failed || $this->hasReachedStatus(self::STATUS_INITIALIZED)) {
$reason = $failed ? 'is errored' : 'has a built container already';
$this->handleConnectionFailure($packageName, "current package {$reason}", true);
}
$this->connectedPackages[$packageName] = true;
@ -330,9 +303,10 @@ class Package
}
);
// If the other package is booted, we can obtain a container, otherwise
// we build a proxy container
$container = $package->statusIs(self::STATUS_BOOTED)
// If we can obtain a container we do, otherwise we build a proxy container
$packageHasContainer = $package->hasReachedStatus(self::STATUS_INITIALIZED)
|| $package->hasContainer();
$container = $packageHasContainer
? $package->container()
: new PackageProxyContainer($package);
@ -347,7 +321,10 @@ class Package
return true;
} catch (\Throwable $throwable) {
if (isset($packageName)) {
if (
isset($packageName)
&& (($this->connectedPackages[$packageName] ?? false) !== true)
) {
$this->connectedPackages[$packageName] = false;
}
$this->handleFailure($throwable, self::ACTION_FAILED_BUILD);
@ -362,17 +339,26 @@ class Package
public function build(): Package
{
try {
// Don't allow building the application multiple times.
// Be tolerant about things like `$package->build()->build()`.
// Sometimes, from the extern, we might want to call `build()` to ensure the container
// is ready before accessing a service. And in that case we don't want to throw an
// exception if the container is already built.
if ($this->built && $this->statusIs(self::STATUS_INITIALIZED)) {
return $this;
}
// We expect `build` to be called only after `addModule()` or `connect()` which do
// not change the status, so we expect status to be still "IDLE".
// This will prevent invalid things like calling `build()` from inside something
// hooking ACTION_INIT OR ACTION_INITIALIZED.
$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 will change the status to "INITIALIZING" then fire the action that allow other
// packages to add modules or connect packages.
$this->progress(self::STATUS_INITIALIZING);
// This will change the status to "INITIALIZED" then fire an action when it is safe to
// access the container, because from this moment on, container is locked from change.
$this->progress(self::STATUS_INITIALIZED);
} catch (\Throwable $throwable) {
$this->handleFailure($throwable, self::ACTION_FAILED_BUILD);
@ -386,37 +372,47 @@ class Package
/**
* @param Module ...$defaultModules Deprecated, use `addModule()` to add default modules.
* @return bool
*
* @throws \Throwable
*/
public function boot(Module ...$defaultModules): bool
{
try {
// When package is done, nothing should happen to it calling boot again, but we call
// false to signal something is off.
if ($this->statusIs(self::STATUS_DONE)) {
return false;
}
// 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', '!=');
// Make sure we call boot() on a non-failed instance, and also make a sanity check
// on the status flow, e.g. prevent calling boot() from an action hook.
$this->assertStatus(self::STATUS_INITIALIZED, 'boot application');
$this->progress(self::STATUS_MODULES_ADDED);
// This will change status to STATUS_BOOTING "locking" subsequent call to `boot()`, but
// no hook is fired here, because at this point we can not do anything more or less than
// what can be done on the ACTION_INITIALIZED hook, so that hook is sufficient.
$this->progress(self::STATUS_BOOTING);
$this->doExecute();
$this->progress(self::STATUS_READY);
do_action(
$this->hookName(self::ACTION_READY),
$this
);
// This will change status to STATUS_BOOTED and then fire an action that make it
// possible to hook on a package that has finished its bootstrapping process, so all its
// "executable" modules have been executed.
$this->progress(self::STATUS_BOOTED);
} catch (\Throwable $throwable) {
$this->handleFailure($throwable, self::ACTION_FAILED_BOOT);
return false;
}
$this->progress(self::STATUS_BOOTED);
// This will change the status to DONE and will not fire any action.
// This is a status that proves that everything went well, not only the Package itself,
// but also anything hooking Package's hooks.
// The only way to move out of this status is a failure that might only happen directly
// calling `addModule()`, `connect()` or `build()`.
$this->progress(self::STATUS_DONE);
return true;
}
@ -439,39 +435,67 @@ class Package
);
}
// We expect `boot()` to be called either:
// 1. Directly after `addModule()`/`connect()`, without any `build()` call in between, so
// status is IDLE and `$this->built` is `false`.
// 2. After `build()` is called, so status is INITIALIZED and `$this->built` is `true`.
// Any other usage is not allowed (e.g. calling `boot()` from an hook callback) and in that
// case we return here, giving back control to `boot()` which will throw.
$validFlows = (!$this->built && $this->statusIs(self::STATUS_IDLE))
|| ($this->built && $this->statusIs(self::STATUS_INITIALIZED));
if (!$validFlows) {
// If none of the two supported flows happened, we just return handling control back
// to `boot()`, that will throw.
return;
}
if (!$this->built) {
array_map([$this, 'addModule'], $defaultModules);
// First valid flow: `boot()` was called directly after `addModule()`/`connect()`
// without any call to `build()`. We can call `build()` and return, handing control
// back to `boot()`. Before returning, if we had default modules passed to `boot()` we
// already have fired a deprecation, so here we just add them dealing with back-compat.
foreach ($defaultModules as $defaultModule) {
$this->addModule($defaultModule);
}
$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.
// Second valid flow: we have called `boot()` after `build()`. If we did it correctly,
// without default modules passed to `boot()`, we can just return handing control back
// to `boot()`.
if (!$defaultModules) {
return;
}
// If here, we have done something like: `$package->build()->boot($module1, $module2)`.
// Passing modules to `boot()` was deprecated when `build()` was introduced, so whoever
// added `build()` should have removed modules passed to `boot()`.
// But we want to keep 100% backward compatibility so we still support this behavior
// until the next major is released. To do that, we simulate IDLE status to prevent
// `addModule()` from throwing when adding default modules.
// But we can do that only if we don't have a compiled container yet.
// If anything hooking ACTION_INIT called `container()` we have a compiled container
// already, and we can't add modules, so we not going to simulate INIT status, which mean
// the `$this->addModule()` call below will throw.
$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;
if (!$this->hasContainer()) {
$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 a module was already added via `addModule()` we can skip it, reducing the
// chances of throwing an exception if not needed.
if (!$this->moduleIs($defaultModule->id(), self::MODULE_ADDED)) {
$this->addModule($defaultModule);
}
}
} finally {
$this->status = $backup;
if (!$this->hasFailed()) {
$this->status = $backup;
}
}
}
@ -482,7 +506,9 @@ class Package
*/
private function addModuleServices(Module $module, string $status): bool
{
/** @var null|array<string, Service|ExtendingService> $services */
$services = null;
/** @var null|callable(string, Service|ExtendingService): void $addCallback */
$addCallback = null;
switch ($status) {
case self::MODULE_REGISTERED:
@ -499,21 +525,16 @@ class Package
break;
}
if (!$services) {
if (($services === null) || ($services === []) || ($addCallback === null)) {
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<string> $ids */
$ids[] = $id;
}
);
/** @var list<string> $ids */
foreach ($services as $id => $service) {
$addCallback($id, $service);
$ids[] = $id;
}
$this->moduleProgress($module->id(), $status, $ids);
return true;
@ -521,8 +542,6 @@ class Package
/**
* @return void
*
* @throws \Throwable
*/
private function doExecute(): void
{
@ -530,9 +549,7 @@ class Package
$success = $executable->run($this->container());
$this->moduleProgress(
$executable->id(),
$success
? self::MODULE_EXECUTED
: self::MODULE_EXECUTION_FAILED
$success ? self::MODULE_EXECUTED : self::MODULE_EXECUTION_FAILED,
);
}
}
@ -541,15 +558,20 @@ class Package
* @param string $moduleId
* @param string $status
* @param list<string>|null $serviceIds
*
* @return void
* @return void
*/
private function moduleProgress(string $moduleId, string $status, ?array $serviceIds = null)
{
isset($this->moduleStatus[$status]) or $this->moduleStatus[$status] = [];
private function moduleProgress(
string $moduleId,
string $status,
?array $serviceIds = null
): void {
if (!isset($this->moduleStatus[$status])) {
$this->moduleStatus[$status] = [];
}
$this->moduleStatus[$status][] = $moduleId;
if (!$serviceIds || !$this->properties->isDebug()) {
if (($serviceIds === null) || ($serviceIds === []) || !$this->properties->isDebug()) {
$this->moduleStatus[self::MODULES_ALL][] = "{$moduleId} {$status}";
return;
@ -605,10 +627,9 @@ class Package
* `inpsyde.modularity.my-plugin` anyway, so the file name is not relevant.
*
* @param string $suffix
*
* @return string
* @see Package::name()
*
* @see Package::name()
*/
public function hookName(string $suffix = ''): string
{
@ -631,8 +652,6 @@ class Package
/**
* @return ContainerInterface
*
* @throws \Exception
*/
public function container(): ContainerInterface
{
@ -642,6 +661,14 @@ class Package
return $this->containerConfigurator->createReadOnlyContainer();
}
/**
* @return bool
*/
public function hasContainer(): bool
{
return $this->hasContainer;
}
/**
* @return string
*/
@ -652,27 +679,94 @@ class Package
/**
* @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;
return $this->checkStatus($status);
}
/**
* @return bool
*/
public function hasFailed(): bool
{
return $this->status === self::STATUS_FAILED;
}
/**
* @param int $status
* @return bool
*/
public function hasReachedStatus(int $status): bool
{
if ($this->hasFailed()) {
return false;
}
return isset(self::SUCCESS_STATUSES[$status]) && $this->checkStatus($status, '>=');
}
/**
* @param int $status
* @param value-of<Package::OPERATORS> $operator
* @return bool
*/
private function checkStatus(int $status, string $operator = '=='): bool
{
assert(isset(self::OPERATORS[$operator]));
return version_compare((string) $this->status, (string) $status, $operator);
}
/**
* @param Package::STATUS_* $status
*/
private function progress(int $status): void
{
$this->status = $status;
[$packageHookSuffix, $globalHook] = self::STATUSES_ACTIONS_MAP[$status] ?? [null, null];
if ($packageHookSuffix !== null) {
do_action($this->hookName($packageHookSuffix), $this);
}
if ($globalHook !== null) {
do_action($globalHook, $this->name(), $this);
}
}
/**
* @param string $packageName
* @param string $reason
* @param bool $throw
* @return bool
*/
private function handleConnectionFailure(string $packageName, string $reason, bool $throw): bool
{
$errorData = ['package' => $packageName, 'status' => $this->status];
$message = "Failed connecting package {$packageName} because {$reason}.";
do_action(
$this->hookName(self::ACTION_FAILED_CONNECT),
$packageName,
new \WP_Error('failed_connection', $message, $errorData)
);
if ($throw) {
throw new \Exception(
esc_html($message),
0,
$this->lastError // phpcs:ignore WordPress.Security.EscapeOutput
);
}
return false;
}
/**
* @param \Throwable $throwable
* @param Package::ACTION_FAILED_* $action
* @return void
* @throws \Throwable
*/
private function handleFailure(\Throwable $throwable, string $action): void
{
@ -690,18 +784,15 @@ class Package
/**
* @param int $status
* @param string $action
* @param string $operator
*
* @throws \Exception
* @psalm-suppress ArgumentTypeCoercion
* @param value-of<Package::OPERATORS> $operator
*/
private function assertStatus(int $status, string $action, string $operator = '=='): void
{
if (!version_compare((string) $this->status, (string) $status, $operator)) {
if (!$this->checkStatus($status, $operator)) {
throw new \Exception(
sprintf("Can't %s at this point of application.", $action),
sprintf("Can't %s at this point of application.", esc_html($action)),
0,
$this->lastError
$this->lastError // phpcs:ignore WordPress.Security.EscapeOutput
);
}
}
@ -713,7 +804,6 @@ class Package
* @param string $message
* @param string $function
* @param string $version
*
* @return void
*/
private function deprecatedArgument(string $message, string $function, string $version): void
@ -721,7 +811,9 @@ class Package
do_action('deprecated_argument_run', $function, $message, $version);
if (apply_filters('deprecated_argument_trigger_error', true)) {
trigger_error($message, \E_USER_DEPRECATED);
do_action('wp_trigger_error_run', $function, $message, \E_USER_DEPRECATED);
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
trigger_error(esc_html($message), \E_USER_DEPRECATED);
}
}
}

View file

@ -6,47 +6,30 @@ namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties;
class BaseProperties implements Properties
{
/**
* @var null|bool
*/
protected $isDebug = null;
/**
* @var string
*/
protected $baseName;
/**
* @var string
*/
protected $basePath;
/**
* @var string|null
*/
protected $baseUrl;
/**
* @var array
*/
protected $properties;
protected ?bool $isDebug = null;
protected string $baseName;
protected string $basePath;
protected ?string $baseUrl;
/** @var array<string, mixed> */
protected array $properties;
/**
* @param string $baseName
* @param string $basePath
* @param string|null $baseUrl
* @param array $properties
* @param array<string, mixed> $properties
*/
protected function __construct(
string $baseName,
string $basePath,
string $baseUrl = null,
?string $baseUrl = null,
array $properties = []
) {
$baseName = $this->sanitizeBaseName($baseName);
$basePath = (string) trailingslashit($basePath);
if ($baseUrl) {
$baseUrl = (string) trailingslashit($baseUrl);
$basePath = trailingslashit($basePath);
if ($baseUrl !== null) {
$baseUrl = trailingslashit($baseUrl);
}
$this->baseName = $baseName;
@ -58,11 +41,13 @@ class BaseProperties implements Properties
/**
* @param string $name
*
* @return string
* @return lowercase-string
*/
protected function sanitizeBaseName(string $name): string
{
substr_count($name, '/') and $name = dirname($name);
if (substr_count($name, '/')) {
$name = dirname($name);
}
return strtolower(pathinfo($name, PATHINFO_FILENAME));
}
@ -162,7 +147,9 @@ class BaseProperties implements Properties
{
$value = $this->get(self::PROP_REQUIRES_WP);
return $value && is_string($value) ? $value : null;
return (($value !== '') && is_string($value))
? $value
: null;
}
/**
@ -172,11 +159,13 @@ class BaseProperties implements Properties
{
$value = $this->get(self::PROP_REQUIRES_PHP);
return $value && is_string($value) ? $value : null;
return (($value !== '') && is_string($value))
? $value
: null;
}
/**
* @return array
* @return string[]
*/
public function tags(): array
{
@ -185,7 +174,8 @@ class BaseProperties implements Properties
/**
* @param string $key
* @param null $default
* @param mixed $default
*
* @return mixed
*/
public function get(string $key, $default = null)
@ -195,6 +185,7 @@ class BaseProperties implements Properties
/**
* @param string $key
*
* @return bool
*/
public function has(string $key): bool

View file

@ -5,17 +5,26 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties;
/**
* Class LibraryProperties
*
* @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties
* @phpstan-type ComposerAuthor array{
* name: string,
* email?: string,
* homepage?: string,
* role?: string,
* }
* @phpstan-type ComposerData array{
* name: string,
* version?: string,
* require?: array<string, string>,
* require-dev?: array<string, string>,
* description?: string,
* keywords?: string[],
* authors?: ComposerAuthor[],
* extra?: array{modularity?: array<string, string>},
* }
*/
class LibraryProperties extends BaseProperties
{
/**
* Allowed configuration in composer.json "extra.modularity".
*
* @var array
*/
/** Allowed configuration in composer.json "extra.modularity" */
public const EXTRA_KEYS = [
self::PROP_DOMAIN_PATH,
self::PROP_NAME,
@ -31,32 +40,33 @@ class LibraryProperties extends BaseProperties
*
* @return LibraryProperties
*
* @throws \Exception
* @psalm-suppress MixedArrayAccess
* phpcs:disable SlevomatCodingStandard.Complexity
*/
public static function new(string $composerJsonFile, ?string $baseUrl = null): LibraryProperties
{
if (!\is_file($composerJsonFile) || !\is_readable($composerJsonFile)) {
throw new \Exception("File {$composerJsonFile} does not exist or is not readable.");
}
// phpcs:enable SlevomatCodingStandard.Complexity
$content = (string) file_get_contents($composerJsonFile);
/** @var array $composerJsonData */
$composerJsonData = json_decode($content, true);
$composerJsonData = self::readComposerJsonData($composerJsonFile);
$properties = Properties::DEFAULT_PROPERTIES;
$properties[self::PROP_DESCRIPTION] = $composerJsonData['description'] ?? '';
$properties[self::PROP_TAGS] = $composerJsonData['keywords'] ?? [];
$authors = $composerJsonData['authors'] ?? [];
if (!is_array($authors)) {
$authors = [];
}
$names = [];
foreach ((array) $authors as $author) {
$name = $author['name'] ?? null;
if ($name && is_string($name)) {
foreach ($authors as $author) {
if (!is_array($author)) {
continue;
}
$name = $author['name'] ?? '';
if (($name !== '') && is_string($name)) {
$names[] = $name;
}
$url = $author['homepage'] ?? null;
if ($url && !$properties['authorUri'] && is_string($url)) {
$url = $author['homepage'] ?? '';
if (($url !== '') && ($properties[self::PROP_AUTHOR_URI] === '') && is_string($url)) {
$properties[self::PROP_AUTHOR_URI] = $url;
}
}
@ -66,6 +76,9 @@ class LibraryProperties extends BaseProperties
// Custom settings which can be stored in composer.json "extra.modularity"
$extra = $composerJsonData['extra']['modularity'] ?? [];
if (!is_array($extra)) {
$extra = [];
}
foreach (self::EXTRA_KEYS as $key) {
$properties[$key] = $extra[$key] ?? '';
}
@ -74,39 +87,50 @@ class LibraryProperties extends BaseProperties
$properties[self::PROP_REQUIRES_PHP] = self::extractPhpVersion($composerJsonData);
// composer.json might have "version" in root
$version = $composerJsonData['version'] ?? null;
if ($version && is_string($version)) {
$version = $composerJsonData['version'] ?? '';
if (($version !== '') && is_string($version)) {
$properties[self::PROP_VERSION] = $version;
}
[$baseName, $name] = static::buildNames($composerJsonData);
$basePath = dirname($composerJsonFile);
if (empty($properties[self::PROP_NAME])) {
if (($properties[self::PROP_NAME] === '') || !is_string($properties[self::PROP_NAME])) {
$properties[self::PROP_NAME] = $name;
}
return new self(
$baseName,
$basePath,
$baseUrl,
$properties
);
return new self($baseName, $basePath, $baseUrl, $properties);
}
/**
* @param array $composerJsonData
* @param string $url
*
* @return static
*/
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;
}
/**
* @param ComposerData $composerJsonData
*
* @return array{string, string}
*/
private static function buildNames(array $composerJsonData): array
protected 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"
// From "syde/foo-bar-baz" to "Syde Foo Bar Baz"
$name = mb_convert_case(
str_replace(['-', '_', '.'], ' ', implode(' ', $packageNamePieces)),
MB_CASE_TITLE
MB_CASE_TITLE,
);
return [$basename, $name];
@ -122,88 +146,111 @@ class LibraryProperties extends BaseProperties
* `5.6 || >= 7.1` returns `5.6`
* `>= 7.1 < 8` returns `7.1`
*
* @param array $composerData
* @param ComposerData $composerData
* @param string $key
*
* @return string|null
* @return string
*/
private static function extractPhpVersion(array $composerData, string $key = 'require'): ?string
{
protected 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))
$base = $composerData[$key] ?? null;
$requirement = is_array($base)
? ($base['php'] ?? '')
: '';
$version = (($requirement !== '') && is_string($requirement))
? trim($requirement)
: null;
if (!$version) {
return $nextKey
: '';
if ($version === '') {
return ($nextKey !== null)
? 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);
/** @var non-empty-string|null $found */
$found = null;
foreach ($alternatives as $alternative) {
/** @var callable(string):?string $matcher */
$itemFound = $matcher($alternative);
if ($itemFound && (!$found || version_compare($itemFound, $found, '<'))) {
$itemFound = static::parseVersion($alternative);
if (
($itemFound !== '')
&& (($found === null) || version_compare($itemFound, $found, '<'))
) {
$found = $itemFound;
}
}
if ($found) {
if ($found !== null) {
return $found;
}
return $nextKey
return ($nextKey !== null)
? static::extractPhpVersion($composerData, $nextKey)
: null;
: '';
}
/**
* @param string $url
* @param string $version
*
* @return static
*
* @throws \Exception
* @return string
*/
public function withBaseUrl(string $url): LibraryProperties
protected static function parseVersion(string $version): string
{
if ($this->baseUrl !== null) {
throw new \Exception(sprintf('%s::$baseUrl property is not overridable.', __CLASS__));
$version = trim($version);
if ($version === '') {
return '';
}
$this->baseUrl = trailingslashit($url);
// versions range like `>= 7.2.4 < 8`
if (preg_match('{>=?([\s0-9\.]+)<}', $version, $matches)) {
return trim($matches[1], " \t\n\r\0\x0B.");
}
return $this;
// 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 '';
}
/**
* @param string $composerJsonFile
*
* @return ComposerData
* @throws \Exception
*/
private static function readComposerJsonData(string $composerJsonFile): array
{
if (!\is_file($composerJsonFile) || !\is_readable($composerJsonFile)) {
throw new \Exception(
esc_html("File {$composerJsonFile} does not exist or is not readable."),
);
}
$content = (string) file_get_contents($composerJsonFile);
/** @var ComposerData $composerJsonData */
$composerJsonData = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception(
esc_html("Error reading file {$composerJsonFile}: " . json_last_error_msg()),
);
}
return $composerJsonData;
}
}

View file

@ -4,24 +4,14 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties;
/**
* Class PluginProperties
*
* @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties
*
* @psalm-suppress PossiblyFalseArgument, InvalidArgument
*/
class PluginProperties extends BaseProperties
{
/**
* Custom properties for Plugins.
*/
// Custom properties for Plugins
public const PROP_NETWORK = 'network';
public const PROP_REQUIRES_PLUGINS = 'requiresPlugins';
/**
* Available methods of Properties::__call()
* from plugin headers.
*
* @link https://developer.wordpress.org/reference/functions/get_plugin_data/
* @see https://developer.wordpress.org/reference/functions/get_plugin_data/
*/
protected const HEADERS = [
self::PROP_AUTHOR => 'Author',
@ -37,36 +27,17 @@ class PluginProperties extends BaseProperties
// additional headers
self::PROP_NETWORK => 'Network',
self::PROP_REQUIRES_PLUGINS => 'RequiresPlugins',
];
/**
* @var string
*/
private $pluginMainFile;
/**
* @var string
*/
private $pluginBaseName;
/**
* @var bool|null
*/
protected $isMu;
/**
* @var bool|null
*/
protected $isActive;
/**
* @var bool|null
*/
protected $isNetworkActive;
private string $pluginMainFile;
private string $pluginBaseName;
protected ?bool $isMu = null;
protected ?bool $isActive = null;
protected ?bool $isNetworkActive = null;
/**
* @param string $pluginMainFile
*
* @return PluginProperties
*/
public static function new(string $pluginMainFile): PluginProperties
@ -75,8 +46,6 @@ class PluginProperties extends BaseProperties
}
/**
* PluginProperties constructor.
*
* @param string $pluginMainFile
*/
protected function __construct(string $pluginMainFile)
@ -85,7 +54,11 @@ class PluginProperties extends BaseProperties
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$pluginData = get_plugin_data($pluginMainFile);
// $markup = false, to avoid an incorrect early wptexturize call.
// $translate = false, to avoid loading translations too early
// @see https://core.trac.wordpress.org/ticket/49965
// @see https://core.trac.wordpress.org/ticket/34114
$pluginData = (array) get_plugin_data($pluginMainFile, false, false);
$properties = Properties::DEFAULT_PROPERTIES;
// Map pluginData to internal structure.
@ -93,6 +66,7 @@ class PluginProperties extends BaseProperties
$properties[$key] = $pluginData[$pluginDataKey] ?? '';
unset($pluginData[$pluginDataKey]);
}
/** @var array<string, mixed> $properties */
$properties = array_merge($properties, $pluginData);
$this->pluginMainFile = wp_normalize_path($pluginMainFile);
@ -119,14 +93,22 @@ class PluginProperties extends BaseProperties
/**
* @return bool
*
* @psalm-suppress PossiblyFalseArgument
*/
public function network(): bool
{
return (bool) $this->get(self::PROP_NETWORK, false);
}
/**
* @return string[]
*/
public function requiresPlugins(): array
{
$value = $this->get(self::PROP_REQUIRES_PLUGINS);
return $value && is_string($value) ? explode(',', $value) : [];
}
/**
* @return bool
*/
@ -163,10 +145,6 @@ class PluginProperties extends BaseProperties
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;
}

View file

@ -17,9 +17,7 @@ interface Properties
public const PROP_REQUIRES_WP = 'requiresWp';
public const PROP_REQUIRES_PHP = 'requiresPhp';
public const PROP_TAGS = 'tags';
/**
* @var array
*/
public const DEFAULT_PROPERTIES = [
self::PROP_AUTHOR => '',
self::PROP_AUTHOR_URI => '',
@ -36,15 +34,13 @@ interface Properties
/**
* @param string $key
* @param null $default
*
* @param mixed $default
* @return mixed
*/
public function get(string $key, $default = null);
/**
* @param string $key
*
* @return bool
*/
public function has(string $key): bool;
@ -103,6 +99,7 @@ interface Properties
/**
* The home page of the plugin, theme or library.
*
* @return string
*/
public function uri(): string;
@ -122,7 +119,7 @@ interface Properties
/**
* Optional. Specify the minimum required PHP version.
*
* @return string
* @return string|null
*/
public function requiresPhp(): ?string;
@ -130,10 +127,10 @@ interface Properties
* 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 string[]
*
* @return array
* @see https://developer.wordpress.org/reference/classes/wp_theme/#properties
* @see https://getcomposer.org/doc/04-schema.md#keywords
*/
public function tags(): array;
}

View file

@ -4,26 +4,12 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties;
/**
* Class ThemeProperties
*
* @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties
*
* @psalm-suppress PossiblyFalseArgument, InvalidArgument
*/
class ThemeProperties extends BaseProperties
{
/**
* Additional properties specific for themes.
*/
public const PROP_STATUS = 'status';
public const PROP_TEMPLATE = 'template';
/**
* Available methods of Properties::__call()
* from theme headers.
*
* @link https://developer.wordpress.org/reference/classes/wp_theme/
*/
/** @see https://developer.wordpress.org/reference/classes/wp_theme/ */
protected const HEADERS = [
self::PROP_AUTHOR => 'Author',
self::PROP_AUTHOR_URI => 'AuthorURI',
@ -53,8 +39,6 @@ class ThemeProperties extends BaseProperties
}
/**
* ThemeProperties constructor.
*
* @param string $themeDirectory
*/
protected function __construct(string $themeDirectory)
@ -67,13 +51,15 @@ class ThemeProperties extends BaseProperties
$properties = Properties::DEFAULT_PROPERTIES;
foreach (self::HEADERS as $key => $themeKey) {
/** @psalm-suppress DocblockTypeContradiction */
$properties[$key] = $theme->get($themeKey) ?? '';
$property = $theme->get($themeKey);
if (is_string($property) || is_array($property)) {
$properties[$key] = $property;
}
}
$baseName = $theme->get_stylesheet();
$basePath = $theme->get_stylesheet_directory();
$baseUrl = (string) trailingslashit($theme->get_stylesheet_directory_uri());
$baseUrl = trailingslashit($theme->get_stylesheet_directory_uri());
parent::__construct(
$baseName,
@ -84,8 +70,6 @@ class ThemeProperties extends BaseProperties
}
/**
* If the theme is published.
*
* @return string
*/
public function status(): string
@ -93,6 +77,9 @@ class ThemeProperties extends BaseProperties
return (string) $this->get(self::PROP_STATUS);
}
/**
* @return string
*/
public function template(): string
{
return (string) $this->get(self::PROP_TEMPLATE);
@ -120,7 +107,7 @@ class ThemeProperties extends BaseProperties
public function parentThemeProperties(): ?ThemeProperties
{
$template = $this->template();
if (!$template) {
if ($template === '') {
return null;
}

View file

@ -2,9 +2,11 @@
namespace WooCommerce\PayPalCommerce\Vendor\Psr\Container;
use Throwable;
/**
* Base interface representing a generic exception in a container.
*/
interface ContainerExceptionInterface
interface ContainerExceptionInterface extends Throwable
{
}

View file

@ -18,14 +18,7 @@ use WooCommerce\PayPalCommerce\AdminNotices\Endpoint\MuteMessageEndpoint;
return array(
'admin-notices.url' => static function ( ContainerInterface $container ): string {
$path = realpath( __FILE__ );
if ( false === $path ) {
return '';
}
return plugins_url(
'/modules/ppcp-admin-notices/',
dirname( $path, 3 ) . '/woocommerce-paypal-payments.php'
);
return plugins_url( '/modules/ppcp-admin-notices/', $container->get( 'ppcp.path-to-plugin-main-file' ) );
},
'admin-notices.renderer' => static function ( ContainerInterface $container ): RendererInterface {
return new Renderer(

View file

@ -49,7 +49,7 @@ class AdminNotices implements ServiceModule, ExtendingModule, ExecutableModule {
add_action(
'admin_notices',
function() use ( $renderer ) {
function () use ( $renderer ) {
$renderer->render();
}
);
@ -113,11 +113,10 @@ class AdminNotices implements ServiceModule, ExtendingModule, ExecutableModule {
add_action(
'woocommerce_init',
function() {
function () {
if ( is_admin() && is_callable( array( WC(), 'is_wc_admin_active' ) ) && WC()->is_wc_admin_active() && class_exists( 'Automattic\WooCommerce\Admin\Notes\Notes' ) ) {
MexicoInstallmentsNote::init();
}
}
);

View file

@ -53,7 +53,7 @@ class MuteMessageEndpoint {
*
* @return string
*/
public static function nonce() : string {
public static function nonce(): string {
return self::ENDPOINT;
}
@ -62,7 +62,7 @@ class MuteMessageEndpoint {
*
* @return void
*/
public function handle_request() : void {
public function handle_request(): void {
try {
$data = $this->request_data->read_request( $this->nonce() );
} catch ( RuntimeException $ex ) {

View file

@ -62,7 +62,7 @@ class Message {
*
* @return string
*/
public function message() : string {
public function message(): string {
return $this->message;
}
@ -71,7 +71,7 @@ class Message {
*
* @return string
*/
public function type() : string {
public function type(): string {
return $this->type;
}
@ -80,7 +80,7 @@ class Message {
*
* @return bool
*/
public function is_dismissible() : bool {
public function is_dismissible(): bool {
return $this->dismissible;
}
@ -89,7 +89,7 @@ class Message {
*
* @return string
*/
public function wrapper() : string {
public function wrapper(): string {
return $this->wrapper;
}
@ -98,7 +98,7 @@ class Message {
*
* @return array
*/
public function to_array() : array {
public function to_array(): array {
return array(
'type' => $this->type,
'message' => $this->message,
@ -114,7 +114,7 @@ class Message {
*
* @return Message
*/
public static function from_array( array $data ) : Message {
public static function from_array( array $data ): Message {
return new Message(
(string) ( $data['message'] ?? '' ),
(string) ( $data['type'] ?? '' ),

View file

@ -47,7 +47,7 @@ class PersistentMessage extends Message {
*
* @return string
*/
public function id( bool $with_db_prefix = false ) : string {
public function id( bool $with_db_prefix = false ): string {
if ( ! $this->message_id ) {
return '';
}
@ -58,7 +58,7 @@ class PersistentMessage extends Message {
/**
* {@inheritDoc}
*/
public function to_array() : array {
public function to_array(): array {
$data = parent::to_array();
$data['id'] = $this->message_id;
@ -70,7 +70,7 @@ class PersistentMessage extends Message {
*
* @return PersistentMessage
*/
public static function from_array( array $data ) : Message {
public static function from_array( array $data ): Message {
return new PersistentMessage(
(string) ( $data['id'] ?? '' ),
(string) ( $data['message'] ?? '' ),
@ -84,7 +84,7 @@ class PersistentMessage extends Message {
*
* @return bool
*/
public function is_muted() : bool {
public function is_muted(): bool {
$user_id = get_current_user_id();
if ( ! $this->message_id || ! $user_id ) {
@ -99,7 +99,7 @@ class PersistentMessage extends Message {
*
* @return void
*/
public function mute() : void {
public function mute(): void {
$user_id = get_current_user_id();
if ( $this->message_id && $user_id && ! $this->is_muted() ) {
@ -112,7 +112,7 @@ class PersistentMessage extends Message {
*
* @return void
*/
public static function clear_all() : void {
public static function clear_all(): void {
global $wpdb;
$wpdb->query(

View file

@ -95,7 +95,7 @@ class Renderer implements RendererInterface {
/**
* {@inheritDoc}
*/
public function enqueue_admin() : void {
public function enqueue_admin(): void {
if ( ! $this->can_mute_message ) {
return;
}
@ -129,7 +129,7 @@ class Renderer implements RendererInterface {
*
* @return array
*/
protected function script_data_for_admin() : array {
protected function script_data_for_admin(): array {
$ajax_url = admin_url( 'admin-ajax.php' );
return array(

View file

@ -26,5 +26,5 @@ interface RendererInterface {
*
* @return void
*/
public function enqueue_admin() : void;
public function enqueue_admin(): void;
}

View file

@ -25,7 +25,7 @@ class Repository implements RepositoryInterface {
*
* @return Message[]
*/
public function current_message() : array {
public function current_message(): array {
return array_filter(
/**
* Returns the list of admin messages.
@ -34,7 +34,7 @@ class Repository implements RepositoryInterface {
self::NOTICES_FILTER,
array()
),
function ( $element ) : bool {
function ( $element ): bool {
if ( $element instanceof PersistentMessage ) {
return ! $element->is_muted();
}
@ -51,7 +51,7 @@ class Repository implements RepositoryInterface {
*
* @return void
*/
public function persist( Message $message ) : void {
public function persist( Message $message ): void {
$persisted_notices = get_option( self::PERSISTED_NOTICES_OPTION ) ?: array();
$persisted_notices[] = $message->to_array();
@ -64,7 +64,7 @@ class Repository implements RepositoryInterface {
*
* @return array|Message[]
*/
public function get_persisted_and_clear() : array {
public function get_persisted_and_clear(): array {
$notices = array();
$persisted_data = get_option( self::PERSISTED_NOTICES_OPTION ) ?: array();

View file

@ -86,7 +86,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
return array(
'api.host' => static function( ContainerInterface $container ) : string {
'api.host' => static function ( ContainerInterface $container ): string {
$environment = $container->get( 'settings.environment' );
assert( $environment instanceof Environment );
@ -96,25 +96,25 @@ return array(
return (string) $container->get( 'api.production-host' );
},
'api.paypal-host' => function( ContainerInterface $container ) : string {
'api.paypal-host' => function ( ContainerInterface $container ): string {
return PAYPAL_API_URL;
},
// It seems this 'api.paypal-website-url' key is always overridden in ppcp-onboarding/services.php.
'api.paypal-website-url' => function( ContainerInterface $container ) : string {
'api.paypal-website-url' => function ( ContainerInterface $container ): string {
return PAYPAL_URL;
},
'api.factory.paypal-checkout-url' => function( ContainerInterface $container ) : callable {
'api.factory.paypal-checkout-url' => function ( ContainerInterface $container ): callable {
return function ( string $id ) use ( $container ): string {
return $container->get( 'api.paypal-website-url' ) . '/checkoutnow?token=' . $id;
};
},
'api.partner_merchant_id' => static function () : string {
'api.partner_merchant_id' => static function (): string {
return '';
},
'api.merchant_email' => function () : string {
'api.merchant_email' => function (): string {
return '';
},
'api.merchant_id' => function () : string {
'api.merchant_id' => function (): string {
return '';
},
'api.key' => static function (): string {
@ -142,7 +142,7 @@ return array(
$container->get( 'wcgateway.settings' )
);
},
'api.endpoint.partners' => static function ( ContainerInterface $container ) : PartnersEndpoint {
'api.endpoint.partners' => static function ( ContainerInterface $container ): PartnersEndpoint {
return new PartnersEndpoint(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
@ -153,10 +153,10 @@ return array(
$container->get( 'api.helper.failure-registry' )
);
},
'api.factory.sellerstatus' => static function ( ContainerInterface $container ) : SellerStatusFactory {
'api.factory.sellerstatus' => static function ( ContainerInterface $container ): SellerStatusFactory {
return new SellerStatusFactory();
},
'api.endpoint.payment-token' => static function ( ContainerInterface $container ) : PaymentTokenEndpoint {
'api.endpoint.payment-token' => static function ( ContainerInterface $container ): PaymentTokenEndpoint {
return new PaymentTokenEndpoint(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
@ -166,14 +166,14 @@ return array(
$container->get( 'api.repository.customer' )
);
},
'api.endpoint.payment-tokens' => static function( ContainerInterface $container ) : PaymentTokensEndpoint {
'api.endpoint.payment-tokens' => static function ( ContainerInterface $container ): PaymentTokensEndpoint {
return new PaymentTokensEndpoint(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.endpoint.webhook' => static function ( ContainerInterface $container ) : WebhookEndpoint {
'api.endpoint.webhook' => static function ( ContainerInterface $container ): WebhookEndpoint {
return new WebhookEndpoint(
$container->get( 'api.host' ),
@ -183,7 +183,7 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.endpoint.partner-referrals' => static function ( ContainerInterface $container ) : PartnerReferrals {
'api.endpoint.partner-referrals' => static function ( ContainerInterface $container ): PartnerReferrals {
return new PartnerReferrals(
$container->get( 'api.host' ),
@ -191,7 +191,7 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.endpoint.partner-referrals-sandbox' => static function ( ContainerInterface $container ) : PartnerReferrals {
'api.endpoint.partner-referrals-sandbox' => static function ( ContainerInterface $container ): PartnerReferrals {
return new PartnerReferrals(
CONNECT_WOO_SANDBOX_URL,
@ -199,7 +199,7 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.endpoint.partner-referrals-production' => static function ( ContainerInterface $container ) : PartnerReferrals {
'api.endpoint.partner-referrals-production' => static function ( ContainerInterface $container ): PartnerReferrals {
return new PartnerReferrals(
CONNECT_WOO_URL,
@ -207,7 +207,7 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.endpoint.identity-token' => static function ( ContainerInterface $container ) : IdentityToken {
'api.endpoint.identity-token' => static function ( ContainerInterface $container ): IdentityToken {
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$settings = $container->get( 'wcgateway.settings' );
$customer_repository = $container->get( 'api.repository.customer' );
@ -232,7 +232,7 @@ return array(
$logger
);
},
'api.endpoint.login-seller' => static function ( ContainerInterface $container ) : LoginSeller {
'api.endpoint.login-seller' => static function ( ContainerInterface $container ): LoginSeller {
$logger = $container->get( 'woocommerce.logger.woocommerce' );
return new LoginSeller(
@ -287,7 +287,7 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.endpoint.billing-plans' => static function( ContainerInterface $container ): BillingPlans {
'api.endpoint.billing-plans' => static function ( ContainerInterface $container ): BillingPlans {
return new BillingPlans(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
@ -296,21 +296,21 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.endpoint.billing-subscriptions' => static function( ContainerInterface $container ): BillingSubscriptions {
'api.endpoint.billing-subscriptions' => static function ( ContainerInterface $container ): BillingSubscriptions {
return new BillingSubscriptions(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.endpoint.payment-method-tokens' => static function( ContainerInterface $container ): PaymentMethodTokensEndpoint {
'api.endpoint.payment-method-tokens' => static function ( ContainerInterface $container ): PaymentMethodTokensEndpoint {
return new PaymentMethodTokensEndpoint(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.repository.partner-referrals-data' => static function ( ContainerInterface $container ) : PartnerReferralsData {
'api.repository.partner-referrals-data' => static function ( ContainerInterface $container ): PartnerReferralsData {
$dcc_applies = $container->get( 'api.helpers.dccapplies' );
return new PartnerReferralsData( $dcc_applies );
@ -320,11 +320,11 @@ return array(
$merchant_id = $container->get( 'api.merchant_id' );
return new PayeeRepository( $merchant_email, $merchant_id );
},
'api.repository.customer' => static function( ContainerInterface $container ): CustomerRepository {
'api.repository.customer' => static function ( ContainerInterface $container ): CustomerRepository {
$prefix = $container->get( 'api.prefix' );
return new CustomerRepository( $prefix );
},
'api.repository.order' => static function( ContainerInterface $container ): OrderRepository {
'api.repository.order' => static function ( ContainerInterface $container ): OrderRepository {
return new OrderRepository(
$container->get( 'api.endpoint.order' )
);
@ -345,10 +345,10 @@ return array(
$container->get( 'settings.merchant-details' )
);
},
'api.factory.payment-token' => static function ( ContainerInterface $container ) : PaymentTokenFactory {
'api.factory.payment-token' => static function ( ContainerInterface $container ): PaymentTokenFactory {
return new PaymentTokenFactory();
},
'api.factory.payment-token-action-links' => static function ( ContainerInterface $container ) : PaymentTokenActionLinksFactory {
'api.factory.payment-token-action-links' => static function ( ContainerInterface $container ): PaymentTokenActionLinksFactory {
return new PaymentTokenActionLinksFactory();
},
'api.factory.webhook' => static function ( ContainerInterface $container ): WebhookFactory {
@ -485,25 +485,25 @@ return array(
'api.factory.fraud-processor-response' => static function ( ContainerInterface $container ): FraudProcessorResponseFactory {
return new FraudProcessorResponseFactory();
},
'api.factory.product' => static function( ContainerInterface $container ): ProductFactory {
'api.factory.product' => static function ( ContainerInterface $container ): ProductFactory {
return new ProductFactory();
},
'api.factory.billing-cycle' => static function( ContainerInterface $container ): BillingCycleFactory {
'api.factory.billing-cycle' => static function ( ContainerInterface $container ): BillingCycleFactory {
return new BillingCycleFactory( $container->get( 'api.shop.currency.getter' ) );
},
'api.factory.payment-preferences' => static function( ContainerInterface $container ):PaymentPreferencesFactory {
'api.factory.payment-preferences' => static function ( ContainerInterface $container ): PaymentPreferencesFactory {
return new PaymentPreferencesFactory( $container->get( 'api.shop.currency.getter' ) );
},
'api.factory.plan' => static function( ContainerInterface $container ): PlanFactory {
'api.factory.plan' => static function ( ContainerInterface $container ): PlanFactory {
return new PlanFactory(
$container->get( 'api.factory.billing-cycle' ),
$container->get( 'api.factory.payment-preferences' )
);
},
'api.factory.card-authentication-result-factory' => static function( ContainerInterface $container ): CardAuthenticationResultFactory {
'api.factory.card-authentication-result-factory' => static function ( ContainerInterface $container ): CardAuthenticationResultFactory {
return new CardAuthenticationResultFactory();
},
'api.helpers.dccapplies' => static function ( ContainerInterface $container ) : DccApplies {
'api.helpers.dccapplies' => static function ( ContainerInterface $container ): DccApplies {
return new DccApplies(
$container->get( 'api.dcc-supported-country-currency-matrix' ),
$container->get( 'api.dcc-supported-country-card-matrix' ),
@ -512,21 +512,21 @@ return array(
);
},
'api.shop.currency.getter' => static function ( ContainerInterface $container ) : CurrencyGetter {
'api.shop.currency.getter' => static function ( ContainerInterface $container ): CurrencyGetter {
return new CurrencyGetter();
},
'api.shop.country' => static function ( ContainerInterface $container ) : string {
'api.shop.country' => static function ( ContainerInterface $container ): string {
$location = wc_get_base_location();
return $location['country'];
},
'api.shop.is-psd2-country' => static function ( ContainerInterface $container ) : bool {
'api.shop.is-psd2-country' => static function ( ContainerInterface $container ): bool {
return in_array(
$container->get( 'api.shop.country' ),
$container->get( 'api.psd2-countries' ),
true
);
},
'api.shop.is-currency-supported' => static function ( ContainerInterface $container ) : bool {
'api.shop.is-currency-supported' => static function ( ContainerInterface $container ): bool {
return in_array(
$container->get( 'api.shop.currency.getter' )->get(),
$container->get( 'api.supported-currencies' ),
@ -593,7 +593,7 @@ return array(
*
* From https://developer.paypal.com/docs/reports/reference/paypal-supported-currencies/
*/
'api.supported-currencies' => static function ( ContainerInterface $container ) : array {
'api.supported-currencies' => static function ( ContainerInterface $container ): array {
return array(
'AUD',
'BRL',
@ -626,7 +626,7 @@ return array(
/**
* The matrix which countries and currency combinations can be used for DCC.
*/
'api.dcc-supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array {
'api.dcc-supported-country-currency-matrix' => static function ( ContainerInterface $container ): array {
$default_currencies = apply_filters(
'woocommerce_paypal_payments_supported_currencies',
array(
@ -712,7 +712,7 @@ return array(
/**
* Which countries support which credit cards. Empty credit card arrays mean no restriction on currency.
*/
'api.dcc-supported-country-card-matrix' => static function ( ContainerInterface $container ) : array {
'api.dcc-supported-country-card-matrix' => static function ( ContainerInterface $container ): array {
$mastercard_visa_amex = array(
'mastercard' => array(),
'visa' => array(),
@ -793,7 +793,7 @@ return array(
);
},
'api.psd2-countries' => static function ( ContainerInterface $container ) : array {
'api.psd2-countries' => static function ( ContainerInterface $container ): array {
return array(
'AT',
'BE',
@ -826,7 +826,7 @@ return array(
);
},
'api.paylater-countries' => static function ( ContainerInterface $container ) : array {
'api.paylater-countries' => static function ( ContainerInterface $container ): array {
return apply_filters(
'woocommerce_paypal_payments_supported_paylater_countries',
array(
@ -840,20 +840,20 @@ return array(
)
);
},
'api.order-helper' => static function( ContainerInterface $container ): OrderHelper {
'api.order-helper' => static function ( ContainerInterface $container ): OrderHelper {
return new OrderHelper();
},
'api.helper.order-transient' => static function( ContainerInterface $container ): OrderTransient {
'api.helper.order-transient' => static function ( ContainerInterface $container ): OrderTransient {
$cache = $container->get( 'api.paypal-bearer-cache' );
$purchase_unit_sanitizer = $container->get( 'api.helper.purchase-unit-sanitizer' );
return new OrderTransient( $cache, $purchase_unit_sanitizer );
},
'api.helper.failure-registry' => static function( ContainerInterface $container ): FailureRegistry {
'api.helper.failure-registry' => static function ( ContainerInterface $container ): FailureRegistry {
$cache = new Cache( 'ppcp-paypal-api-status-cache' );
return new FailureRegistry( $cache );
},
'api.helper.purchase-unit-sanitizer' => SingletonDecorator::make(
static function( ContainerInterface $container ): PurchaseUnitSanitizer {
static function ( ContainerInterface $container ): PurchaseUnitSanitizer {
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
@ -862,24 +862,24 @@ return array(
return new PurchaseUnitSanitizer( $behavior, $line_name );
}
),
'api.client-credentials' => static function( ContainerInterface $container ): ClientCredentials {
'api.client-credentials' => static function ( ContainerInterface $container ): ClientCredentials {
return new ClientCredentials(
$container->get( 'wcgateway.settings' )
);
},
'api.paypal-bearer-cache' => static function( ContainerInterface $container ): Cache {
'api.paypal-bearer-cache' => static function ( ContainerInterface $container ): Cache {
return new Cache( 'ppcp-paypal-bearer' );
},
'api.client-credentials-cache' => static function( ContainerInterface $container ): Cache {
'api.client-credentials-cache' => static function ( ContainerInterface $container ): Cache {
return new Cache( 'ppcp-client-credentials-cache' );
},
'api.user-id-token-cache' => static function( ContainerInterface $container ): Cache {
'api.user-id-token-cache' => static function ( ContainerInterface $container ): Cache {
return new Cache( 'ppcp-id-token-cache' );
},
'api.reference-transaction-status-cache' => static function( ContainerInterface $container ): Cache {
'api.reference-transaction-status-cache' => static function ( ContainerInterface $container ): Cache {
return new Cache( 'ppcp-reference-transaction-status-cache' );
},
'api.user-id-token' => static function( ContainerInterface $container ): UserIdToken {
'api.user-id-token' => static function ( ContainerInterface $container ): UserIdToken {
return new UserIdToken(
$container->get( 'api.host' ),
$container->get( 'woocommerce.logger.woocommerce' ),
@ -887,7 +887,7 @@ return array(
$container->get( 'api.user-id-token-cache' )
);
},
'api.sdk-client-token' => static function( ContainerInterface $container ): SdkClientToken {
'api.sdk-client-token' => static function ( ContainerInterface $container ): SdkClientToken {
return new SdkClientToken(
$container->get( 'api.host' ),
$container->get( 'woocommerce.logger.woocommerce' ),
@ -895,39 +895,39 @@ return array(
$container->get( 'api.client-credentials-cache' )
);
},
'api.paypal-host-production' => static function( ContainerInterface $container ) : string {
'api.paypal-host-production' => static function ( ContainerInterface $container ): string {
return PAYPAL_API_URL;
},
'api.paypal-host-sandbox' => static function( ContainerInterface $container ) : string {
'api.paypal-host-sandbox' => static function ( ContainerInterface $container ): string {
return PAYPAL_SANDBOX_API_URL;
},
'api.paypal-website-url-production' => static function( ContainerInterface $container ) : string {
'api.paypal-website-url-production' => static function ( ContainerInterface $container ): string {
return PAYPAL_URL;
},
'api.paypal-website-url-sandbox' => static function( ContainerInterface $container ) : string {
'api.paypal-website-url-sandbox' => static function ( ContainerInterface $container ): string {
return PAYPAL_SANDBOX_URL;
},
'api.partner_merchant_id-production' => static function( ContainerInterface $container ) : string {
'api.partner_merchant_id-production' => static function ( ContainerInterface $container ): string {
return CONNECT_WOO_MERCHANT_ID;
},
'api.partner_merchant_id-sandbox' => static function( ContainerInterface $container ) : string {
'api.partner_merchant_id-sandbox' => static function ( ContainerInterface $container ): string {
return CONNECT_WOO_SANDBOX_MERCHANT_ID;
},
'api.endpoint.login-seller-production' => static function ( ContainerInterface $container ) : LoginSeller {
'api.endpoint.login-seller-production' => static function ( ContainerInterface $container ): LoginSeller {
return new LoginSeller(
$container->get( 'api.paypal-host-production' ),
$container->get( 'api.partner_merchant_id-production' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.endpoint.login-seller-sandbox' => static function ( ContainerInterface $container ) : LoginSeller {
'api.endpoint.login-seller-sandbox' => static function ( ContainerInterface $container ): LoginSeller {
return new LoginSeller(
$container->get( 'api.paypal-host-sandbox' ),
$container->get( 'api.partner_merchant_id-sandbox' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.env.paypal-host' => static function ( ContainerInterface $container ) : EnvironmentConfig {
'api.env.paypal-host' => static function ( ContainerInterface $container ): EnvironmentConfig {
/**
* Environment specific API host names.
*
@ -939,7 +939,7 @@ return array(
$container->get( 'api.paypal-host-sandbox' )
);
},
'api.env.endpoint.login-seller' => static function ( ContainerInterface $container ) : EnvironmentConfig {
'api.env.endpoint.login-seller' => static function ( ContainerInterface $container ): EnvironmentConfig {
/**
* Environment specific LoginSeller API instances.
*
@ -951,7 +951,7 @@ return array(
$container->get( 'api.endpoint.login-seller-sandbox' )
);
},
'api.env.endpoint.partner-referrals' => static function ( ContainerInterface $container ) : EnvironmentConfig {
'api.env.endpoint.partner-referrals' => static function ( ContainerInterface $container ): EnvironmentConfig {
/**
* Environment specific PartnerReferrals API instances.
*
@ -981,7 +981,7 @@ return array(
return CONNECT_WOO_URL;
},
'api.helper.partner-attribution' => static function ( ContainerInterface $container ) : PartnerAttribution {
'api.helper.partner-attribution' => static function ( ContainerInterface $container ): PartnerAttribution {
return new PartnerAttribution(
'ppcp_bn_code',
array(

View file

@ -66,7 +66,7 @@ class ApiModule implements ServiceModule, FactoryModule, ExtendingModule, Execut
);
add_filter(
'ppcp_create_order_request_body_data',
function( array $data ) use ( $c ) {
function ( array $data ) use ( $c ) {
foreach ( ( $data['purchase_units'] ?? array() ) as $purchase_unit_index => $purchase_unit ) {
foreach ( ( $purchase_unit['items'] ?? array() ) as $item_index => $item ) {

View file

@ -100,7 +100,7 @@ class PayPalBearer implements Bearer {
* @throws RuntimeException When request fails.
* @return Token
*/
public function bearer() : Token {
public function bearer(): Token {
try {
$bearer = Token::from_json( (string) $this->cache->get( self::CACHE_KEY ) );
@ -115,7 +115,7 @@ class PayPalBearer implements Bearer {
*
* @return string The client ID from settings, or the key defined via constructor.
*/
private function get_key() : string {
private function get_key(): string {
if (
$this->settings
&& $this->settings->has( 'client_id' )
@ -132,7 +132,7 @@ class PayPalBearer implements Bearer {
*
* @return string The client secret from settings, or the value defined via constructor.
*/
private function get_secret() : string {
private function get_secret(): string {
if (
$this->settings
&& $this->settings->has( 'client_secret' )
@ -150,7 +150,7 @@ class PayPalBearer implements Bearer {
* @throws RuntimeException When request fails.
* @return Token
*/
private function newBearer() : Token {
private function newBearer(): Token {
$key = $this->get_key();
$secret = $this->get_secret();
$url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials';

View file

@ -65,7 +65,7 @@ class BillingSubscriptions {
* @throws RuntimeException If the request fails.
* @throws PayPalApiException If the request fails.
*/
public function suspend( string $id ):void {
public function suspend( string $id ): void {
$data = array(
'reason' => sprintf( 'Suspended by %s.', is_admin() ? 'merchant' : 'customer' ),
);
@ -214,6 +214,4 @@ class BillingSubscriptions {
return $json;
}
}

View file

@ -108,7 +108,7 @@ class PartnersEndpoint {
* @return SellerStatus
* @throws RuntimeException When request could not be fulfilled.
*/
public function seller_status() : SellerStatus {
public function seller_status(): SellerStatus {
$url = trailingslashit( $this->host ) . 'v1/customer/partners/' . $this->partner_id . '/merchant-integrations/' . $this->merchant_id;
$bearer = $this->bearer->bearer();
$args = array(

View file

@ -203,7 +203,7 @@ class PaymentsEndpoint {
* @throws RuntimeException If the request fails.
* @throws PayPalApiException If the request fails.
*/
public function reauthorize( string $authorization_id, ?Money $amount = null ) : string {
public function reauthorize( string $authorization_id, ?Money $amount = null ): string {
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v2/payments/authorizations/' . $authorization_id . '/reauthorize';
@ -249,7 +249,7 @@ class PaymentsEndpoint {
* @throws RuntimeException If the request fails.
* @throws PayPalApiException If the request fails.
*/
public function refund( RefundCapture $refund ) : string {
public function refund( RefundCapture $refund ): string {
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v2/payments/captures/' . $refund->for_capture()->id() . '/refund';
$args = array(
@ -289,7 +289,7 @@ class PaymentsEndpoint {
* @throws RuntimeException If the request fails.
* @throws PayPalApiException If the request fails.
*/
public function void( Authorization $authorization ) : void {
public function void( Authorization $authorization ): void {
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v2/payments/authorizations/' . $authorization->id() . '/void';
$args = array(

View file

@ -348,7 +348,7 @@ class WebhookEndpoint {
if ( isset( $expected_headers[ $key ] ) ) {
$expected_headers[ $key ] = $header;
}
};
}
foreach ( $expected_headers as $key => $value ) {
if ( ! empty( $value ) ) {

View file

@ -86,7 +86,7 @@ class Authorization {
*
* @return FraudProcessorResponse|null
*/
public function fraud_processor_response() : ?FraudProcessorResponse {
public function fraud_processor_response(): ?FraudProcessorResponse {
return $this->fraud_processor_response;
}

View file

@ -120,7 +120,7 @@ class Capture {
*
* @return string
*/
public function id() : string {
public function id(): string {
return $this->id;
}
@ -129,7 +129,7 @@ class Capture {
*
* @return CaptureStatus
*/
public function status() : CaptureStatus {
public function status(): CaptureStatus {
return $this->status;
}
@ -138,7 +138,7 @@ class Capture {
*
* @return Amount
*/
public function amount() : Amount {
public function amount(): Amount {
return $this->amount;
}
@ -147,7 +147,7 @@ class Capture {
*
* @return bool
*/
public function final_capture() : bool {
public function final_capture(): bool {
return $this->final_capture;
}
@ -165,7 +165,7 @@ class Capture {
*
* @return string
*/
public function invoice_id() : string {
public function invoice_id(): string {
return $this->invoice_id;
}
@ -174,7 +174,7 @@ class Capture {
*
* @return string
*/
public function custom_id() : string {
public function custom_id(): string {
return $this->custom_id;
}
@ -183,7 +183,7 @@ class Capture {
*
* @return SellerReceivableBreakdown|null
*/
public function seller_receivable_breakdown() : ?SellerReceivableBreakdown {
public function seller_receivable_breakdown(): ?SellerReceivableBreakdown {
return $this->seller_receivable_breakdown;
}
@ -192,7 +192,7 @@ class Capture {
*
* @return FraudProcessorResponse|null
*/
public function fraud_processor_response() : ?FraudProcessorResponse {
public function fraud_processor_response(): ?FraudProcessorResponse {
return $this->fraud_processor_response;
}
@ -201,7 +201,7 @@ class Capture {
*
* @return array
*/
public function to_array() : array {
public function to_array(): array {
$data = array(
'id' => $this->id(),
'status' => $this->status()->name(),

View file

@ -204,7 +204,7 @@ class Item {
*
* @return string
*/
public function url():string {
public function url(): string {
return $this->url;
}
@ -213,7 +213,7 @@ class Item {
*
* @return string
*/
public function image_url():string {
public function image_url(): string {
return $this->validate_image_url() ? $this->image_url : '';
}
@ -222,7 +222,7 @@ class Item {
*
* @return float
*/
public function tax_rate():float {
public function tax_rate(): float {
return round( (float) $this->tax_rate, 2 );
}
@ -231,7 +231,7 @@ class Item {
*
* @return string|null
*/
public function cart_item_key():?string {
public function cart_item_key(): ?string {
return $this->cart_item_key;
}

View file

@ -104,7 +104,7 @@ class PaymentPreferences {
*
* @return array
*/
public function to_array():array {
public function to_array(): array {
return array(
'setup_fee' => $this->setup_fee(),
'auto_bill_outstanding' => $this->auto_bill_outstanding(),

View file

@ -124,5 +124,4 @@ class PaymentToken {
)
);
}
}

View file

@ -141,7 +141,7 @@ class Plan {
*
* @return array
*/
public function to_array():array {
public function to_array(): array {
return array(
'id' => $this->id(),
'name' => $this->name(),

View file

@ -119,7 +119,7 @@ class Refund {
*
* @return string
*/
public function id() : string {
public function id(): string {
return $this->id;
}
@ -128,7 +128,7 @@ class Refund {
*
* @return RefundStatus
*/
public function status() : RefundStatus {
public function status(): RefundStatus {
return $this->status;
}
@ -137,7 +137,7 @@ class Refund {
*
* @return Amount
*/
public function amount() : Amount {
public function amount(): Amount {
return $this->amount;
}
@ -146,7 +146,7 @@ class Refund {
*
* @return string
*/
public function invoice_id() : string {
public function invoice_id(): string {
return $this->invoice_id;
}
@ -155,7 +155,7 @@ class Refund {
*
* @return string
*/
public function custom_id() : string {
public function custom_id(): string {
return $this->custom_id;
}
@ -164,7 +164,7 @@ class Refund {
*
* @return SellerPayableBreakdown|null
*/
public function seller_payable_breakdown() : ?SellerPayableBreakdown {
public function seller_payable_breakdown(): ?SellerPayableBreakdown {
return $this->seller_payable_breakdown;
}
@ -173,7 +173,7 @@ class Refund {
*
* @return string
*/
public function acquirer_reference_number() : string {
public function acquirer_reference_number(): string {
return $this->acquirer_reference_number;
}
@ -182,7 +182,7 @@ class Refund {
*
* @return string
*/
public function note_to_payer() : string {
public function note_to_payer(): string {
return $this->note_to_payer;
}
@ -191,7 +191,7 @@ class Refund {
*
* @return RefundPayer|null
*/
public function payer() : ?RefundPayer {
public function payer(): ?RefundPayer {
return $this->payer;
}
@ -200,7 +200,7 @@ class Refund {
*
* @return array
*/
public function to_array() : array {
public function to_array(): array {
$data = array(
'id' => $this->id(),
'status' => $this->status()->name(),

View file

@ -67,7 +67,7 @@ class RefundCapture {
*
* @return Capture
*/
public function for_capture() : Capture {
public function for_capture(): Capture {
return $this->capture;
}
@ -76,7 +76,7 @@ class RefundCapture {
*
* @return string
*/
public function invoice_id() : string {
public function invoice_id(): string {
return $this->invoice_id;
}
@ -85,7 +85,7 @@ class RefundCapture {
*
* @return string
*/
public function note_to_payer() : string {
public function note_to_payer(): string {
return $this->note_to_payer;
}
@ -103,7 +103,7 @@ class RefundCapture {
*
* @return array
*/
public function to_array() : array {
public function to_array(): array {
$data = array(
'invoice_id' => $this->invoice_id(),
);

View file

@ -68,7 +68,7 @@ class SellerStatus {
*
* @return SellerStatusProduct[]
*/
public function products() : array {
public function products(): array {
return $this->products;
}
@ -77,7 +77,7 @@ class SellerStatus {
*
* @return SellerStatusCapability[]
*/
public function capabilities() : array {
public function capabilities(): array {
return $this->capabilities;
}
@ -86,7 +86,7 @@ class SellerStatus {
*
* @return string
*/
public function country() : string {
public function country(): string {
return $this->country;
}
@ -95,16 +95,16 @@ class SellerStatus {
*
* @return array
*/
public function to_array() : array {
public function to_array(): array {
$products = array_map(
function( SellerStatusProduct $product ) : array {
function ( SellerStatusProduct $product ): array {
return $product->to_array();
},
$this->products()
);
$capabilities = array_map(
function( SellerStatusCapability $capability ) : array {
function ( SellerStatusCapability $capability ): array {
return $capability->to_array();
},
$this->capabilities()

View file

@ -49,7 +49,7 @@ class SellerStatusCapability {
*
* @return string
*/
public function name() : string {
public function name(): string {
return $this->name;
}
@ -58,7 +58,7 @@ class SellerStatusCapability {
*
* @return string
*/
public function status() : string {
public function status(): string {
return $this->status;
}
@ -67,11 +67,10 @@ class SellerStatusCapability {
*
* @return array
*/
public function to_array() : array {
public function to_array(): array {
return array(
'name' => $this->name(),
'status' => $this->status(),
);
}
}

View file

@ -69,7 +69,7 @@ class SellerStatusProduct {
*
* @return string
*/
public function name() : string {
public function name(): string {
return $this->name;
}
@ -78,7 +78,7 @@ class SellerStatusProduct {
*
* @return string
*/
public function vetting_status() : string {
public function vetting_status(): string {
return $this->vetting_status;
}
@ -87,7 +87,7 @@ class SellerStatusProduct {
*
* @return string[]
*/
public function capabilities() : array {
public function capabilities(): array {
return $this->capabilities;
}
@ -96,13 +96,11 @@ class SellerStatusProduct {
*
* @return array
*/
public function to_array() : array {
public function to_array(): array {
return array(
'name' => $this->name(),
'vetting_status' => $this->vetting_status(),
'capabilities' => $this->capabilities(),
);
}
}

View file

@ -17,46 +17,46 @@ class Shipping {
/**
* The name.
*
* @var string
* @var string|null
*/
private $name;
private ?string $name;
/**
* The address.
*
* @var Address
* @var Address|null
*/
private $address;
private ?Address $address;
/**
* Custom contact email address, usually added via the Contact Module.
*/
private ?string $email_address = null;
private ?string $email_address;
/**
* Custom contact phone number, usually added via the Contact Module.
*/
private ?Phone $phone_number = null;
private ?Phone $phone_number;
/**
* Shipping methods.
*
* @var ShippingOption[]
*/
private $options;
private array $options;
/**
* Shipping constructor.
*
* @param string $name The name.
* @param Address $address The address.
* @param string|null $name The name.
* @param Address|null $address The address.
* @param string|null $email_address Contact email.
* @param Phone|null $phone_number Contact phone.
* @param ShippingOption[] $options Shipping methods.
*/
public function __construct(
string $name,
Address $address,
?string $name = null,
?Address $address = null,
?string $email_address = null,
?Phone $phone_number = null,
array $options = array()
@ -71,18 +71,18 @@ class Shipping {
/**
* Returns the name.
*
* @return string
* @return null|string
*/
public function name(): string {
public function name(): ?string {
return $this->name;
}
/**
* Returns the shipping address.
*
* @return Address
* @return null|Address
*/
public function address(): Address {
public function address(): ?Address {
return $this->address;
}
@ -91,7 +91,7 @@ class Shipping {
*
* @return null|string
*/
public function email_address() : ?string {
public function email_address(): ?string {
return $this->email_address;
}
@ -100,7 +100,7 @@ class Shipping {
*
* @return null|Phone
*/
public function phone_number() : ?Phone {
public function phone_number(): ?Phone {
return $this->phone_number;
}
@ -119,19 +119,26 @@ class Shipping {
* @return array
*/
public function to_array(): array {
$result = array(
'name' => array(
'full_name' => $this->name(),
),
'address' => $this->address()->to_array(),
);
$result = array();
$name = $this->name();
if ( $name ) {
$result['name'] = array(
'full_name' => $name,
);
}
$address = $this->address();
if ( $address ) {
$result['address'] = $address->to_array();
}
$contact_email = $this->email_address();
$contact_phone = $this->phone_number();
if ( $contact_email ) {
$result['email_address'] = $contact_email;
}
$contact_phone = $this->phone_number();
if ( $contact_phone ) {
$result['phone_number'] = $contact_phone->to_array();
}
@ -144,6 +151,7 @@ class Shipping {
$this->options
);
}
return $result;
}
}

View file

@ -66,7 +66,7 @@ class CaptureFactory {
* @return Capture
* @throws RuntimeException When capture amount data is invalid.
*/
public function from_paypal_response( \stdClass $data ) : Capture {
public function from_paypal_response( \stdClass $data ): Capture {
$reason = $data->status_details->reason ?? null;
$seller_receivable_breakdown = isset( $data->seller_receivable_breakdown ) ?
$this->seller_receivable_breakdown_factory->from_paypal_response( $data->seller_receivable_breakdown )

View file

@ -48,7 +48,7 @@ class ContactPreferenceFactory {
* @param string $payment_source_key Name of the payment_source.
* @return string|null
*/
public function from_state( string $payment_source_key ) : ?string {
public function from_state( string $payment_source_key ): ?string {
$payment_sources_with_contact = array( 'paypal', 'venmo' );
/**

View file

@ -214,7 +214,7 @@ class ExperienceContextBuilder {
*
* @param string|null $preference The new preference to apply.
*/
public function with_contact_preference( ?string $preference = null ) : ExperienceContextBuilder {
public function with_contact_preference( ?string $preference = null ): ExperienceContextBuilder {
$builder = clone $this;
$builder->experience_context = $builder->experience_context

View file

@ -53,7 +53,7 @@ class PatchCollectionFactory {
$from,
static function ( PurchaseUnit $unit ) use ( $purchase_unit_to ): bool {
// Loose comparison needed to compare two objects.
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
// phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
return $unit == $purchase_unit_to;
}
)

View file

@ -41,7 +41,7 @@ class PaymentPreferencesFactory {
* @param WC_Product $product WC product.
* @return PaymentPreferences
*/
public function from_wc_product( WC_Product $product ):PaymentPreferences {
public function from_wc_product( WC_Product $product ): PaymentPreferences {
return new PaymentPreferences(
array(
'value' => $product->get_meta( '_subscription_sign_up_fee' ) ?: '0',

View file

@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
use WooCommerce\PayPalCommerce\Webhooks\CustomIds;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Address;
/**
* Class PurchaseUnitFactory
@ -108,21 +109,20 @@ class PurchaseUnitFactory {
* @return PurchaseUnit
*/
public function from_wc_order( \WC_Order $order ): PurchaseUnit {
$amount = $this->amount_factory->from_wc_order( $order );
$items = array_filter(
$amount = $this->amount_factory->from_wc_order( $order );
$items = array_filter(
$this->item_factory->from_wc_order( $order ),
function ( Item $item ): bool {
return $item->unit_amount()->value() >= 0;
}
);
$shipping = $this->shipping_factory->from_wc_order( $order );
if (
! $this->shipping_needed( ... array_values( $items ) ) ||
empty( $shipping->address()->country_code() ) ||
( ! $shipping->address()->postal_code() && ! $this->country_without_postal_code( $shipping->address()->country_code() ) )
) {
$shipping = $this->shipping_factory->from_wc_order( $order );
$shipping_address = $shipping->address();
if ( $this->should_disable_shipping( $items, $shipping_address ) ) {
$shipping = null;
}
$reference_id = 'default';
$description = '';
$custom_id = (string) $order->get_id();
@ -175,11 +175,14 @@ class PurchaseUnitFactory {
$shipping = null;
$customer = \WC()->customer;
if ( $this->shipping_needed( ... array_values( $items ) ) && is_a( $customer, \WC_Customer::class ) ) {
$shipping = $this->shipping_factory->from_wc_customer( \WC()->customer, $with_shipping_options );
/** @psalm-suppress RedundantConditionGivenDocblockType False positive. Ignored because $customer can be null as well. */
if ( $this->shipping_needed( ...array_values( $items ) ) && is_a( $customer, \WC_Customer::class ) ) {
$shipping = $this->shipping_factory->from_wc_customer( \WC()->customer, $with_shipping_options );
$shipping_address = $shipping->address();
if (
2 !== strlen( $shipping->address()->country_code() ) ||
( ! $shipping->address()->postal_code() && ! $this->country_without_postal_code( $shipping->address()->country_code() ) )
! $shipping_address ||
2 !== strlen( $shipping_address->country_code() ) ||
( ! $shipping_address->postal_code() && ! $this->country_without_postal_code( $shipping_address->country_code() ) )
) {
$shipping = null;
}
@ -248,11 +251,11 @@ class PurchaseUnitFactory {
}
$shipping = null;
try {
if ( isset( $data->shipping ) ) {
if ( isset( $data->shipping ) && ! empty( (array) $data->shipping ) ) {
$shipping = $this->shipping_factory->from_paypal_response( $data->shipping );
}
} catch ( RuntimeException $error ) {
;
$shipping = null;
}
$payments = null;
try {
@ -260,7 +263,7 @@ class PurchaseUnitFactory {
$payments = $this->payments_factory->from_paypal_response( $data->payments );
}
} catch ( RuntimeException $error ) {
;
$payments = null;
}
$purchase_unit = new PurchaseUnit(
@ -331,10 +334,25 @@ class PurchaseUnitFactory {
*
* @return string The sanitized soft descriptor.
*/
private function sanitize_soft_descriptor( string $soft_descriptor ) : string {
private function sanitize_soft_descriptor( string $soft_descriptor ): string {
$decoded = html_entity_decode( $soft_descriptor, ENT_QUOTES, 'UTF-8' );
$sanitized = preg_replace( '/[^a-zA-Z0-9 *\-.]/', '', $decoded ) ?: '';
return substr( $sanitized, 0, 22 ) ?: '';
}
/**
* Determines whether shipping should be disabled for a purchase unit.
*
* @param array $items Purchase unit items.
* @param Address|null $shipping_address The shipping address to validate.
*
* @return bool
*/
private function should_disable_shipping( array $items, ?Address $shipping_address ): bool {
return ! $this->shipping_needed( ...array_values( $items ) ) ||
! $shipping_address ||
empty( $shipping_address->country_code() ) ||
( ! $shipping_address->postal_code() && ! $this->country_without_postal_code( $shipping_address->country_code() ) );
}
}

View file

@ -65,7 +65,7 @@ class RefundFactory {
* @return Refund
* @throws RuntimeException When refund amount data is invalid.
*/
public function from_paypal_response( \stdClass $data ) : Refund {
public function from_paypal_response( \stdClass $data ): Refund {
$reason = $data->status_details->reason ?? null;
$seller_payable_breakdown = isset( $data->seller_payable_breakdown ) ?
$this->seller_payable_breakdown_factory->from_paypal_response( $data->seller_payable_breakdown )

View file

@ -25,9 +25,9 @@ class SellerStatusFactory {
*
* @return SellerStatus
*/
public function from_paypal_response( \stdClass $json ) : SellerStatus {
public function from_paypal_response( \stdClass $json ): SellerStatus {
$products = array_map(
function( $json ) : SellerStatusProduct {
function ( $json ): SellerStatusProduct {
$product = new SellerStatusProduct(
isset( $json->name ) ? (string) $json->name : '',
isset( $json->vetting_status ) ? (string) $json->vetting_status : '',
@ -39,7 +39,7 @@ class SellerStatusFactory {
);
$capabilities = array_map(
function( $json ) : SellerStatusCapability {
function ( $json ): SellerStatusCapability {
$capability = new SellerStatusCapability(
isset( $json->name ) ? (string) $json->name : '',
isset( $json->status ) ? (string) $json->status : ''

View file

@ -96,30 +96,25 @@ class ShippingFactory {
* @throws RuntimeException When JSON object is malformed.
*/
public function from_paypal_response( \stdClass $data ): Shipping {
if ( ! isset( $data->name->full_name ) ) {
throw new RuntimeException( 'No name was given for shipping.' );
}
if ( ! isset( $data->address ) ) {
throw new RuntimeException( 'No address was given for shipping.' );
}
$contact_phone = null;
$contact_email = null;
$address = $this->address_factory->from_paypal_response( $data->address );
$name = $data->name->full_name ?? null;
$address = isset( $data->address )
? $this->address_factory->from_paypal_response( $data->address )
: null;
$contact_email = $data->email_address ?? null;
$contact_phone = isset( $data->phone_number->national_number )
? new Phone( $data->phone_number->national_number )
: null;
$options = array_map(
array( $this->shipping_option_factory, 'from_paypal_response' ),
$data->options ?? array()
);
if ( isset( $data->phone_number->national_number ) ) {
$contact_phone = new Phone( $data->phone_number->national_number );
}
if ( isset( $data->email_address ) ) {
$contact_email = $data->email_address;
}
return new Shipping(
$data->name->full_name,
$name,
$address,
$contact_email,
$contact_phone,

View file

@ -48,7 +48,7 @@ class Cache {
*
* @return bool
*/
public function has( string $key ) : bool {
public function has( string $key ): bool {
$value = $this->get( $key );
return false !== $value;
@ -59,7 +59,7 @@ class Cache {
*
* @param string $key The key.
*/
public function delete( string $key ) : void {
public function delete( string $key ): void {
delete_transient( $this->prefix . $key );
}
@ -72,7 +72,7 @@ class Cache {
*
* @return bool
*/
public function set( string $key, $value, int $expiration = 0 ) : bool {
public function set( string $key, $value, int $expiration = 0 ): bool {
return (bool) set_transient( $this->prefix . $key, $value, $expiration );
}
@ -81,7 +81,7 @@ class Cache {
*
* @return void
*/
public function flush() : void {
public function flush(): void {
global $wpdb;
// Get a list of all transients with the relevant "group prefix" from the DB.

View file

@ -94,7 +94,7 @@ class DccApplies {
*
* @return array
*/
public function valid_cards() : array {
public function valid_cards(): array {
$cards = array();
if ( ! isset( $this->country_card_matrix[ $this->country ] ) ) {
return $cards;
@ -122,7 +122,7 @@ class DccApplies {
*
* @return bool
*/
public function can_process_card( string $card ) : bool {
public function can_process_card( string $card ): bool {
if ( ! isset( $this->country_card_matrix[ $this->country ] ) ) {
return false;
}

View file

@ -90,5 +90,4 @@ class FailureRegistry {
private function cache_key( string $key ): string {
return implode( '_', array( self::CACHE_KEY, $key ) );
}
}

View file

@ -156,5 +156,4 @@ class OrderTransient {
}
return implode( '_', array( self::CACHE_KEY . $order->id() ) );
}
}

View file

@ -63,7 +63,7 @@ class PartnerAttribution {
* @param string $installation_path The installation path used to determine the BN Code.
* @param bool $force_update Whether to force an update of the BN code if it already exists.
*/
public function initialize_bn_code( string $installation_path, bool $force_update = false ) : void {
public function initialize_bn_code( string $installation_path, bool $force_update = false ): void {
$selected_bn_code = $this->bn_codes[ $installation_path ] ?? '';
if ( ! $selected_bn_code ) {
return;
@ -82,7 +82,7 @@ class PartnerAttribution {
*
* @return string The stored BN Code, or the default value if no path is detected.
*/
public function get_bn_code() : string {
public function get_bn_code(): string {
$bn_code = (string) ( get_option( $this->bn_code_option_name, $this->default_bn_code ) ?? $this->default_bn_code );
if ( ! in_array( $bn_code, $this->bn_codes, true ) ) {

View file

@ -93,7 +93,7 @@ abstract class ProductStatus {
* @throws RuntimeException When the check failed.
* @throws NotFoundException When a relevant service or setting was not found.
*/
abstract protected function check_local_state() : ?bool;
abstract protected function check_local_state(): ?bool;
/**
* Inspects the API response of the SellerStatus to determine feature eligibility.
@ -105,7 +105,7 @@ abstract class ProductStatus {
* @return bool
* @throws RuntimeException When the check failed.
*/
abstract protected function check_active_state( SellerStatus $seller_status ) : bool;
abstract protected function check_active_state( SellerStatus $seller_status ): bool;
/**
* Clears the eligibility status from the local cache/DB to enforce a new
@ -114,14 +114,14 @@ abstract class ProductStatus {
* @param Settings|null $settings See description in {@see self::clear()}.
* @return void
*/
abstract protected function clear_state( ?Settings $settings = null ) : void;
abstract protected function clear_state( ?Settings $settings = null ): void;
/**
* Whether the merchant has access to the feature.
*
* @return bool
*/
public function is_active() : bool {
public function is_active(): bool {
if ( null !== $this->is_eligible ) {
return $this->is_eligible;
}
@ -158,7 +158,7 @@ abstract class ProductStatus {
* @return SellerStatus
* @throws RuntimeException When the check failed.
*/
protected function get_seller_status_object() : SellerStatus {
protected function get_seller_status_object(): SellerStatus {
if ( null === self::$seller_status ) {
// Check API failure registry to prevent multiple failed API requests.
if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, MINUTE_IN_SECONDS ) ) {
@ -177,7 +177,7 @@ abstract class ProductStatus {
*
* @return bool True, if we can use the merchant API endpoints.
*/
public function is_onboarded() : bool {
public function is_onboarded(): bool {
return $this->is_connected;
}
@ -186,7 +186,7 @@ abstract class ProductStatus {
*
* @return bool
*/
public function has_request_failure() : bool {
public function has_request_failure(): bool {
return $this->has_request_failure;
}
@ -199,7 +199,7 @@ abstract class ProductStatus {
* @param Settings|null $settings The settings object.
* @return void
*/
public function clear( ?Settings $settings = null ) : void {
public function clear( ?Settings $settings = null ): void {
$this->is_eligible = null;
$this->has_request_failure = false;

View file

@ -393,5 +393,4 @@ class PurchaseUnitSanitizer {
public function set_last_message( string $message ): void {
$this->last_message = $message;
}
}

View file

@ -40,7 +40,7 @@ class PartnerReferralsData {
*
* @return string
*/
public function nonce() : string {
public function nonce(): string {
return 'a1233wtergfsdt4365tzrshgfbaewa36AGa1233wtergfsdt4365tzrshgfbaewa36AG';
}
@ -59,7 +59,7 @@ class PartnerReferralsData {
string $onboarding_token = '',
?bool $use_subscriptions = null,
bool $use_card_payments = true
) : array {
): array {
$in_acdc_country = $this->dcc_applies->for_country_currency();
if ( ! $products ) {

View file

@ -334,12 +334,22 @@ class ApplePayButton extends PaymentButton {
this.checkEligibility();
}
reinit() {
async reinit() {
// Missing (invalid) configuration indicates, that the first `init()` call did not happen yet.
if ( ! this.validateConfiguration( true ) ) {
return;
}
// Ensures transaction info is updated when cart or checkout update events are triggered.
await this.contextHandler
.transactionInfo()
.then( ( transactionInfo ) => {
this.transactionInfo = transactionInfo;
} )
.catch( ( error ) => {
console.error( 'Failed to get transaction info:', error );
} );
super.reinit();
this.init();

View file

@ -94,20 +94,23 @@ if (
features.push( 'subscriptions' );
}
registerExpressPaymentMethod( {
name: buttonData.id,
title: `PayPal - ${ buttonData.title }`,
description: __(
'Eligible users will see the PayPal button.',
'woocommerce-paypal-payments'
),
label: <div dangerouslySetInnerHTML={ { __html: buttonData.title } } />,
content: <ApplePayComponent isEditing={ false } />,
edit: <ApplePayComponent isEditing={ true } />,
ariaLabel: buttonData.title,
canMakePayment: () => buttonData.enabled,
supports: {
features,
style: [ 'height', 'borderRadius' ],
},
} );
if ( buttonConfig?.is_enabled ) {
registerExpressPaymentMethod( {
name: buttonData.id,
title: `PayPal - ${ buttonData.title }`,
description: __(
'Eligible users will see the PayPal button.',
'woocommerce-paypal-payments'
),
label: <div dangerouslySetInnerHTML={ { __html: buttonData.title } } />,
content: <ApplePayComponent isEditing={ false } />,
edit: <ApplePayComponent isEditing={ true } />,
ariaLabel: buttonData.title,
canMakePayment: () =>
buttonData.enabled && window.ApplePaySession?.canMakePayments(),
supports: {
features,
style: [ 'height', 'borderRadius' ],
},
} );
}

View file

@ -33,11 +33,11 @@ return array(
$apm_applies = $container->get( 'applepay.helpers.apm-applies' );
assert( $apm_applies instanceof ApmApplies );
return static function () use ( $apm_applies ) : bool {
return static function () use ( $apm_applies ): bool {
return $apm_applies->for_country() && $apm_applies->for_currency() && $apm_applies->for_merchant();
};
},
'applepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies {
'applepay.helpers.apm-applies' => static function ( ContainerInterface $container ): ApmApplies {
return new ApmApplies(
$container->get( 'applepay.supported-countries' ),
$container->get( 'applepay.supported-currencies' ),
@ -45,7 +45,7 @@ return array(
$container->get( 'api.shop.country' )
);
},
'applepay.status-cache' => static function( ContainerInterface $container ): Cache {
'applepay.status-cache' => static function ( ContainerInterface $container ): Cache {
return new Cache( 'ppcp-paypal-apple-status-cache' );
},
@ -82,7 +82,7 @@ return array(
},
'applepay.apple-product-status' => SingletonDecorator::make(
static function( ContainerInterface $container ): AppleProductStatus {
static function ( ContainerInterface $container ): AppleProductStatus {
return new AppleProductStatus(
$container->get( 'wcgateway.settings' ),
$container->get( 'api.endpoint.partners' ),
@ -136,14 +136,7 @@ return array(
return false;
},
'applepay.url' => static function ( ContainerInterface $container ): string {
$path = realpath( __FILE__ );
if ( false === $path ) {
return '';
}
return plugins_url(
'/modules/ppcp-applepay/',
dirname( $path, 3 ) . '/woocommerce-paypal-payments.php'
);
return plugins_url( '/modules/ppcp-applepay/', $container->get( 'ppcp.path-to-plugin-main-file' ) );
},
'applepay.sdk_script_url' => static function ( ContainerInterface $container ): string {
return 'https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js';
@ -176,7 +169,7 @@ return array(
/**
* The list of which countries can be used for ApplePay.
*/
'applepay.supported-countries' => static function ( ContainerInterface $container ) : array {
'applepay.supported-countries' => static function ( ContainerInterface $container ): array {
/**
* Returns which countries can be used for ApplePay.
*/
@ -232,7 +225,7 @@ return array(
/**
* The list of which currencies can be used for ApplePay.
*/
'applepay.supported-currencies' => static function ( ContainerInterface $container ) : array {
'applepay.supported-currencies' => static function ( ContainerInterface $container ): array {
/**
* Returns which currencies can be used for ApplePay.
*/

View file

@ -171,7 +171,7 @@ class ApplePayGateway extends WC_Payment_Gateway {
*
* @return array
*/
public function process_payment( $order_id ) : array {
public function process_payment( $order_id ): array {
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return $this->handle_payment_failure(
@ -234,7 +234,7 @@ class ApplePayGateway extends WC_Payment_Gateway {
*
* @return boolean True or false based on success, or a WP_Error object.
*/
public function process_refund( $order_id, $amount = null, $reason = '' ) : bool {
public function process_refund( $order_id, $amount = null, $reason = '' ): bool {
$order = wc_get_order( $order_id );
if ( ! is_a( $order, WC_Order::class ) ) {
return false;
@ -250,7 +250,7 @@ class ApplePayGateway extends WC_Payment_Gateway {
*
* @return string
*/
public function get_transaction_url( $order ) : string {
public function get_transaction_url( $order ): string {
$this->view_transaction_url = $this->transaction_url_provider->get_transaction_url_base( $order );
return parent::get_transaction_url( $order );

View file

@ -18,6 +18,7 @@ use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice;
use WooCommerce\PayPalCommerce\Settings\SettingsModule;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
@ -55,7 +56,7 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
// Clears product status when appropriate.
add_action(
'woocommerce_paypal_payments_clear_apm_product_status',
function( ?Settings $settings = null ) use ( $c ): void {
function ( ?Settings $settings = null ) use ( $c ): void {
$apm_status = $c->get( 'applepay.apple-product-status' );
assert( $apm_status instanceof AppleProductStatus );
$apm_status->clear( $settings );
@ -93,15 +94,17 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
return;
}
if ( $apple_payment_method->is_enabled() ) {
$module->load_assets( $c, $apple_payment_method );
$module->handle_validation_file( $c, $apple_payment_method );
$module->render_buttons( $c, $apple_payment_method );
$apple_payment_method->bootstrap_ajax_request();
}
$module->load_admin_assets( $c, $apple_payment_method );
$module->load_block_editor_assets( $c, $apple_payment_method );
if ( SettingsModule::should_use_the_old_ui() && ! $apple_payment_method->is_enabled() ) {
return;
}
$module->load_assets( $c, $apple_payment_method );
$module->handle_validation_file( $c, $apple_payment_method );
$module->render_buttons( $c, $apple_payment_method );
$apple_payment_method->bootstrap_ajax_request();
},
1
);
@ -171,7 +174,7 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
add_filter(
'woocommerce_paypal_payments_selected_button_locations',
function( array $locations, string $setting_name ): array {
function ( array $locations, string $setting_name ): array {
$gateway = WC()->payment_gateways()->payment_gateways()[ ApplePayGateway::ID ] ?? '';
if ( $gateway && $gateway->enabled === 'yes' && $setting_name === 'smart_button_locations' ) {
$locations[] = 'checkout';
@ -185,7 +188,7 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
add_filter(
'woocommerce_paypal_payments_rest_common_merchant_features',
function( array $features ) use ( $c ): array {
function ( array $features ) use ( $c ): array {
$product_status = $c->get( 'applepay.apple-product-status' );
assert( $product_status instanceof AppleProductStatus );
@ -201,7 +204,7 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
add_filter(
'ppcp_create_order_request_body_data',
static function ( array $data, string $payment_method, array $request ) use ( $c ) : array {
static function ( array $data, string $payment_method, array $request ) use ( $c ): array {
if ( $payment_method !== ApplePayGateway::ID ) {
return $data;
@ -255,13 +258,13 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
* @return void
*/
public function load_assets( ContainerInterface $c, ApplePayButton $button ): void {
if ( ! $button->is_enabled() ) {
return;
}
add_action(
'wp_enqueue_scripts',
function () use ( $c, $button ) {
if ( ! $button->is_enabled() ) {
return;
}
$smart_button = $c->get( 'button.smart-button' );
assert( $smart_button instanceof SmartButtonInterface );
if ( $smart_button->should_load_ppcp_script() ) {
@ -282,13 +285,16 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
add_action(
'enqueue_block_editor_assets',
function () use ( $c, $button ) {
if ( ! $button->is_enabled() ) {
return;
}
$button->enqueue_admin_styles();
}
);
add_action(
'woocommerce_blocks_payment_method_type_registration',
function( PaymentMethodRegistry $payment_method_registry ) use ( $c ): void {
function ( PaymentMethodRegistry $payment_method_registry ) use ( $c ): void {
$payment_method_registry->register( $c->get( 'applepay.blocks-payment-method' ) );
}
);
@ -323,7 +329,7 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
// Adds ApplePay component to the backend button preview settings.
add_action(
'woocommerce_paypal_payments_admin_gateway_settings',
function( array $settings ) use ( $c ): array {
function ( array $settings ) use ( $c ): array {
if ( is_array( $settings['components'] ) ) {
$settings['components'][] = 'applepay';
}
@ -362,9 +368,6 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
* @return void
*/
public function render_buttons( ContainerInterface $c, ApplePayButton $button ): void {
if ( ! $button->is_enabled() ) {
return;
}
add_action(
'wp',
@ -372,8 +375,13 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
if ( is_admin() ) {
return;
}
$button = $c->get( 'applepay.button' );
if ( ! $button->is_enabled() ) {
return;
}
/**
* The Button.
*
@ -392,9 +400,6 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
* @return void
*/
public function handle_validation_file( ContainerInterface $c, ApplePayButton $button ): void {
if ( ! $button->is_enabled() ) {
return;
}
$env = $c->get( 'settings.environment' );
assert( $env instanceof Environment );
$is_sandbox = $env->current_environment_is( Environment::SANDBOX );
@ -407,7 +412,7 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
* @param bool $is_sandbox The environment for this merchant.
* @return string
*/
public function validation_string( bool $is_sandbox ) : string {
public function validation_string( bool $is_sandbox ): string {
$sandbox_string = $this->sandbox_validation_string();
$live_string = $this->live_validation_string();
return $is_sandbox ? $sandbox_string : $live_string;

View file

@ -26,7 +26,8 @@ use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
* Class ApplePayButton
*/
class ApplePayButton implements ButtonInterface {
use RequestHandlerTrait, ContextTrait;
use RequestHandlerTrait;
use ContextTrait;
/**
* The settings.
@ -222,7 +223,6 @@ class ApplePayButton implements ButtonInterface {
return $options . '<li><label><input type="checkbox" id="ppcp-onboarding-apple" ' . $checked . ' data-onboarding-option="ppcp-onboarding-apple"> ' .
__( 'Onboard with ApplePay', 'woocommerce-paypal-payments' ) . '
</label></li>';
}
/**
@ -445,7 +445,7 @@ class ApplePayButton implements ButtonInterface {
} else {
add_filter(
'woocommerce_payment_successful_result',
function ( array $result ) use ( $cart, $cart_item_key ) : array {
function ( array $result ) use ( $cart, $cart_item_key ): array {
$this->clear_current_cart( $cart, $cart_item_key );
$this->reload_cart( $cart );
return $result;
@ -917,7 +917,7 @@ class ApplePayButton implements ButtonInterface {
add_filter(
'woocommerce_paypal_payments_sdk_components_hook',
function( array $components ) {
function ( array $components ) {
$components[] = 'applepay';
return $components;
}

View file

@ -361,7 +361,6 @@ class ApplePayDataObjectHttp {
}
$this->$key = $value;
}
}
/**
@ -509,7 +508,7 @@ class ApplePayDataObjectHttp {
* @param array $data The data.
* @return void
*/
protected function update_simplified_contact( array $data ) : void {
protected function update_simplified_contact( array $data ): void {
$simplified_contact_info = array_map( 'sanitize_text_field', $data );
$this->simplified_contact = $this->simplified_address(
$simplified_contact_info
@ -727,7 +726,7 @@ class ApplePayDataObjectHttp {
*
* @return bool
*/
public function is_nonce_valid():bool {
public function is_nonce_valid(): bool {
$nonce = filter_input( INPUT_POST, 'woocommerce-process-checkout-nonce', FILTER_SANITIZE_SPECIAL_CHARS );
if ( ! $nonce ) {
return false;

View file

@ -54,7 +54,7 @@ class AppleProductStatus extends ProductStatus {
}
/** {@inheritDoc} */
protected function check_local_state() : ?bool {
protected function check_local_state(): ?bool {
$status_override = apply_filters( 'woocommerce_paypal_payments_apple_pay_product_status', null );
if ( null !== $status_override ) {
return $status_override;
@ -68,7 +68,7 @@ class AppleProductStatus extends ProductStatus {
}
/** {@inheritDoc} */
protected function check_active_state( SellerStatus $seller_status ) : bool {
protected function check_active_state( SellerStatus $seller_status ): bool {
// Check the seller status for the intended capability.
$has_capability = false;
foreach ( $seller_status->products() as $product ) {
@ -101,7 +101,7 @@ class AppleProductStatus extends ProductStatus {
}
/** {@inheritDoc} */
protected function clear_state( ?Settings $settings = null ) : void {
protected function clear_state( ?Settings $settings = null ): void {
if ( null === $settings ) {
$settings = $this->settings;
}

View file

@ -49,7 +49,7 @@ class DataToAppleButtonScripts {
*
* @return array
*/
public function apple_pay_script_data() : array {
public function apple_pay_script_data(): array {
if ( is_product() ) {
return $this->data_for_product_page();
}
@ -62,7 +62,7 @@ class DataToAppleButtonScripts {
*
* @return array
*/
public function apple_pay_script_data_for_admin() : array {
public function apple_pay_script_data_for_admin(): array {
return $this->data_for_admin_page();
}
@ -73,7 +73,7 @@ class DataToAppleButtonScripts {
*
* @return array
*/
private function get_apple_pay_data( array $product = array() ) : array {
private function get_apple_pay_data( array $product = array() ): array {
// true: Use Apple Pay as distinct gateway.
// false: integrate it with the smart buttons.
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
@ -132,7 +132,7 @@ class DataToAppleButtonScripts {
*
* @return bool
*/
protected function check_if_need_shipping( WC_Product $product ) : bool {
protected function check_if_need_shipping( WC_Product $product ): bool {
if (
! wc_shipping_enabled()
|| 0 === wc_get_shipping_method_count(
@ -154,7 +154,7 @@ class DataToAppleButtonScripts {
*
* @return array
*/
protected function data_for_product_page() : array {
protected function data_for_product_page(): array {
$product = wc_get_product( get_the_id() );
if ( ! $product ) {
return array();
@ -185,7 +185,7 @@ class DataToAppleButtonScripts {
*
* @return array
*/
protected function data_for_cart_page() : array {
protected function data_for_cart_page(): array {
$cart = WC()->cart;
if ( ! $cart ) {
return array();
@ -206,7 +206,7 @@ class DataToAppleButtonScripts {
*
* @return array
*/
protected function data_for_admin_page() : array {
protected function data_for_admin_page(): array {
$data = $this->get_apple_pay_data(
array(
'needShipping' => false,

View file

@ -89,7 +89,7 @@ class ApmApplies {
*
* @return bool
*/
public function for_merchant() : bool {
public function for_merchant(): bool {
return apply_filters(
'woocommerce_paypal_payments_is_eligible_for_applepay',
true

View file

@ -141,7 +141,6 @@ class AvailabilityNotice {
if ( ! $this->is_merchant_validated ) {
$this->add_merchant_not_validated_notice();
}
}
/**
@ -287,5 +286,4 @@ class AvailabilityNotice {
}
);
}
}

View file

@ -9,6 +9,6 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\AxoBlock;
return static function () : AxoBlockModule {
return static function (): AxoBlockModule {
return new AxoBlockModule();
};

View file

@ -42,40 +42,56 @@ $fast-transition-duration: 0.5s;
display: flex;
flex-direction: column;
align-items: center;
max-width: 300px;
max-width: 340px;
width: 100%;
}
&__content {
position: relative;
box-sizing: border-box;
aspect-ratio: 1.586;
display: flex;
flex-direction: column;
justify-content: space-between;
border: 1px solid $border-color;
font-size: 0.875em;
font-family: monospace;
padding: 1em;
margin: 1em 0;
border-radius: 4px;
border-radius: 10px;
width: 100%;
box-shadow: 0 3px 10px -3px rgba(0, 0, 0, .2666666667);
background-image: linear-gradient(60deg, rgba(0, 0, 0, 0.0666666667), rgba(204, 204, 204, 0.0666666667) 65%, rgba(255, 255, 255, 0.4) 68%, rgba(255, 255, 255, 0));
border: 2px solid #ccc;
background-color: #f6f6f6;
}
&__meta {
@include flex-space-between;
width: 100%;
text-align: left;
&-digits {
letter-spacing: 2px;
margin-top: 76px;
font-size: 24px;
text-shadow: 0 -1px 1px #fff, 0 1px 1px rgba(0, 0, 0, .2666666667);
color: #666;
text-align: center;
font-family: monospace;
}
&:last-child {
align-self: flex-end;
&-logo {
position: absolute;
right: 32px;
top: 32px;
height: 40px;
}
}
&__watermark {
align-self: flex-end;
&-expiry {
text-align: right;
font-size: 14px;
padding-right: 32px;
}
&-name {
text-transform: uppercase;
position: absolute;
left: 24px;
bottom: 20px;
line-height: 1em;
}
}
&__edit {

Some files were not shown because too many files have changed in this diff Show more