class-registrar.php
184 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Abstract base class for registering a category and its abilities with the |
| 4 | * WordPress Abilities API. |
| 5 | * |
| 6 | * @package automattic/jetpack-wp-abilities |
| 7 | */ |
| 8 | |
| 9 | // @phan-file-suppress PhanAbstractStaticMethodCallInStatic -- static:: dispatches to the concrete subclass for the three abstract getters; callers must not instantiate Registrar itself. |
| 10 | |
| 11 | namespace Automattic\Jetpack\WP_Abilities; |
| 12 | |
| 13 | /** |
| 14 | * Abstract base class that owns the boilerplate every Jetpack abilities |
| 15 | * registrar needs: hooking into the Abilities API lifecycle actions (or |
| 16 | * calling the registration methods directly when those actions have already |
| 17 | * fired), and guarding the Abilities API function calls so the class is safe |
| 18 | * to load on WP < 6.9. |
| 19 | * |
| 20 | * Consumers extend this class and override `get_category_slug()`, |
| 21 | * `get_category_definition()`, and `get_abilities()`. |
| 22 | */ |
| 23 | abstract class Registrar { |
| 24 | |
| 25 | const PACKAGE_VERSION = '0.1.5'; |
| 26 | |
| 27 | /** |
| 28 | * Action fired by the Abilities API when ability categories should register. |
| 29 | */ |
| 30 | const CATEGORIES_INIT_ACTION = 'wp_abilities_api_categories_init'; |
| 31 | |
| 32 | /** |
| 33 | * Action fired by the Abilities API when abilities should register. |
| 34 | */ |
| 35 | const ABILITIES_INIT_ACTION = 'wp_abilities_api_init'; |
| 36 | |
| 37 | /** |
| 38 | * Return the category slug this registrar owns (e.g. "jetpack-forms"). |
| 39 | * |
| 40 | * @return string |
| 41 | */ |
| 42 | abstract public static function get_category_slug(): string; |
| 43 | |
| 44 | /** |
| 45 | * Return the category definition passed to `wp_register_ability_category()`. |
| 46 | * |
| 47 | * Expected shape: [ 'label' => string, 'description' => string ]. |
| 48 | * |
| 49 | * @return array |
| 50 | */ |
| 51 | abstract public static function get_category_definition(): array; |
| 52 | |
| 53 | /** |
| 54 | * Return the abilities this registrar owns as a `[ slug => spec ]` map. |
| 55 | * |
| 56 | * Each spec is passed as-is to `wp_register_ability()`. If a spec omits |
| 57 | * `category`, the registrar auto-injects `get_category_slug()`. If the spec |
| 58 | * sets `category` explicitly, it is preserved unchanged. |
| 59 | * |
| 60 | * @return array<string, array> |
| 61 | */ |
| 62 | abstract public static function get_abilities(): array; |
| 63 | |
| 64 | /** |
| 65 | * Wire up the Abilities API registrations. |
| 66 | * |
| 67 | * Gated behind the `jetpack_wp_abilities_enabled` filter, which defaults |
| 68 | * to `false`. Consumers opt in explicitly — per site, per user, or via |
| 69 | * feature flag — so abilities roll out gradually rather than flipping on |
| 70 | * for every site the moment the package loads. |
| 71 | * |
| 72 | * When the filter returns `true`, each of the two Abilities API lifecycle |
| 73 | * actions either gets a registration callback hooked, or — if the action |
| 74 | * has already fired — dispatches immediately so late-loading plugins |
| 75 | * still register on time. |
| 76 | * |
| 77 | * @return void |
| 78 | */ |
| 79 | public static function init() { |
| 80 | /** |
| 81 | * Filters whether Jetpack Abilities API registration should run. |
| 82 | * |
| 83 | * Default `false`. Return `true` to enable registration for this |
| 84 | * request, typically gated on a site option, user capability, or |
| 85 | * feature flag to support staged rollout. |
| 86 | * |
| 87 | * @since 0.1.0 |
| 88 | * |
| 89 | * @param bool $enabled Whether to register abilities. Default false. |
| 90 | */ |
| 91 | if ( ! apply_filters( 'jetpack_wp_abilities_enabled', false ) ) { |
| 92 | return; |
| 93 | } |
| 94 | |
| 95 | if ( did_action( self::CATEGORIES_INIT_ACTION ) ) { |
| 96 | static::register_category(); |
| 97 | } else { |
| 98 | add_action( self::CATEGORIES_INIT_ACTION, array( static::class, 'register_category' ) ); |
| 99 | } |
| 100 | |
| 101 | if ( did_action( self::ABILITIES_INIT_ACTION ) ) { |
| 102 | static::register_abilities(); |
| 103 | } else { |
| 104 | add_action( self::ABILITIES_INIT_ACTION, array( static::class, 'register_abilities' ) ); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * Register the category with the WordPress Abilities API. |
| 110 | * |
| 111 | * Safe to call directly or as a hook callback. Passes the category slug |
| 112 | * through the `jetpack_wp_abilities_should_register` filter so consumers |
| 113 | * can gate registration per-site, per-user, or per-feature-flag. |
| 114 | * |
| 115 | * @return void |
| 116 | */ |
| 117 | public static function register_category() { |
| 118 | if ( ! function_exists( 'wp_register_ability_category' ) ) { |
| 119 | return; |
| 120 | } |
| 121 | |
| 122 | $slug = static::get_category_slug(); |
| 123 | if ( ! self::should_register( 'category', $slug ) ) { |
| 124 | return; |
| 125 | } |
| 126 | |
| 127 | wp_register_ability_category( $slug, static::get_category_definition() ); |
| 128 | } |
| 129 | |
| 130 | /** |
| 131 | * Register every ability returned by `get_abilities()`. |
| 132 | * |
| 133 | * Safe to call directly or as a hook callback. Each ability slug is |
| 134 | * passed through the `jetpack_wp_abilities_should_register` filter |
| 135 | * individually, so consumers can enable a subset of abilities on a |
| 136 | * subset of sites or users. |
| 137 | * |
| 138 | * @return void |
| 139 | */ |
| 140 | public static function register_abilities() { |
| 141 | if ( ! function_exists( 'wp_register_ability' ) ) { |
| 142 | return; |
| 143 | } |
| 144 | |
| 145 | $category_slug = static::get_category_slug(); |
| 146 | |
| 147 | foreach ( static::get_abilities() as $slug => $spec ) { |
| 148 | if ( ! self::should_register( 'ability', $slug ) ) { |
| 149 | continue; |
| 150 | } |
| 151 | if ( ! array_key_exists( 'category', $spec ) ) { |
| 152 | $spec['category'] = $category_slug; |
| 153 | } |
| 154 | wp_register_ability( $slug, $spec ); |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | /** |
| 159 | * Apply the shared registration filter. |
| 160 | * |
| 161 | * @param string $type One of 'category' or 'ability'. |
| 162 | * @param string $slug The slug being registered. |
| 163 | * @return bool True when registration should proceed, false to skip. |
| 164 | */ |
| 165 | private static function should_register( string $type, string $slug ): bool { |
| 166 | /** |
| 167 | * Filters whether an Abilities API category or ability should be registered. |
| 168 | * |
| 169 | * Returning false from any filter callback skips the registration, |
| 170 | * which is how consumers gate rollout per-site, per-user, or per |
| 171 | * feature flag. The filter fires once per category and once per |
| 172 | * individual ability, so callbacks can allow-list or deny-list by |
| 173 | * `$slug`. |
| 174 | * |
| 175 | * @since 0.1.0 |
| 176 | * |
| 177 | * @param bool $enabled Whether to register. Default true. |
| 178 | * @param string $type Either 'category' or 'ability'. |
| 179 | * @param string $slug The category or ability slug being registered. |
| 180 | */ |
| 181 | return (bool) apply_filters( 'jetpack_wp_abilities_should_register', true, $type, $slug ); |
| 182 | } |
| 183 | } |
| 184 |