From 2baebf9c3012c9a4e153e69344d6c0723f84399d Mon Sep 17 00:00:00 2001 From: Anthony Correa Date: Thu, 2 Apr 2026 11:28:08 -0500 Subject: [PATCH] Add officials manager role and event officials column --- includes/sp-event-quick-edit-officials.php | 99 +++++- includes/sp-officials-manager-role.php | 371 +++++++++++++++++++++ tonys-sportspress-enhancements.php | 3 + 3 files changed, 471 insertions(+), 2 deletions(-) create mode 100644 includes/sp-officials-manager-role.php diff --git a/includes/sp-event-quick-edit-officials.php b/includes/sp-event-quick-edit-officials.php index 14c11f3..0ed76b9 100644 --- a/includes/sp-event-quick-edit-officials.php +++ b/includes/sp-event-quick-edit-officials.php @@ -7,6 +7,89 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } +/** + * Add an Officials column to the event admin list. + * + * @param array $columns Existing columns. + * @return array + */ +function tony_sportspress_event_add_officials_column( $columns ) { + $updated = array(); + + foreach ( $columns as $key => $label ) { + $updated[ $key ] = $label; + + if ( 'sp_team' === $key ) { + $updated['tony_sp_officials'] = esc_html__( 'Officials', 'tonys-sportspress-enhancements' ); + } + } + + if ( ! isset( $updated['tony_sp_officials'] ) ) { + $updated['tony_sp_officials'] = esc_html__( 'Officials', 'tonys-sportspress-enhancements' ); + } + + return $updated; +} +add_filter( 'manage_edit-sp_event_columns', 'tony_sportspress_event_add_officials_column', 20 ); + +/** + * Build a display-ready officials map for an event. + * + * @param int $post_id Post ID. + * @return array + */ +function tony_sportspress_event_get_officials_display( $post_id ) { + $officials_by_duty = get_post_meta( $post_id, 'sp_officials', true ); + if ( ! is_array( $officials_by_duty ) || empty( $officials_by_duty ) ) { + return array(); + } + + $duties = get_terms( + array( + 'taxonomy' => 'sp_duty', + 'hide_empty' => false, + ) + ); + + $duty_names = array(); + if ( is_array( $duties ) ) { + foreach ( $duties as $duty ) { + if ( isset( $duty->term_id, $duty->name ) ) { + $duty_names[ (int) $duty->term_id ] = $duty->name; + } + } + } + + $rows = array(); + foreach ( $officials_by_duty as $duty_id => $official_ids ) { + $duty_id = absint( $duty_id ); + $official_ids = array_filter( array_map( 'absint', (array) $official_ids ) ); + + if ( $duty_id <= 0 || empty( $official_ids ) ) { + continue; + } + + $names = array(); + foreach ( $official_ids as $official_id ) { + $title = get_the_title( $official_id ); + if ( is_string( $title ) && '' !== $title ) { + $names[] = $title; + } + } + + if ( empty( $names ) ) { + continue; + } + + $rows[] = array( + 'name' => isset( $duty_names[ $duty_id ] ) ? $duty_names[ $duty_id ] : (string) $duty_id, + 'officials' => $names, + ); + } + + return $rows; +} + /** * Print hidden officials data on each event row for quick edit prefill. * @@ -14,7 +97,7 @@ if ( ! defined( 'ABSPATH' ) ) { * @param int $post_id Post ID. */ function tony_sportspress_event_quick_edit_officials_row_data( $column, $post_id ) { - if ( 'sp_team' !== $column ) { + if ( 'tony_sp_officials' !== $column ) { return; } @@ -28,6 +111,18 @@ function tony_sportspress_event_quick_edit_officials_row_data( $column, $post_id $serialized = '{}'; } + $rows = tony_sportspress_event_get_officials_display( $post_id ); + if ( empty( $rows ) ) { + echo '—'; + } else { + foreach ( $rows as $row ) { + echo '
'; + echo '' . esc_html( $row['name'] ) . ': '; + echo esc_html( implode( ', ', $row['officials'] ) ); + echo '
'; + } + } + echo ''; } add_action( 'manage_sp_event_posts_custom_column', 'tony_sportspress_event_quick_edit_officials_row_data', 20, 2 ); @@ -39,7 +134,7 @@ add_action( 'manage_sp_event_posts_custom_column', 'tony_sportspress_event_quick * @param string $post_type Post type key. */ function tony_sportspress_event_quick_edit_officials_field( $column_name, $post_type ) { - if ( 'sp_event' !== $post_type || 'sp_team' !== $column_name ) { + if ( 'sp_event' !== $post_type || 'tony_sp_officials' !== $column_name ) { return; } diff --git a/includes/sp-officials-manager-role.php b/includes/sp-officials-manager-role.php new file mode 100644 index 0000000..6b1523a --- /dev/null +++ b/includes/sp-officials-manager-role.php @@ -0,0 +1,371 @@ + + */ +function tony_sportspress_get_officials_manager_caps() { + $caps = array( + 'read' => true, + 'edit_posts' => true, + 'delete_posts' => true, + 'edit_published_posts' => true, + ); + + foreach ( tony_sportspress_build_post_type_caps( 'sp_official', 'sp_officials' ) as $cap ) { + $caps[ $cap ] = true; + } + + // Allow access to the event list and quick edit for assignments, without full event management. + $caps['read_sp_event'] = true; + $caps['edit_sp_event'] = true; + $caps['edit_sp_events'] = true; + $caps['edit_others_sp_events'] = true; + $caps['edit_published_sp_events'] = true; + $caps['read_private_sp_events'] = true; + + return $caps; +} + +/** + * Get the capabilities managed for the officials manager role. + * + * @return string[] + */ +function tony_sportspress_get_officials_manager_managed_caps() { + return array_keys( tony_sportspress_get_officials_manager_caps() ); +} + +/** + * Grant custom official caps to existing roles that already have matching event caps. + * + * This preserves existing access after moving officials off the `sp_event` capability type. + * + * @return void + */ +function tony_sportspress_sync_official_caps_to_existing_roles() { + global $wp_roles; + + if ( ! class_exists( 'WP_Roles' ) ) { + return; + } + + if ( ! isset( $wp_roles ) ) { + $wp_roles = wp_roles(); + } + + if ( ! $wp_roles instanceof WP_Roles ) { + return; + } + + $cap_map = array( + 'edit_sp_event' => 'edit_sp_official', + 'read_sp_event' => 'read_sp_official', + 'delete_sp_event' => 'delete_sp_official', + 'edit_sp_events' => 'edit_sp_officials', + 'edit_others_sp_events' => 'edit_others_sp_officials', + 'publish_sp_events' => 'publish_sp_officials', + 'read_private_sp_events' => 'read_private_sp_officials', + 'delete_sp_events' => 'delete_sp_officials', + 'delete_private_sp_events' => 'delete_private_sp_officials', + 'delete_published_sp_events' => 'delete_published_sp_officials', + 'delete_others_sp_events' => 'delete_others_sp_officials', + 'edit_private_sp_events' => 'edit_private_sp_officials', + 'edit_published_sp_events' => 'edit_published_sp_officials', + ); + + foreach ( $wp_roles->role_objects as $role ) { + if ( ! $role instanceof WP_Role ) { + continue; + } + + foreach ( $cap_map as $event_cap => $official_cap ) { + if ( $role->has_cap( $event_cap ) ) { + $role->add_cap( $official_cap ); + } + } + } +} + +/** + * Create or update the officials manager role. + * + * @return void + */ +function tony_sportspress_sync_officials_manager_roles() { + $role = get_role( TONY_SPORTSPRESS_OFFICIALS_MANAGER_ROLE ); + + if ( ! $role ) { + $role = add_role( + TONY_SPORTSPRESS_OFFICIALS_MANAGER_ROLE, + __( 'Officials Manager', 'tonys-sportspress-enhancements' ), + array() + ); + } + + if ( ! $role instanceof WP_Role ) { + return; + } + + $desired_caps = tony_sportspress_get_officials_manager_caps(); + + foreach ( tony_sportspress_get_officials_manager_managed_caps() as $cap ) { + if ( ! empty( $desired_caps[ $cap ] ) ) { + $role->add_cap( $cap ); + } else { + $role->remove_cap( $cap ); + } + } + + tony_sportspress_sync_official_caps_to_existing_roles(); +} +add_action( 'init', 'tony_sportspress_sync_officials_manager_roles' ); + +/** + * Assign custom capabilities to the officials post type. + * + * @param array $args Post type registration args. + * @return array + */ +function tony_sportspress_officials_post_type_caps( $args ) { + $args['capability_type'] = array( 'sp_official', 'sp_officials' ); + $args['map_meta_cap'] = true; + $args['capabilities'] = array( + 'create_posts' => 'edit_sp_officials', + ); + + return $args; +} +add_filter( 'sportspress_register_post_type_official', 'tony_sportspress_officials_post_type_caps' ); + +/** + * Determine whether the current user should be restricted to assignment-only event access. + * + * @return bool + */ +function tony_sportspress_is_officials_manager_user() { + $user = wp_get_current_user(); + + if ( ! $user instanceof WP_User || empty( $user->roles ) ) { + return false; + } + + if ( ! in_array( TONY_SPORTSPRESS_OFFICIALS_MANAGER_ROLE, $user->roles, true ) ) { + return false; + } + + if ( current_user_can( 'manage_options' ) || current_user_can( 'manage_sportspress' ) ) { + return false; + } + + return true; +} + +/** + * Prevent assignment-only users from opening full event edit screens. + * + * @return void + */ +function tony_sportspress_lock_event_editor_for_officials_manager() { + global $pagenow; + + if ( ! is_admin() || ! tony_sportspress_is_officials_manager_user() ) { + return; + } + + if ( 'post-new.php' === $pagenow ) { + $post_type = isset( $_GET['post_type'] ) ? sanitize_key( wp_unslash( $_GET['post_type'] ) ) : 'post'; + + if ( 'sp_event' === $post_type ) { + wp_safe_redirect( admin_url( 'edit.php?post_type=sp_event&tse_event_editor_locked=1' ) ); + exit; + } + } + + if ( 'post.php' !== $pagenow ) { + return; + } + + $post_id = isset( $_GET['post'] ) ? absint( wp_unslash( $_GET['post'] ) ) : 0; + if ( $post_id > 0 && 'sp_event' === get_post_type( $post_id ) ) { + wp_safe_redirect( admin_url( 'edit.php?post_type=sp_event&tse_event_editor_locked=1' ) ); + exit; + } +} +add_action( 'admin_init', 'tony_sportspress_lock_event_editor_for_officials_manager' ); + +/** + * Show an admin notice when event editor access is blocked. + * + * @return void + */ +function tony_sportspress_officials_manager_admin_notice() { + if ( ! tony_sportspress_is_officials_manager_user() ) { + return; + } + + if ( empty( $_GET['tse_event_editor_locked'] ) ) { + return; + } + + echo '

' . esc_html__( 'Officials Managers can assign officials from the events list via Quick Edit, but cannot open the full event editor.', 'tonys-sportspress-enhancements' ) . '

'; +} +add_action( 'admin_notices', 'tony_sportspress_officials_manager_admin_notice' ); + +/** + * Remove event row actions that would expose broader editing. + * + * @param array $actions Row actions. + * @param WP_Post $post Post object. + * @return array + */ +function tony_sportspress_limit_event_row_actions_for_officials_manager( $actions, $post ) { + if ( ! tony_sportspress_is_officials_manager_user() || ! $post instanceof WP_Post || 'sp_event' !== $post->post_type ) { + return $actions; + } + + $allowed = array(); + + if ( isset( $actions['inline hide-if-no-js'] ) ) { + $allowed['inline hide-if-no-js'] = $actions['inline hide-if-no-js']; + } + + if ( isset( $actions['view'] ) ) { + $allowed['view'] = $actions['view']; + } + + return $allowed; +} +add_filter( 'post_row_actions', 'tony_sportspress_limit_event_row_actions_for_officials_manager', 10, 2 ); + +/** + * Remove bulk actions from the events list for assignment-only users. + * + * @param array $actions Bulk actions. + * @return array + */ +function tony_sportspress_limit_event_bulk_actions_for_officials_manager( $actions ) { + if ( ! tony_sportspress_is_officials_manager_user() ) { + return $actions; + } + + return array(); +} +add_filter( 'bulk_actions-edit-sp_event', 'tony_sportspress_limit_event_bulk_actions_for_officials_manager' ); + +/** + * Remove the Add New events submenu for assignment-only users. + * + * @return void + */ +function tony_sportspress_limit_event_admin_menu_for_officials_manager() { + if ( ! tony_sportspress_is_officials_manager_user() ) { + return; + } + + remove_submenu_page( 'edit.php?post_type=sp_event', 'post-new.php?post_type=sp_event' ); +} +add_action( 'admin_menu', 'tony_sportspress_limit_event_admin_menu_for_officials_manager', 99 ); + +/** + * Hide event Add New buttons on the list screen for assignment-only users. + * + * @return void + */ +function tony_sportspress_limit_event_admin_ui_for_officials_manager() { + $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; + if ( ! $screen || 'edit-sp_event' !== $screen->id || ! tony_sportspress_is_officials_manager_user() ) { + return; + } + ?> + +