PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.24
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.24
1.10.24 1.10.23 1.10.22 1.10.21 1.10.20 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 All 168 releases
← All changes | includes/Services/Permission_Rules.php +97 -0 1.10.21 → 1.10.24 View file →
@@ -440,8 +440,105 @@
440 440 return $caps;
441 441 }
442 442
443 443 /**
444 + * The only role a till user may assign.
445 + *
446 + * Customer creation from the till always produces this role; changing a
447 + * role is not a POS feature, so nothing legitimate needs more.
448 + */
449 + private const TILL_ASSIGNABLE_ROLES = array( 'customer' );
450 +
451 + /**
452 + * The roles a POS actor may hand out, or null when the actor is not fenced.
453 + *
454 + * Administrators (and so multisite super admins) are not fenced. A shop
455 + * manager — any actor with `manage_woocommerce` — follows WooCommerce's own
456 + * list for shop managers, `woocommerce_shop_manager_editable_roles`
457 + * (customer by default), which WooCommerce enforces only while it is active
458 + * and only for the literal `shop_manager` role name; applying it here keeps
459 + * that fence up when WooCommerce is deactivated (the roles and their
460 + * capabilities persist) and for a cashier who also holds shop manager.
461 + * Everyone else with till access gets TILL_ASSIGNABLE_ROLES.
462 + *
463 + * @param int $actor Acting user ID.
464 + *
465 + * @return array|null Role names, or null for an unfenced actor.
466 + */
467 + private static function assignable_roles( int $actor ): ?array {
468 + if ( $actor < 1 || ! user_can( $actor, 'access_woocommerce_pos' ) || user_can( $actor, 'manage_options' ) ) {
469 + return null;
470 + }
471 + $allowed = self::TILL_ASSIGNABLE_ROLES;
472 + if ( user_can( $actor, 'manage_woocommerce' ) ) {
473 + $allowed = (array) apply_filters( 'woocommerce_shop_manager_editable_roles', self::TILL_ASSIGNABLE_ROLES ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce's own fence list, applied as WooCommerce applies it.
474 + }
475 +
476 + return array_values( array_filter( array_map( 'strval', $allowed ) ) );
477 + }
478 +
479 + /**
480 + * Fence the roles a till user may assign (`editable_roles` filter).
481 + *
482 + * The Cashier role holds `edit_users` and, on WooCommerce below 9.9,
483 + * `promote_users` (customer creation needed it). WordPress's role-update
484 + * checks — `WP_REST_Users_Controller::check_role_update()`, `edit_user()`
485 + * and the users.php bulk actions — accept those two capabilities and then
486 + * ask `get_editable_roles()` which roles the actor may hand out; nothing
487 + * ranked them, so a cashier could set an ordinary customer's role to
488 + * Administrator. can_modify() does not catch that: it judges the target's
489 + * current capabilities, which a plain customer has none of until after the
490 + * update. It only ever removes roles; it never adds one.
491 + *
492 + * @param array $roles Editable roles keyed by role name.
493 + *
494 + * @return array
495 + */
496 + public static function filter_editable_roles( $roles ): array {
497 + $roles = (array) $roles;
498 + $allowed = self::assignable_roles( get_current_user_id() );
499 + if ( null === $allowed ) {
500 + return $roles;
501 + }
502 +
503 + return array_intersect_key( $roles, array_fill_keys( $allowed, true ) );
504 + }
505 +
506 + /**
507 + * Refuse a multisite "add existing user" invite outside the fence (`invite_user` action).
508 + *
509 + * WordPress's wp-admin/user-new.php stores the requested role in the `new_user_<key>`
510 + * option and only reads `get_editable_roles()` for the email's label, so an
511 + * invite to an existing network account can carry any role; accepting it
512 + * calls `add_user_to_blog()` with that role unchecked. Same fence as
513 + * filter_editable_roles(): a fenced actor's invite may name only an
514 + * assignable role, or the invite is deleted before its email goes out.
515 + *
516 + * @param int $user_id Invited user ID.
517 + * @param array|null $role Role label array, null when the role was not editable.
518 + * @param string $newuser_key Invitation key.
519 + *
520 + * @return void
521 + */
522 + public static function refuse_unfenced_invite( $user_id, $role, $newuser_key ): void {
523 + $allowed = self::assignable_roles( get_current_user_id() );
524 + if ( null === $allowed ) {
525 + return;
526 + }
527 + $invite = get_option( 'new_user_' . $newuser_key );
528 + $requested = \is_array( $invite ) && isset( $invite['role'] ) ? (string) $invite['role'] : '';
529 + if ( \in_array( $requested, $allowed, true ) ) {
530 + return;
531 + }
532 + delete_option( 'new_user_' . $newuser_key );
533 + wp_die(
534 + esc_html__( 'Sorry, you are not allowed to give users that role.', 'woocommerce-pos' ),
535 + '',
536 + array( 'response' => 403 )
537 + );
538 + }
539 +
540 + /**
444 541 * Build the staff account permission error.
445 542 *
446 543 * @return \WP_Error
447 544 */