PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.9
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.9
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
woocommerce-pos / includes / Services / Settings / Access_Section.php

Access_Section.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.9, at includes/Services/Settings/Access_Section.php

270 lines 7.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Access Settings Section.
4 *
5 * @package WCPOS\WooCommercePOS
6 */
7
8 namespace WCPOS\WooCommercePOS\Services\Settings;
9
10 use WCPOS\WooCommercePOS\Interfaces\Settings_Section_Interface;
11 use WP_Error;
12
13 /**
14 * The Access Settings Section.
15 *
16 * Non-option-backed: read() computes role capabilities from $wp_roles;
17 * write() mutates role capabilities via WP_Role::add_cap()/remove_cap().
18 * There is no woocommerce_pos_settings_access option.
19 */
20 class Access_Section implements Settings_Section_Interface {
21 /**
22 * Section id.
23 *
24 * @return string
25 */
26 public function id(): string {
27 return 'access';
28 }
29
30 /**
31 * No option-backed defaults for this section.
32 *
33 * @return array
34 */
35 public function defaults(): array {
36 return array();
37 }
38
39 /**
40 * Get the flat list of capability names.
41 *
42 * @return array
43 */
44 public static function capability_names(): array {
45 $caps = self::get_caps();
46
47 return array_merge( $caps['wcpos'], $caps['wc'], $caps['wp'] );
48 }
49
50 /**
51 * Get capabilities grouped by type.
52 *
53 * WooCommerce 9.9 replaced promote_users with create_customers for
54 * customer creation via the REST API. We show the correct capability
55 * on the Access settings page based on the installed WC version.
56 *
57 * The `wc` group lists PRIMITIVE capabilities only — the names a role grant
58 * can actually be read back from. `product` and `shop_coupon` register with
59 * map_meta_cap = true, so WordPress rewrites their singular meta caps
60 * (`edit_shop_coupon`, `delete_shop_coupon`, ...) into the `*_others_*` /
61 * `*_published_*` / `*_private_*` primitives below; exposing the singular
62 * names would be a dead toggle. `edit_product` / `delete_product` are the
63 * exception and ARE required: `product_variation` registers with
64 * capability_type `product` and map_meta_cap = false, so a variation write
65 * checks those two names literally.
66 *
67 * @return array
68 */
69 private static function get_caps(): array {
70 $customer_create_cap = version_compare( WC()->version, '9.9', '>=' )
71 ? 'create_customers'
72 : 'promote_users';
73
74 return array(
75 'wcpos' => array(
76 'access_woocommerce_pos',
77 'manage_woocommerce_pos',
78 ),
79 'wc' => array(
80 $customer_create_cap,
81 'read_private_products',
82 'edit_product',
83 'edit_products',
84 'edit_others_products',
85 'edit_private_products',
86 'edit_published_products',
87 'publish_products',
88 'delete_product',
89 'delete_products',
90 'delete_others_products',
91 'delete_private_products',
92 'delete_published_products',
93 'read_private_shop_orders',
94 'publish_shop_orders',
95 'edit_shop_orders',
96 'edit_others_shop_orders',
97 'edit_users',
98 'list_users',
99 'manage_product_terms',
100 'read_private_shop_coupons',
101 'edit_shop_coupons',
102 'edit_others_shop_coupons',
103 'edit_private_shop_coupons',
104 'edit_published_shop_coupons',
105 'publish_shop_coupons',
106 'delete_shop_coupons',
107 'delete_others_shop_coupons',
108 'delete_private_shop_coupons',
109 'delete_published_shop_coupons',
110 ),
111 'wp' => array(
112 'read',
113 ),
114 );
115 }
116
117 /**
118 * Read the section's public view: role capability groups computed from $wp_roles.
119 *
120 * @return array
121 */
122 public function read(): array {
123 global $wp_roles;
124 $role_caps = array();
125 $caps = self::get_caps();
126
127 $roles = $wp_roles->roles;
128 if ( $roles ) {
129 foreach ( $roles as $slug => $role ) {
130 $role_caps[ $slug ] = array(
131 'name' => $role['name'],
132 'capabilities' => array(
133 'wcpos' => array_intersect_key(
134 array_merge( array_fill_keys( $caps['wcpos'], false ), $role['capabilities'] ),
135 array_flip( $caps['wcpos'] )
136 ),
137 'wc' => array_intersect_key(
138 array_merge( array_fill_keys( $caps['wc'], false ), $role['capabilities'] ),
139 array_flip( $caps['wc'] )
140 ),
141 'wp' => array_intersect_key(
142 array_merge( array_fill_keys( $caps['wp'], false ), $role['capabilities'] ),
143 array_flip( $caps['wp'] )
144 ),
145 ),
146 );
147 }
148 }
149
150 /*
151 * Filters the access settings.
152 *
153 * @param {array} $settings
154 * @returns {array} $settings
155 * @since 1.0.0
156 * @hook woocommerce_pos_access_settings
157 */
158 return apply_filters( 'woocommerce_pos_access_settings', $role_caps );
159 }
160
161 /**
162 * Mutate role capabilities.
163 *
164 * Expects the payload to contain exactly one role slug key. The value is a
165 * partial structure with a 'capabilities' key whose groups (wcpos/wc/wp) map
166 * capability names to boolean grants. Only one role is mutated per call —
167 * this mirrors the single-role update semantics of the original REST
168 * controller.
169 *
170 * The administrator/read capability is never removed as a sanity guard.
171 *
172 * @param array $settings Incoming payload keyed by role slug.
173 *
174 * @return array|WP_Error The fresh read() view on success.
175 */
176 public function write( array $settings ) {
177 // Defense-in-depth: capability mutation is a privileged service-layer
178 // operation; do not rely solely on the REST route's permission
179 // callback (matches the Settings::delete_settings() precedent).
180 if ( ! current_user_can( 'edit_users' ) || ! current_user_can( 'promote_users' ) ) {
181 return new WP_Error(
182 'woocommerce_pos_settings_error',
183 __( 'You do not have permission to update access settings.', 'woocommerce-pos' ),
184 array( 'status' => 403 )
185 );
186 }
187
188 global $wp_roles;
189
190 // Intersect payload against known role slugs.
191 $roles = array_keys( $wp_roles->roles );
192 $update = array_intersect_key( $settings, array_flip( $roles ) );
193
194 // Only update a single role per call.
195 if ( 1 === \count( $update ) ) {
196 $slugs = array_keys( $update );
197 $slug = $slugs[0];
198 $role = get_role( $slug );
199
200 if ( $role ) {
201 // Build the allow-list of capabilities exposed for this role, including
202 // extension groups added via the woocommerce_pos_access_settings filter.
203 $access_settings = $this->read();
204 $allowed_caps = array();
205 if ( isset( $access_settings[ $slug ]['capabilities'] ) ) {
206 foreach ( $access_settings[ $slug ]['capabilities'] as $capabilities ) {
207 if ( \is_array( $capabilities ) ) {
208 $allowed_caps = array_merge( $allowed_caps, array_keys( $capabilities ) );
209 }
210 }
211 }
212
213 // Flatten capability groups (wcpos / wc / wp) into a single map.
214 $flattened_caps = array();
215 foreach ( $update[ $slug ]['capabilities'] as $capabilities ) {
216 if ( \is_array( $capabilities ) ) {
217 $flattened_caps = array_merge( $flattened_caps, $capabilities );
218 }
219 }
220
221 // Ignore capabilities outside the access settings view (issue #1159).
222 $flattened_caps = array_intersect_key( $flattened_caps, array_flip( $allowed_caps ) );
223 $flattened_caps = array_map( 'wp_validate_boolean', $flattened_caps );
224
225 // Apply each allowed capability grant/revoke.
226 foreach ( $flattened_caps as $cap => $grant ) {
227 // Sanity check: administrator role must always keep the `read` capability.
228 if ( 'administrator' === $slug && 'read' === $cap ) {
229 continue;
230 }
231 if ( $grant ) {
232 $role->add_cap( $cap );
233 } else {
234 $role->remove_cap( $cap );
235 }
236 }
237 }
238 }
239
240 return $this->read();
241 }
242
243 /**
244 * Full replacement, not a merge: the incoming payload IS the write.
245 *
246 * Writes mutate exactly one role per call and identify that role by the
247 * payload carrying a single known role slug. Merging the existing view in
248 * would hand write() every role on the site and silently turn a capability
249 * update into a no-op, so this section opts out of the default
250 * array_replace_recursive PATCH strategy.
251 *
252 * @param array $existing Existing settings view.
253 * @param array $patch Incoming payload keyed by role slug.
254 *
255 * @return array
256 */
257 public function merge( array $existing, array $patch ): array {
258 return $patch;
259 }
260
261 /**
262 * REST endpoint args — none required for access.
263 *
264 * @return array
265 */
266 public function endpoint_args(): array {
267 return array();
268 }
269 }
270