array(), 'total' => 0 ); } $args = array( 'number' => self::PER_PAGE, 'paged' => max( 1, (int) $paged ), 'orderby' => 'display_name', 'order' => 'ASC', 'count_total' => true, ); if ( '' !== $q ) { $args['search'] = '*' . $q . '*'; $args['search_columns'] = array( 'user_login', 'user_email', 'display_name', 'user_nicename' ); } if ( '' !== $role ) { $args['role'] = $role; } $query = new WP_User_Query( $args ); return array( 'users' => (array) $query->get_results(), 'total' => (int) $query->get_total(), ); } public static function grid_staff() { $out = array(); $delegated = AIEO_User_Roles::delegations(); foreach ( AIEO_User_Roles::staff_roles() as $slug => $name ) { $obj = get_role( $slug ); if ( ! $obj || $obj->has_cap( 'promote_users' ) ) { continue; } $reaches = $obj->has_cap( defined( 'AIEO_CAPABILITY' ) ? AIEO_CAPABILITY : 'aieo_access' ) || $obj->has_cap( 'manage_woocommerce' ) || $obj->has_cap( 'edit_pages' ); if ( $reaches || isset( $delegated[ $slug ] ) ) { $out[ $slug ] = $name; } } return $out; } public static function grid_roles() { return array_intersect_key( AIEO_Safe_Roles::roles(), AIEO_User_Roles::assignable() ); } public static function describe_change( array $r ) { $names = static function ( array $slugs ) { return implode( ', ', array_map( array( __CLASS__, 'role_name' ), $slugs ) ); }; $parts = array(); if ( $r['added'] ) { $parts[] = sprintf( __( 'Added: %s.', 'ai-eshop-optimizer' ), $names( $r['added'] ) ); } if ( $r['removed'] ) { $parts[] = sprintf( __( 'Removed: %s.', 'ai-eshop-optimizer' ), $names( $r['removed'] ) ); } if ( '' !== $r['kept'] ) { $parts[] = sprintf( __( 'An account keeps one role, so it has %s.', 'ai-eshop-optimizer' ), self::role_name( $r['kept'] ) ); } return $parts ? implode( ' ', $parts ) : __( 'Nothing changed.', 'ai-eshop-optimizer' ); } public function ajax_save_account() { AIEO_DMM::verify_ajax_request(); $user_id = isset( $_POST['user_id'] ) ? absint( wp_unslash( $_POST['user_id'] ) ) : 0; $roles = isset( $_POST['roles'] ) && is_array( $_POST['roles'] ) ? array_map( 'sanitize_key', wp_unslash( $_POST['roles'] ) ) : array(); if ( ! AIEO_User_Roles::may_manage( $user_id ) ) { wp_send_json_error( array( 'message' => __( 'Not saved: you do not manage this account’s roles.', 'ai-eshop-optimizer' ) ) ); } $r = AIEO_User_Roles::set( $user_id, $roles, 'manual' ); if ( is_wp_error( $r ) ) { wp_send_json_error( array( 'message' => sprintf( __( 'Not saved: %s', 'ai-eshop-optimizer' ), $r->get_error_message() ) ) ); } wp_send_json_success( array( 'message' => self::describe_change( $r ) ) ); } public function ajax_save_delegations() { AIEO_DMM::verify_ajax_request(); if ( ! AIEO_User_Roles::is_role_admin() ) { wp_send_json_error( array( 'message' => __( 'Not saved: only an administrator decides who may give roles.', 'ai-eshop-optimizer' ) ) ); } $posted = isset( $_POST['delegate'] ) && is_array( $_POST['delegate'] ) ? wp_unslash( $_POST['delegate'] ) : array(); $errors = array(); foreach ( array_keys( self::grid_staff() ) as $staff ) { $want = isset( $posted[ $staff ] ) ? array_map( 'sanitize_key', (array) $posted[ $staff ] ) : array(); $obj = get_role( $staff ); foreach ( array_keys( self::grid_roles() ) as $role ) { $on = in_array( $role, $want, true ); if ( $obj && $obj->has_cap( AIEO_User_Roles::cap( $role ) ) === $on ) { continue; } $r = AIEO_User_Roles::set_delegation( $staff, $role, $on ); if ( is_wp_error( $r ) ) { $errors[] = $r->get_error_message(); } } } if ( $errors ) { wp_send_json_error( array( 'message' => sprintf( __( 'Not saved: %s', 'ai-eshop-optimizer' ), implode( ' ', array_unique( $errors ) ) ) ) ); } wp_send_json_success( array( 'message' => __( 'Saved.', 'ai-eshop-optimizer' ) ) ); } public function handle_role_form() { check_admin_referer( self::POST_ACTION ); if ( ! current_user_can( defined( 'AIEO_CAPABILITY' ) ? AIEO_CAPABILITY : 'manage_options' ) || ! AIEO_User_Roles::is_role_admin() ) { wp_die( esc_html__( 'Sorry, only an administrator changes the shop’s roles.', 'ai-eshop-optimizer' ), 403 ); } $op = isset( $_POST['op'] ) ? sanitize_key( wp_unslash( $_POST['op'] ) ) : ''; $role = isset( $_POST['role'] ) ? sanitize_key( wp_unslash( $_POST['role'] ) ) : ''; $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : ''; if ( 'create' === $op ) { $slug = isset( $_POST['slug'] ) ? sanitize_key( wp_unslash( $_POST['slug'] ) ) : ''; $r = AIEO_User_Roles::create_role( $name, $slug ); $done = is_wp_error( $r ) ? '' : sprintf( __( 'Made the customer role %s. Give it to staff to manage below, if they should.', 'ai-eshop-optimizer' ), $name ); } elseif ( 'rename' === $op ) { $old = self::role_name( $role ); $r = AIEO_User_Roles::rename_role( $role, $name ); $done = is_wp_error( $r ) ? '' : sprintf( __( 'Renamed %1$s to %2$s.', 'ai-eshop-optimizer' ), $old, $name ); } elseif ( 'delete' === $op ) { $old = self::role_name( $role ); $r = AIEO_User_Roles::delete_role( $role ); $done = is_wp_error( $r ) ? '' : sprintf( __( 'Deleted the role %s.', 'ai-eshop-optimizer' ), $old ); } else { $r = new WP_Error( 'aieo_roles_op', __( 'Nothing was asked.', 'ai-eshop-optimizer' ) ); } set_transient( self::NOTICE . get_current_user_id(), is_wp_error( $r ) ? array( 'destructive', $r->get_error_message() ) : array( 'success', $done ), MINUTE_IN_SECONDS ); wp_safe_redirect( $this->get_url() . '#aieo-card-user-roles-customer-roles' ); exit; } public function render_subtab() { if ( ! AIEO_User_Roles::can_use() ) { echo \Rooster\Admin\Ui::notice( esc_html__( 'Roles are managed by an administrator, and by staff an administrator lets give customer roles.', 'ai-eshop-optimizer' ), 'info' ); return; } $q = isset( $_GET['q'] ) ? sanitize_text_field( wp_unslash( $_GET['q'] ) ) : ''; $role = isset( $_GET['role'] ) ? sanitize_key( wp_unslash( $_GET['role'] ) ) : ''; $paged = isset( $_GET['paged'] ) ? max( 1, absint( wp_unslash( $_GET['paged'] ) ) ) : 1; $user = isset( $_GET['user'] ) ? get_userdata( absint( wp_unslash( $_GET['user'] ) ) ) : false; $notice = get_transient( self::NOTICE . get_current_user_id() ); if ( is_array( $notice ) ) { delete_transient( self::NOTICE . get_current_user_id() ); echo \Rooster\Admin\Ui::notice( esc_html( (string) $notice[1] ), (string) $notice[0] ); } $counts = AIEO_User_Roles::counts(); if ( function_exists( 'rooster_admin_band_open' ) ) { rooster_admin_band_open( array( 'cols' => 1 ) ); } $this->render_find( $q, $role, $paged, $counts ); if ( $user instanceof WP_User ) { $this->render_account( $user ); } if ( function_exists( 'rooster_admin_band_close' ) ) { rooster_admin_band_close(); } if ( AIEO_User_Roles::is_role_admin() ) { if ( function_exists( 'rooster_admin_band_open' ) ) { rooster_admin_band_open( array( 'cols' => 1 ) ); } $this->render_delegations(); $this->render_catalogue( $counts ); if ( function_exists( 'rooster_admin_band_close' ) ) { rooster_admin_band_close(); } } } private function render_find( $q, $role, $paged, array $counts ) { list( $open, $close ) = AIEO_DMM_UI::card( array( 'anchor' => 'user-roles-find', 'icon' => 'users', 'title' => __( 'Find an account', 'ai-eshop-optimizer' ), 'description' => __( 'Search by name, e-mail or username, or list the accounts that hold a role; open one to see every role it holds and change the ones you may.', 'ai-eshop-optimizer' ), ) ); echo $open; $roles = array( '' => __( 'Any role', 'ai-eshop-optimizer' ) ); foreach ( array_keys( wp_roles()->roles ) as $slug ) { $roles[ (string) $slug ] = self::role_name( (string) $slug ); } echo '
'; if ( '' !== $q || '' !== $role ) { $found = self::search( $q, $role, $paged ); $base = add_query_arg( array( 'q' => $q, 'role' => $role ), $this->get_url() ); $rows = array(); foreach ( $found['users'] as $u ) { $badges = ''; foreach ( (array) $u->roles as $slug ) { $badges .= \Rooster\Admin\Ui::badge( self::role_name( (string) $slug ), AIEO_Safe_Roles::is_safe( (string) $slug ) ? 'info' : 'mono', array( 'sm' => true ) ) . ' '; } $open_url = add_query_arg( 'user', (int) $u->ID, $base ) . '#aieo-card-user-roles-account'; $rows[] = array( '_name' => (string) $u->display_name, 'name' => '' . esc_html( (string) $u->display_name ) . '', 'email' => (string) $u->user_email, 'login' => (string) $u->user_login, 'roles' => '' !== $badges ? $badges : \Rooster\Admin\Ui::badge( __( 'No role', 'ai-eshop-optimizer' ), 'warning', array( 'sm' => true ) ), '_actions' => \Rooster\Admin\Ui::row_actions( array( array( 'label' => AIEO_User_Roles::may_manage( (int) $u->ID ) ? __( 'Change roles', 'ai-eshop-optimizer' ) : __( 'See roles', 'ai-eshop-optimizer' ), 'url' => $open_url, ), ) ), ); } echo \Rooster\Admin\Ui::table( array( array( 'key' => 'name', 'label' => __( 'Name', 'ai-eshop-optimizer' ), 'type' => 'html' ), array( 'key' => 'email', 'label' => __( 'E-mail', 'ai-eshop-optimizer' ) ), array( 'key' => 'login', 'label' => __( 'Username', 'ai-eshop-optimizer' ), 'type' => 'code', 'priority' => 'low' ), array( 'key' => 'roles', 'label' => __( 'Roles', 'ai-eshop-optimizer' ), 'type' => 'html' ), ), $rows, array( 'id' => 'aieo-ur-found', 'empty' => \Rooster\Admin\States::state( 'no-results', array( 'compact' => true, 'title' => __( 'No account matches.', 'ai-eshop-optimizer' ), 'text' => __( 'Check the spelling, or search with part of the e-mail address.', 'ai-eshop-optimizer' ) ) ), 'footer' => $found['total'] > self::PER_PAGE ? \Rooster\Admin\Ui::pager( $paged, (int) ceil( $found['total'] / self::PER_PAGE ), static fn( int $p ): string => add_query_arg( 'paged', $p, $base ), array( 'total' => $found['total'], 'per_page' => self::PER_PAGE ) ) : '', ) ); } echo $close; } private function render_account( WP_User $user ) { $form = 'aieo-card-user-roles-account-form'; $manage = AIEO_User_Roles::may_manage( (int) $user->ID ); list( $open, $close ) = AIEO_DMM_UI::card( array( 'anchor' => 'user-roles-account', 'icon' => 'user-cog', 'title' => __( 'Account roles', 'ai-eshop-optimizer' ), 'footer' => $manage ? \Rooster\Admin\States::save_button( $form, array( 'label' => __( 'Save roles', 'ai-eshop-optimizer' ) ) ) : '', ) ); echo $open; $facts = array( array( 'label' => __( 'Name', 'ai-eshop-optimizer' ), 'value' => (string) $user->display_name ), array( 'label' => __( 'E-mail', 'ai-eshop-optimizer' ), 'value' => (string) $user->user_email ), array( 'label' => __( 'Username', 'ai-eshop-optimizer' ), 'code' => (string) $user->user_login ), ); if ( current_user_can( 'edit_user', (int) $user->ID ) ) { $facts[] = array( 'label' => __( 'Profile', 'ai-eshop-optimizer' ), 'html' => '' . esc_html__( 'Edit in WordPress', 'ai-eshop-optimizer' ) . '' ); } echo \Rooster\Admin\Ui::facts( $facts ); $notes = AIEO_User_Roles::notes(); $changeable = array( 'customer' => array(), 'staff' => array() ); $described = array(); $locked = array(); foreach ( array_keys( wp_roles()->roles ) as $slug ) { $slug = (string) $slug; $ok = AIEO_User_Roles::check( (int) $user->ID, $slug ); if ( true === $ok ) { $changeable[ AIEO_Safe_Roles::is_safe( $slug ) ? 'customer' : 'staff' ][ $slug ] = self::role_name( $slug ); if ( ! empty( $notes[ $slug ] ) ) { $described[ $slug ] = implode( ' · ', (array) $notes[ $slug ] ); } } elseif ( in_array( $slug, (array) $user->roles, true ) ) { $locked[ $slug ] = $ok->get_error_message(); } } $held = array_values( (array) $user->roles ); $fields = ''; foreach ( array( 'customer' => __( 'Customer roles', 'ai-eshop-optimizer' ), 'staff' => __( 'Staff roles', 'ai-eshop-optimizer' ), ) as $kind => $label ) { if ( $changeable[ $kind ] ) { $fields .= \Rooster\Admin\Ui::field_row( array( 'group' => true, 'label' => $label, 'control' => \Rooster\Admin\Ui::checks( 'roles', $changeable[ $kind ], $held, array( 'grid' => true, 'descriptions' => array_intersect_key( $described, $changeable[ $kind ] ) ) ), 'help' => 'customer' === $kind ? __( 'Several roles at once are fine: an account has the prices and access of each, and keeps at least one role.', 'ai-eshop-optimizer' ) : __( 'A staff role gives access to the shop’s administration.', 'ai-eshop-optimizer' ), ) ); } } if ( '' !== $fields ) { echo AIEO_DMM_UI::card_form( 'aieo_dmm_user_roles_save', 'user_roles_account', $form, '