availabilityController = new AvailabilityController( $availability, $offerings ); $this->lessonController = new LessonController( $bookings, $payments ); $this->offeringController = new OfferingController( $offerings ); $this->questionController = new QuestionController( $questions, $offerings ); $this->policyController = new PolicyController( $policies, $policyVersions, $policyService ); $this->registrationController = new RegistrationController( $invites ); $this->groupClassController = new GroupClassController( $enrollments, $offerings ); $this->studentController = new StudentController( $bookings, $availability, $offerings, $enrollments, $resolver ); $this->settings = $settings; $this->accessSettings = new AccessSettings(); $this->paymentController = new PaymentController( $payments, $paymentService ); $this->paymentReportController = new PaymentReportController( $payments ); } public function register(): void { add_action( 'admin_menu', [ $this, 'addPages' ] ); add_action( 'admin_post_' . PaymentReportController::EXPORT_ACTION, [ $this->paymentReportController, 'export' ] ); } public function addPages(): void { $this->addStudioSeparators(); // Studio-wide dashboard: all upcoming lessons across instructors. add_menu_page( __( 'Scheduler', 'unsupervised-schedular' ), __( 'Scheduler', 'unsupervised-schedular' ), RoleManager::CAP_VIEW_ALL_LESSONS, 'us-scheduler', [ $this->lessonController, 'renderAdminDashboard' ], 'dashicons-calendar-alt', 40 ); // Instructor: manage their own availability. add_menu_page( __( 'My Availability', 'unsupervised-schedular' ), __( 'My Availability', 'unsupervised-schedular' ), RoleManager::CAP_MANAGE_AVAILABILITY, 'us-availability', [ $this->availabilityController, 'renderPage' ], 'dashicons-clock', 41 ); // Studio admin / instructor: manage offerings. add_menu_page( __( 'Offerings', 'unsupervised-schedular' ), __( 'Offerings', 'unsupervised-schedular' ), RoleManager::CAP_MANAGE_OFFERINGS, 'us-offerings', [ $this->offeringController, 'renderPage' ], 'dashicons-tag', 33 ); // Studio admin / instructor: manage per-offering intake questions. add_submenu_page( 'us-offerings', __( 'Questions', 'unsupervised-schedular' ), __( 'Questions', 'unsupervised-schedular' ), RoleManager::CAP_MANAGE_QUESTIONS, 'us-questions', [ $this->questionController, 'renderPage' ] ); // Studio admin: draft, version, and publish policies. add_menu_page( __( 'Policies', 'unsupervised-schedular' ), __( 'Policies', 'unsupervised-schedular' ), RoleManager::CAP_MANAGE_POLICIES, 'us-policies', [ $this->policyController, 'renderPage' ], 'dashicons-text-page', 31 ); // Studio admin: invite students to register. add_menu_page( __( 'Invites', 'unsupervised-schedular' ), __( 'Invites', 'unsupervised-schedular' ), RoleManager::CAP_MANAGE_STUDENTS, 'us-invites', [ $this->registrationController, 'renderPage' ], 'dashicons-email', 32 ); // Studio admin: all group-class enrolments. add_menu_page( __( 'Group Classes', 'unsupervised-schedular' ), __( 'Group Classes', 'unsupervised-schedular' ), RoleManager::CAP_VIEW_ALL_LESSONS, 'us-group-classes', [ $this->groupClassController, 'renderPage' ], 'dashicons-groups', 36 ); // Studio admin: browse students and their activity. add_menu_page( __( 'Students', 'unsupervised-schedular' ), __( 'Students', 'unsupervised-schedular' ), RoleManager::CAP_MANAGE_STUDENTS, 'us-students', [ $this->studentController, 'renderPage' ], 'dashicons-id', 35 ); // Studio admin: confirm pending (e-transfer) payments. add_menu_page( __( 'Payments', 'unsupervised-schedular' ), __( 'Payments', 'unsupervised-schedular' ), RoleManager::CAP_MANAGE_BILLING, 'us-payments', [ $this->paymentController, 'renderPage' ], 'dashicons-money-alt', 37 ); // Studio admin (all) / instructor (own): monthly payments report with HST. // Gated on export so instructors — who lack manage_billing — can still see it. add_menu_page( __( 'Payment Report', 'unsupervised-schedular' ), __( 'Payment Report', 'unsupervised-schedular' ), RoleManager::CAP_EXPORT_PAYMENTS, 'us-reports', [ $this->paymentReportController, 'renderPage' ], 'dashicons-chart-bar', 38 ); // Studio admin: Stripe credentials and billing settings. add_menu_page( __( 'Studio Settings', 'unsupervised-schedular' ), __( 'Studio Settings', 'unsupervised-schedular' ), RoleManager::CAP_MANAGE_BILLING, 'us-settings', [ $this->settings, 'renderPage' ], 'dashicons-admin-settings', 30 ); // Site owner: whether WordPress administrators are studio admins / instructors. // Gated on the core manage_options capability — never the plugin's own grants — // so an administrator can always reach it to re-enable a disabled grant. add_menu_page( __( 'Access', 'unsupervised-schedular' ), __( 'Access', 'unsupervised-schedular' ), 'manage_options', 'us-access', [ $this->accessSettings, 'renderPage' ], 'dashicons-admin-network', 30.5 ); // Instructor: view their upcoming lessons. add_menu_page( __( 'My Lessons', 'unsupervised-schedular' ), __( 'My Lessons', 'unsupervised-schedular' ), RoleManager::CAP_VIEW_LESSONS, 'us-my-lessons', [ $this->lessonController, 'renderInstructorLessons' ], 'dashicons-welcome-learn-more', 42 ); } /** * Insert sidebar separators around the studio menus so they sit visually * apart from the core WordPress items and split into three sections — * mirroring the dividers core uses. Each separator is only added when the user * can see a menu in the following section, to avoid orphaned dividers. * * Layout: [29] · setup (Settings/Policies/Invites/Offerings) · [34] · * people & money (Students/Group Classes/Payments/Payment Report) · [39] · * operations (Scheduler) and instructor menus. */ private function addStudioSeparators(): void { $this->addSeparatorAt( 29, $this->userSeesStudioMenu() ); $this->addSeparatorAt( 34, $this->userSeesPeopleSection() ); $this->addSeparatorAt( 39, current_user_can( RoleManager::CAP_VIEW_ALL_LESSONS ) ); } private function addSeparatorAt( int $position, bool $visible ): void { if ( ! $visible ) { return; } global $menu; if ( ! is_array( $menu ) || isset( $menu[ $position ] ) ) { return; } // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- writing to $menu is the supported way to add an admin sidebar separator. $menu[ $position ] = [ '', 'read', 'us-studio-separator-' . $position, '', 'wp-menu-separator' ]; } private function userSeesPeopleSection(): bool { return current_user_can( RoleManager::CAP_MANAGE_STUDENTS ) || current_user_can( RoleManager::CAP_VIEW_ALL_LESSONS ) || current_user_can( RoleManager::CAP_MANAGE_BILLING ); } private function userSeesStudioMenu(): bool { // Administrators always see the Access page, so the leading separator // should show for them even if both capability grants are disabled. if ( current_user_can( 'manage_options' ) ) { return true; } $caps = [ RoleManager::CAP_VIEW_ALL_LESSONS, RoleManager::CAP_VIEW_LESSONS, RoleManager::CAP_MANAGE_AVAILABILITY, RoleManager::CAP_MANAGE_OFFERINGS, RoleManager::CAP_MANAGE_QUESTIONS, RoleManager::CAP_MANAGE_POLICIES, RoleManager::CAP_MANAGE_STUDENTS, RoleManager::CAP_MANAGE_BILLING, ]; foreach ( $caps as $cap ) { if ( current_user_can( $cap ) ) { return true; } } return false; } }