Abilities_Registrar.php
171 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Class Abilities_Registrar |
| 4 | * |
| 5 | * @package WooCommerce\Square |
| 6 | */ |
| 7 | |
| 8 | // @phan-file-suppress PhanUndeclaredFunction, PhanUndeclaredClassMethod @phan-suppress-current-line UnusedSuppression -- Abilities API ships with WooCommerce 10.9; the suppression covers static analysis runs on older WC versions where the wp_register_ability()/AbilitiesLoader symbols are not loaded. @todo Remove when Square for WooCommerce requires WooCommerce >= 10.9. |
| 9 | |
| 10 | namespace WooCommerce\Square\Internal\Abilities; |
| 11 | |
| 12 | defined( 'ABSPATH' ) || exit; |
| 13 | |
| 14 | /** |
| 15 | * Registers Square for WooCommerce abilities with the WordPress Abilities API. |
| 16 | * |
| 17 | * Thin coordinator: holds the ABILITY_CLASSES list and the |
| 18 | * can_manage_woocommerce_square() capability helper that mirrors the load-bearing |
| 19 | * read gate resolved by the plugin's REST controllers |
| 20 | * (WC_Square_REST_Base_Controller::check_permission()). |
| 21 | * |
| 22 | * Gated by the `woocommerce_square_abilities_enabled` filter (default false). |
| 23 | * |
| 24 | * Registration pattern: abilities are registered exclusively via Woo |
| 25 | * Core's `woocommerce_ability_definition_classes` loader filter |
| 26 | * (introduced in WC 10.9). On stores running WC < 10.9 the feature |
| 27 | * silently no-ops — see woo_abilities_loader_available(). |
| 28 | * |
| 29 | * @internal This class may be modified, moved or removed in future releases. |
| 30 | */ |
| 31 | class Abilities_Registrar { |
| 32 | |
| 33 | /** |
| 34 | * Category slug used for every Square for WooCommerce ability. |
| 35 | * |
| 36 | * The `woocommerce` category is owned and registered by WooCommerce |
| 37 | * Core (10.9+); plugin ownership lives in the ability namespace, not |
| 38 | * the category. Mirrored on Domain\AbstractSquareAbility::CATEGORY_SLUG |
| 39 | * so Domain classes can reference self::CATEGORY_SLUG without a |
| 40 | * cross-namespace static call. |
| 41 | * |
| 42 | * @var string |
| 43 | */ |
| 44 | const CATEGORY_SLUG = 'woocommerce'; |
| 45 | |
| 46 | /** |
| 47 | * Ability definition classes registered through the WC 10.9 loader. |
| 48 | * |
| 49 | * Every Square for WooCommerce ability is listed here. The ::class |
| 50 | * constants are compile-time strings — referencing them does NOT |
| 51 | * autoload the classes. They resolve only when Woo's loader iterates |
| 52 | * the filter return value on WC 10.9+. |
| 53 | * |
| 54 | * @var array<int, class-string> |
| 55 | */ |
| 56 | private const ABILITY_CLASSES = array( |
| 57 | Domain\GetSyncStatus::class, |
| 58 | Domain\GetSyncRecords::class, |
| 59 | Domain\GetConnectionStatus::class, |
| 60 | Domain\GetLocations::class, |
| 61 | Domain\GetProductSyncState::class, |
| 62 | Domain\GetPendingJobs::class, |
| 63 | Domain\GetOrderPaymentStatus::class, |
| 64 | ); |
| 65 | |
| 66 | /** |
| 67 | * Whether init() has already wired its action callbacks. |
| 68 | * |
| 69 | * Without this guard, repeated calls to init() while the feature |
| 70 | * filter is true would each append a fresh add_action() for the |
| 71 | * registrar callbacks, and the Abilities Registry would emit |
| 72 | * _doing_it_wrong notices for every already-registered slug when the |
| 73 | * action fires. |
| 74 | * |
| 75 | * @var bool |
| 76 | */ |
| 77 | private static $initialized = false; |
| 78 | |
| 79 | /** |
| 80 | * Initialize the abilities registration. |
| 81 | * |
| 82 | * @return void |
| 83 | */ |
| 84 | public static function init(): void { |
| 85 | if ( self::$initialized ) { |
| 86 | return; |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * Filter whether Square for WooCommerce's Abilities API registrations are active. |
| 91 | * |
| 92 | * This filter is evaluated from Plugin::__construct() the first time |
| 93 | * wc_square() is called — typically inside the plugin's |
| 94 | * `plugins_loaded` priority-10 init. To take effect, callbacks must |
| 95 | * be registered _before_ that point. Safe registration windows: |
| 96 | * - a must-use plugin, or wp-config-time code; |
| 97 | * - any `plugins_loaded` callback at priority < 10; |
| 98 | * - a priority-10 `plugins_loaded` callback that runs BEFORE Square's |
| 99 | * own (depends on load order — fragile, prefer one of the above). |
| 100 | * |
| 101 | * Callbacks registered on later hooks (`init`, `wp_loaded`, |
| 102 | * `rest_api_init`, …) will silently no-op. |
| 103 | * |
| 104 | * @since 5.4.0 |
| 105 | * |
| 106 | * @param bool $enabled Whether to register Square for WooCommerce abilities. Default false. |
| 107 | */ |
| 108 | if ( ! apply_filters( 'woocommerce_square_abilities_enabled', false ) ) { |
| 109 | return; |
| 110 | } |
| 111 | |
| 112 | if ( ! self::woo_abilities_loader_available() ) { |
| 113 | // Abilities feature requires WC 10.9. Silently no-op on older |
| 114 | // versions; the feature flag is the rollout safety net. |
| 115 | return; |
| 116 | } |
| 117 | |
| 118 | self::$initialized = true; |
| 119 | |
| 120 | add_filter( 'woocommerce_ability_definition_classes', array( __CLASS__, 'append_classes' ) ); |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * Reset the idempotency guard set by init(). |
| 125 | * |
| 126 | * @internal Test-isolation helper. Not part of the public API. |
| 127 | * |
| 128 | * @return void |
| 129 | */ |
| 130 | public static function reset_initialized_for_testing(): void { |
| 131 | self::$initialized = false; |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * Whether WC 10.9's AbilitiesLoader is available. |
| 136 | * |
| 137 | * Used as a hard gate: on WC < 10.9 the abilities feature silently |
| 138 | * no-ops. WC 10.9 also depends on WP 6.9, so wp_register_ability() |
| 139 | * is implicitly available wherever the loader exists. |
| 140 | * |
| 141 | * @return bool |
| 142 | */ |
| 143 | private static function woo_abilities_loader_available(): bool { |
| 144 | return class_exists( '\\Automattic\\WooCommerce\\Internal\\Abilities\\AbilitiesLoader' ); |
| 145 | } |
| 146 | |
| 147 | /** |
| 148 | * Append Square for WooCommerce ability classes to Woo Core's loader. |
| 149 | * |
| 150 | * Filter callback for `woocommerce_ability_definition_classes`. |
| 151 | * |
| 152 | * @param array $classes Class names accumulated by the loader. |
| 153 | * @return array |
| 154 | */ |
| 155 | public static function append_classes( array $classes ): array { |
| 156 | return array_merge( $classes, self::ABILITY_CLASSES ); |
| 157 | } |
| 158 | |
| 159 | /** |
| 160 | * Permission callback for Square for WooCommerce read abilities. |
| 161 | * |
| 162 | * Mirrors the read gate resolved by |
| 163 | * WC_Square_REST_Base_Controller::check_permission(). |
| 164 | * |
| 165 | * @return bool |
| 166 | */ |
| 167 | public static function can_manage_woocommerce_square(): bool { |
| 168 | return current_user_can( 'manage_woocommerce' ); // phpcs:ignore WordPress.WP.Capabilities.Unknown |
| 169 | } |
| 170 | } |
| 171 |