Service Categories > All Services > * All Staff Members > All Bookings > Settings * - Taxonomy page parent/submenu highlight fixed via filter hooks * - Quick Actions link updated (no calendar page) * - Calendar CSS/JS loaded on dashboard hook */ class AumReserva_Admin { private static ?AumReserva_Admin $instance = null; public static function instance(): AumReserva_Admin { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { add_action('admin_menu', [$this, 'register_menus']); add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']); add_filter('plugin_action_links_' . AUMRESERVA_PLUGIN_BASENAME, [$this, 'plugin_action_links']); add_filter('parent_file', [$this, 'fix_taxonomy_parent_file']); add_filter('submenu_file', [$this, 'fix_taxonomy_submenu_file']); } // ------------------------------------------------------------------------- // Menus // ------------------------------------------------------------------------- public function register_menus(): void { // Pending bookings badge — shown on top menu and All Bookings submenu $pending_count = AumReserva_DB::count_bookings(['status' => 'pending']); $badge = $pending_count > 0 ? sprintf( ' %s', $pending_count, $pending_count > 99 ? '99+' : $pending_count ) : ''; add_menu_page( __('Reserva Booking', 'aumreserva'), __('Reserva', 'aumreserva') . $badge, 'manage_options', 'aumreserva-booking', [$this, 'render_dashboard'], 'dashicons-calendar-alt', 26 ); add_submenu_page( 'aumreserva-booking', __('Dashboard', 'aumreserva'), __('Dashboard', 'aumreserva'), 'manage_options', 'aumreserva-booking', [$this, 'render_dashboard'] ); add_submenu_page( 'aumreserva-booking', __('Service Categories', 'aumreserva'), __('Service Categories', 'aumreserva'), 'manage_options', 'edit-tags.php?taxonomy=aumreserva_service_cat&post_type=aumreserva_service' ); add_submenu_page( 'aumreserva-booking', AumReserva_CPT::location_label(true), AumReserva_CPT::location_label(true), 'manage_options', 'edit-tags.php?taxonomy=aumreserva_location&post_type=aumreserva_staff' ); add_submenu_page( 'aumreserva-booking', __('All Bookings', 'aumreserva'), __('All Bookings', 'aumreserva') . $badge, 'manage_options', 'aumreserva-bookings-list', [AumReserva_Bookings_List::instance(), 'render'] ); add_submenu_page( 'aumreserva-booking', __('Settings', 'aumreserva'), __('Settings', 'aumreserva'), 'manage_options', 'aumreserva-settings', [AumReserva_Settings::instance(), 'render'] ); // Staff-role: my bookings page (separate top-level, visible only to aumreserva_staff role) add_menu_page( __('My Bookings', 'aumreserva'), __('My Bookings', 'aumreserva'), 'aumreserva_view_own_bookings', 'aumreserva-my-bookings', [$this, 'render_staff_bookings'], 'dashicons-calendar-alt', 26 ); // Re-sort submenu after WordPress adds CPT auto-entries add_action('admin_menu', [$this, 'sort_submenu'], 999); } /** * Reorders the Reserva sub-menu using the global $submenu array. * Desired order: * Dashboard > Service Categories > All Services (CPT) > * All Staff Members (CPT) > All Bookings > Settings */ public function sort_submenu(): void { global $submenu; if (empty($submenu['aumreserva-booking'])) { return; } $order = [ 'aumreserva-booking', // Dashboard 'edit-tags.php?taxonomy=aumreserva_service_cat&post_type=aumreserva_service', // Service Categories 'edit.php?post_type=aumreserva_service', // All Services (CPT auto) 'post-new.php?post_type=aumreserva_service', // Add Service (CPT auto) 'edit.php?post_type=aumreserva_staff', // All Staff Members (CPT auto) 'post-new.php?post_type=aumreserva_staff', // Add Staff (CPT auto) 'edit-tags.php?taxonomy=aumreserva_location&post_type=aumreserva_staff', // Locations 'aumreserva-bookings-list', // All Bookings 'aumreserva-settings', // Settings ]; $indexed = []; $rest = []; foreach ($submenu['aumreserva-booking'] as $item) { $slug = $item[2]; $pos = array_search($slug, $order, true); if ($pos !== false) { $indexed[$pos] = $item; } else { $rest[] = $item; } } ksort($indexed); /* * Rewriting $submenu is the only way to order submenu entries: the position argument * of `add_submenu_page()` is not honoured consistently within one parent, and core * exposes no API for reordering submenus after registration. Only this plugin's own * parent key is reordered; nothing is added, removed, or read from another plugin's * key. * * The annotation below is load-bearing: with it removed, this line reports * GlobalVariablesOverride. A scan that reports it as unnecessary is a scan restricted * to the security and SQL sniffs, which do not include this one. */ $submenu['aumreserva-booking'] = array_values(array_merge($indexed, $rest)); // phpcs:ignore WordPress.WP.GlobalVariablesOverride } // ------------------------------------------------------------------------- // Taxonomy menu highlight fix // ------------------------------------------------------------------------- public function fix_taxonomy_parent_file(?string $parent_file): ?string { global $current_screen; if ( isset($current_screen->taxonomy) && in_array($current_screen->taxonomy, ['aumreserva_service_cat', 'aumreserva_location'], true) ) { return 'aumreserva-booking'; } return $parent_file; } public function fix_taxonomy_submenu_file(?string $submenu_file): ?string { global $current_screen; if (isset($current_screen->taxonomy) && $current_screen->taxonomy === 'aumreserva_service_cat') { return 'edit-tags.php?taxonomy=aumreserva_service_cat&post_type=aumreserva_service'; } if (isset($current_screen->taxonomy) && $current_screen->taxonomy === 'aumreserva_location') { return 'edit-tags.php?taxonomy=aumreserva_location&post_type=aumreserva_staff'; } return $submenu_file; } // ------------------------------------------------------------------------- // Dashboard (with inline calendar) // ------------------------------------------------------------------------- public function render_dashboard(): void { $total = AumReserva_DB::count_bookings(); $pending = AumReserva_DB::count_bookings(['status' => 'pending']); $confirmed = AumReserva_DB::count_bookings(['status' => 'confirmed']); $today = AumReserva_DB::count_bookings(['booking_date' => gmdate('Y-m-d')]); $recent = AumReserva_DB::get_bookings(['per_page' => 8, 'orderby' => 'created_at', 'order' => 'DESC']); ?>


customer_name); ?> service_id)); ?> booking_date . ' ' . AumReserva_Helpers::format_time($b->start_time)); ?> status)); ?> status === 'pending') : ?>

