| @@ -8,8 +8,9 @@ | ||
| 8 | 8 | namespace WCPOS\WooCommercePOS\Services\Settings; |
| 9 | 9 | |
| 10 | 10 | use WCPOS\WooCommercePOS\Interfaces\Settings_Section_Interface; |
| 11 | 11 | use WP_Error; |
| 12 | +use WP_User; | |
| 12 | 13 | |
| 13 | 14 | /** |
| 14 | 15 | * The Access Settings Section. |
| 15 | 16 | * |
| @@ -47,8 +48,49 @@ | ||
| 47 | 48 | return array_merge( $caps['wcpos'], $caps['wc'], $caps['wp'] ); |
| 48 | 49 | } |
| 49 | 50 | |
| 50 | 51 | /** |
| 52 | + * Capabilities the user can actually exercise, in the Access-settings vocabulary. | |
| 53 | + * | |
| 54 | + * The user_can() check is what every REST permission callback asks, so it is the | |
| 55 | + * answer the client must be given; it also runs the `user_has_cap` filter that | |
| 56 | + * role editors such as Members use to make a Deny on one role override a grant | |
| 57 | + * on another. The two singular meta caps (edit_product, delete_product) cannot | |
| 58 | + * go through user_can() without a post, so they are read from allcaps after the | |
| 59 | + * same filter has run. | |
| 60 | + * | |
| 61 | + * @param WP_User $user User to report on. | |
| 62 | + * | |
| 63 | + * @return string[] Capability names, in capability_names() order. | |
| 64 | + */ | |
| 65 | + public static function effective_capabilities( WP_User $user ): array { | |
| 66 | + $names = self::capability_names(); | |
| 67 | + // A multisite super admin holds every capability before the filter runs | |
| 68 | + // (WP_User::has_cap), so mirror that bypass for the two filtered names. | |
| 69 | + $super_admin = is_multisite() && is_super_admin( $user->ID ); | |
| 70 | + | |
| 71 | + return array_values( | |
| 72 | + array_filter( | |
| 73 | + $names, | |
| 74 | + function ( $cap ) use ( $user, $super_admin ) { | |
| 75 | + if ( ! in_array( $cap, array( 'edit_product', 'delete_product' ), true ) ) { | |
| 76 | + return user_can( $user, $cap ); | |
| 77 | + } | |
| 78 | + if ( $super_admin ) { | |
| 79 | + return true; | |
| 80 | + } | |
| 81 | + // Same argument shape as WP_User::has_cap(): the caps being | |
| 82 | + // checked, then the requested cap and user id. | |
| 83 | + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Core capability filter, run so role-editor denies apply. | |
| 84 | + $filtered = apply_filters( 'user_has_cap', $user->allcaps, array( $cap ), array( $cap, $user->ID ), $user ); | |
| 85 | + | |
| 86 | + return ! empty( $filtered[ $cap ] ); | |
| 87 | + } | |
| 88 | + ) | |
| 89 | + ); | |
| 90 | + } | |
| 91 | + | |
| 92 | + /** | |
| 51 | 93 | * Get capabilities grouped by type. |
| 52 | 94 | * |
| 53 | 95 | * WooCommerce 9.9 replaced promote_users with create_customers for |
| 54 | 96 | * customer creation via the REST API. We show the correct capability |
| @@ -166,9 +208,9 @@ | ||
| 166 | 208 | * capability names to boolean grants. Only one role is mutated per call — |
| 167 | 209 | * this mirrors the single-role update semantics of the original REST |
| 168 | 210 | * controller. |
| 169 | 211 | * |
| 170 | - * The administrator/read capability is never removed as a sanity guard. | |
| 212 | + * The administrator read and manage_woocommerce_pos capabilities are never removed. | |
| 171 | 213 | * |
| 172 | 214 | * @param array $settings Incoming payload keyed by role slug. |
| 173 | 215 | * |
| 174 | 216 | * @return array|WP_Error The fresh read() view on success. |
| @@ -176,9 +218,9 @@ | ||
| 176 | 218 | public function write( array $settings ) { |
| 177 | 219 | // Defense-in-depth: capability mutation is a privileged service-layer |
| 178 | 220 | // operation; do not rely solely on the REST route's permission |
| 179 | 221 | // callback (matches the Settings::delete_settings() precedent). |
| 180 | - if ( ! current_user_can( 'edit_users' ) || ! current_user_can( 'promote_users' ) ) { | |
| 222 | + if ( ! current_user_can( 'manage_woocommerce_pos' ) || ! current_user_can( 'edit_users' ) || ! current_user_can( 'promote_users' ) ) { | |
| 181 | 223 | return new WP_Error( |
| 182 | 224 | 'woocommerce_pos_settings_error', |
| 183 | 225 | __( 'You do not have permission to update access settings.', 'woocommerce-pos' ), |
| 184 | 226 | array( 'status' => 403 ) |
| @@ -225,8 +267,12 @@ | ||
| 225 | 267 | // Apply each allowed capability grant/revoke. |
| 226 | 268 | foreach ( $flattened_caps as $cap => $grant ) { |
| 227 | 269 | // Sanity check: administrator role must always keep the `read` capability. |
| 228 | 270 | if ( 'administrator' === $slug && 'read' === $cap ) { |
| 271 | + continue; | |
| 272 | + } | |
| 273 | + // Administrators must retain access to capability management. | |
| 274 | + if ( 'administrator' === $slug && 'manage_woocommerce_pos' === $cap && ! $grant ) { | |
| 229 | 275 | continue; |
| 230 | 276 | } |
| 231 | 277 | if ( $grant ) { |
| 232 | 278 | $role->add_cap( $cap ); |