render_inline(); ?>

' . esc_html__('No staff profile linked to your account.', 'aumreserva') . '

'; return; } $bookings = AumReserva_DB::get_bookings([ 'staff_id' => $staff_id, 'per_page' => 50, 'orderby' => 'booking_date', 'order' => 'ASC', 'date_from' => gmdate('Y-m-d'), ]); ?>


customer_name); ?>
customer_phone); ?>
service_id)); ?> booking_date))); ?> start_time) . ' – ' . AumReserva_Helpers::format_time($b->end_time)); ?> status)); ?> customer_note); ?>
post_type ?? '', ['aumreserva_service', 'aumreserva_staff'], true); if (! $is_aumreserva_page && ! $is_aumreserva_cpt) { return; } $css_path = AUMRESERVA_PLUGIN_DIR . 'admin/css/aumreserva-admin.css'; $js_path = AUMRESERVA_PLUGIN_DIR . 'admin/js/aumreserva-admin.js'; wp_enqueue_style( 'aumreserva-admin', AUMRESERVA_PLUGIN_URL . 'admin/css/aumreserva-admin.css', [], file_exists($css_path) ? filemtime($css_path) : AUMRESERVA_VERSION ); wp_enqueue_script( 'aumreserva-admin', AUMRESERVA_PLUGIN_URL . 'admin/js/aumreserva-admin.js', ['jquery', 'wp-util'], file_exists($js_path) ? filemtime($js_path) : AUMRESERVA_VERSION, true ); wp_localize_script('aumreserva-admin', 'aumreservaAdmin', [ 'nonce' => wp_create_nonce('aumreserva_admin_nonce'), 'ajax_url' => admin_url('admin-ajax.php'), 'rest_url' => esc_url_raw(rest_url('aumreserva/v1/')), 'i18n' => [ 'confirm_action' => __('Are you sure?', 'aumreserva'), 'confirmed' => __('Confirmed', 'aumreserva'), 'error' => __('An error occurred. Please try again.', 'aumreserva'), ], ]); // Calendar assets — only needed on dashboard if ($current_page === 'aumreserva-booking') { $cal_css = AUMRESERVA_PLUGIN_DIR . 'admin/css/aumreserva-calendar.css'; $cal_js = AUMRESERVA_PLUGIN_DIR . 'admin/js/aumreserva-calendar.js'; wp_enqueue_style( 'aumreserva-calendar', AUMRESERVA_PLUGIN_URL . 'admin/css/aumreserva-calendar.css', [], file_exists($cal_css) ? filemtime($cal_css) : AUMRESERVA_VERSION ); wp_enqueue_script( 'aumreserva-calendar', AUMRESERVA_PLUGIN_URL . 'admin/js/aumreserva-calendar.js', ['jquery'], file_exists($cal_js) ? filemtime($cal_js) : AUMRESERVA_VERSION, true ); wp_localize_script('aumreserva-calendar', 'aumreservaCalendar', [ 'nonce' => wp_create_nonce('aumreserva_admin_nonce'), 'rest_url' => esc_url_raw(rest_url('aumreserva/v1/')), 'ajax_url' => admin_url('admin-ajax.php'), 'work_start' => AumReserva_Helpers::opt('aumreserva_working_start', '09:00'), 'work_end' => AumReserva_Helpers::opt('aumreserva_working_end', '18:00'), 'slot_duration' => (int) AumReserva_Helpers::opt('aumreserva_time_slot_duration', 60), 'time_format' => AumReserva_Helpers::opt('aumreserva_time_format', '24h'), 'locale' => str_replace('_', '-', get_locale()), 'timezone' => wp_timezone_string(), 'i18n' => [ 'today' => __('Today', 'aumreserva'), 'day' => __('Day', 'aumreserva'), 'week' => __('Week', 'aumreserva'), /* * `no_staff` used to label two different things, and one of them was a lie. * The calendar always puts a leading column for bookings with nobody assigned, * and it labelled that column with this same string — so a site with four * named staff showed a fifth column headed "No staff found." right beside * them. The empty state and the unassigned column are not the same sentence. */ 'no_staff' => __('No staff found.', 'aumreserva'), 'unassigned' => __('Unassigned', 'aumreserva'), 'loading' => __('Loading…', 'aumreserva'), 'confirm_delete' => __('Cancel this booking?', 'aumreserva'), ], 'staff' => $this->get_calendar_staff_data(), 'statuses' => AumReserva_Helpers::booking_statuses(), ]); } // Service / staff edit screens: meta-field layout and the unlink handler. if ($is_aumreserva_cpt) { $meta_css = AUMRESERVA_PLUGIN_DIR . 'admin/css/aumreserva-meta-fields.css'; $meta_js = AUMRESERVA_PLUGIN_DIR . 'admin/js/aumreserva-meta-fields.js'; wp_enqueue_style( 'aumreserva-meta-fields', AUMRESERVA_PLUGIN_URL . 'admin/css/aumreserva-meta-fields.css', [], file_exists($meta_css) ? filemtime($meta_css) : AUMRESERVA_VERSION ); wp_enqueue_script( 'aumreserva-meta-fields', AUMRESERVA_PLUGIN_URL . 'admin/js/aumreserva-meta-fields.js', ['jquery'], file_exists($meta_js) ? filemtime($meta_js) : AUMRESERVA_VERSION, true ); wp_localize_script('aumreserva-meta-fields', 'aumreservaMetaFields', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aumreserva_admin_nonce'), 'i18n' => [ 'confirm_unlink' => __('Unlink this account from the staff member?', 'aumreserva'), ], ]); } // Settings screen: the single button that creates the "My Bookings" page. if ($current_page === 'aumreserva-settings') { $settings_js = AUMRESERVA_PLUGIN_DIR . 'admin/js/aumreserva-settings.js'; wp_enqueue_script( 'aumreserva-settings', AUMRESERVA_PLUGIN_URL . 'admin/js/aumreserva-settings.js', ['jquery'], file_exists($settings_js) ? filemtime($settings_js) : AUMRESERVA_VERSION, true ); wp_localize_script('aumreserva-settings', 'aumreservaSettings', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aumreserva_create_page'), 'i18n' => [ 'creating' => __('Creating…', 'aumreserva'), 'create' => __('Create My Bookings Page', 'aumreserva'), 'error' => __('Error creating page.', 'aumreserva'), ], ]); } } private function get_calendar_staff_data(): array { $posts = get_posts([ 'post_type' => 'aumreserva_staff', 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC', ]); return array_map(fn($p) => [ 'id' => $p->ID, 'name' => $p->post_title, 'avatar_url' => get_the_post_thumbnail_url($p->ID, 'thumbnail') ?: '', 'status' => get_post_meta($p->ID, 'aumreserva_staff_status', true) ?: 'active', ], $posts); } // ------------------------------------------------------------------------- // Plugin action links // ------------------------------------------------------------------------- public function plugin_action_links(array $links): array { $settings_link = sprintf( '%s', esc_url(admin_url('admin.php?page=aumreserva-settings')), esc_html__('Settings', 'aumreserva') ); array_unshift($links, $settings_link); /* * The manual, for people who installed this from wordpress.org and have no other channel * to it. Appended rather than unshifted: Settings is what an administrator reaches for * first. Renders nothing when the manual is not published in their language. */ $manual = AumReserva_Docs::url(); if ('' !== $manual) { $links[] = sprintf( '%s', esc_url($manual), esc_html__('Manual', 'aumreserva') ); } return $links; } }