settingsCapability(), 'abece-booking-settings' ], [ 'Getting Started', 'manage_options', 'abece-booking-getting-started' ], [ 'System Status', 'manage_options', 'abece-booking-system-status' ], ]; if ( \ABeCe\Booking\Edition\Entitlements::current()->has('memberships') && ! \ABeCe\Booking\Service\MembershipModeService::enabled() ) { $entries=array_values(array_filter($entries,static fn($entry)=>$entry[0]!=='Members')); } $unavailable=array_values(array_filter($entries,fn($entry)=>($feature=$this->slugFeature($entry[2]))&&!\ABeCe\Booking\Edition\Entitlements::current()->has($feature))); $entries=array_values(array_filter($entries,fn($entry)=>!($feature=$this->slugFeature($entry[2]))||\ABeCe\Booking\Edition\Entitlements::current()->has($feature))); $parent = 'abece-booking'; $top_capability = Capabilities::MANAGE_BOOKINGS; foreach ( $entries as [ , $cap, $slug ] ) { if ( current_user_can( $cap ) ) { $top_capability = $cap; $this->landingSlug = $slug; break; } } $this->pages[] = add_menu_page( __( 'ABeCe Booking', 'abece-booking' ), __( 'ABeCe Booking', 'abece-booking' ), $top_capability, $parent, [ $this, 'render' ], 'dashicons-calendar-alt', 26 ); // Literal source strings remain extractable for localization. $labels = [ 'Reservations' => __( 'Reservations', 'abece-booking' ), 'Resources' => __( 'Resources', 'abece-booking' ), 'Sports' => __( 'Sports', 'abece-booking' ), 'Price lists' => __( 'Price lists', 'abece-booking' ), 'Members' => __( 'Members', 'abece-booking' ), 'Credits' => __( 'Credits', 'abece-booking' ), 'Operations' => __( 'Operations', 'abece-booking' ), 'Seasons' => __( 'Seasons', 'abece-booking' ), 'Statistics' => __( 'Statistics', 'abece-booking' ), 'Settings' => __( 'Settings', 'abece-booking' ), 'Outstanding amounts'=>__('Outstanding amounts','abece-booking'),'Editions and features'=>__('Editions and features','abece-booking'), 'Getting Started'=>__('Getting Started','abece-booking'),'System Status'=>__('System Status','abece-booking'), ]; foreach ( $entries as [ $label, $cap, $slug ] ) { $this->pages[] = add_submenu_page( $parent, $labels[ $label ], $labels[ $label ], $cap, $slug, [ $this, 'render' ] ); } foreach($unavailable as [$label,$cap,$slug])$this->addHiddenPage($labels[$label],$cap,$slug); // Retain direct links and capability protection for existing child pages. foreach ( [ [ __( 'Customer restrictions', 'abece-booking' ), Capabilities::MANAGE_BOOKINGS, 'abece-booking-customer-restrictions' ], [ __( 'External contacts', 'abece-booking' ), Capabilities::MANAGE_BOOKINGS, 'abece-booking-external-contacts' ], [ __( 'Credits', 'abece-booking' ), Capabilities::MANAGE_CREDIT, 'abece-booking-credits' ], [ __( 'Playing time limits', 'abece-booking' ), Capabilities::MANAGE_MEMBERS, 'abece-booking-usage-limits' ], [ __( 'Membership Types', 'abece-booking' ), Capabilities::MANAGE_MEMBERS, 'abece-booking-membership-types' ], [ __( 'Cancellation and penalties', 'abece-booking' ), Capabilities::MANAGE_CANCELLATION_POLICIES, 'abece-booking-cancellation-policies' ], [ __( 'Opening hours', 'abece-booking' ), Capabilities::MANAGE_RESOURCES, 'abece-booking-opening-hours' ], [ __( 'Operating exceptions', 'abece-booking' ), Capabilities::MANAGE_RESOURCES, 'abece-booking-schedule-exceptions' ], [ __( 'Resource blocks', 'abece-booking' ), Capabilities::MANAGE_RESOURCES, 'abece-booking-resource-blocks' ], ] as [ $label, $cap, $slug ] ) { if ( \ABeCe\Booking\Edition\Entitlements::current()->has('memberships') && ! \ABeCe\Booking\Service\MembershipModeService::enabled() && in_array($slug,['abece-booking-members','abece-booking-membership-types','abece-booking-usage-limits'],true) ) { continue; } $this->addHiddenPage( $label, $cap, $slug ); } } /** Hidden screens still need their registered title before WordPress renders admin-header.php. */ private function addHiddenPage( string $label, string $capability, string $slug ): void { $hook = add_submenu_page( '', $label, $label, $capability, $slug, [ $this, 'render' ] ); if ( is_string( $hook ) ) { $this->pages[] = $hook; add_action( 'load-' . $hook, static function () use ( $label ): void { $GLOBALS['title'] = $label; } ); } remove_submenu_page( '', $slug ); } public function render(): void { if ( ! current_user_can( $this->capability() ) ) { wp_die( esc_html__( 'You do not have permission to access this ABeCe Booking page.', 'abece-booking' ) ); } if(($feature=$this->slugFeature(self::requestedPage()))&&!\ABeCe\Booking\Edition\Entitlements::current()->has($feature)){echo '

'.esc_html__('This feature is not available in this edition.','abece-booking').'

';return;} echo '
'; } public function enqueue( string $hook ): void { if ( ! in_array( $hook, $this->pages, true ) || ! current_user_can( $this->capability() ) ) { return; } if(($feature=$this->slugFeature(self::requestedPage()))&&!\ABeCe\Booking\Edition\Entitlements::current()->has($feature))return; $path = dirname( $this->file ) . '/assets/build/'; if ( ! is_readable( $path . 'admin.asset.php' ) || ! is_readable( $path . 'admin.js' ) ) { add_action( 'admin_notices', static function (): void { echo '

' . esc_html__( 'The admin package is incomplete. Install the complete ABeCe Booking release ZIP.', 'abece-booking' ) . '

'; } ); return; } $asset = require $path . 'admin.asset.php'; $style = dirname( $this->file ) . '/admin-ui/admin.css'; $style_version = is_readable( $style ) ? substr( hash_file( 'sha256', $style ), 0, 16 ) : $asset['version']; wp_enqueue_script( 'abece-booking-admin', plugins_url( 'assets/build/admin.js', $this->file ), $asset['dependencies'], $asset['version'], true ); wp_enqueue_style( 'abece-booking-admin', plugins_url( 'admin-ui/admin.css', $this->file ), [], $style_version ); wp_enqueue_style( 'abece-booking-calendar', plugins_url( 'assets/build/calendar.css', $this->file ), [], $asset['version'] ); wp_set_script_translations( 'abece-booking-admin', 'abece-booking', dirname( $this->file ) . '/languages' ); $config = [ 'entitlements'=>\ABeCe\Booking\Edition\Entitlements::current()->manifest(), 'restUrl' => rest_url( 'abece-booking/v1/' ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'page' => $this->page(), 'timezone' => wp_timezone()->getName(), 'locale' => \ABeCe\Booking\Support\LocaleContext::current(), 'requestLocale'=>'user', 'timeFormat'=>get_option('time_format'), 'membershipsEnabled' => \ABeCe\Booking\Service\MembershipModeService::enabled(), 'membershipSettingsUrl' => admin_url( 'admin.php?page=abece-booking-membership-types' ), 'usageLimitsUrl'=>admin_url('admin.php?page=abece-booking-usage-limits'), 'membersUrl' => admin_url( 'admin.php?page=abece-booking-members' ), 'pricesUrl' => admin_url( 'admin.php?page=abece-booking-price-lists' ), 'canViewFinance'=>current_user_can(Capabilities::MANAGE_CREDIT)&&\ABeCe\Booking\Edition\Entitlements::current()->has('financial_reporting'), 'canManageBookings'=>current_user_can(Capabilities::MANAGE_BOOKINGS), 'canManageMembers'=>current_user_can(Capabilities::MANAGE_MEMBERS), 'canManagePrices' => current_user_can( Capabilities::MANAGE_PRICES ), 'canManageCalendarSettings' => current_user_can( Capabilities::MANAGE_RESOURCES ), 'operationsUrl' => admin_url( 'admin.php?page=abece-booking-operations' ), ]; wp_add_inline_script( 'abece-booking-admin', 'window.ABeCeBookingFeatures = '.wp_json_encode($config['entitlements']['features']).';window.ABeCeBookingConfig = ' . wp_json_encode( $config, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ';', 'before' ); } /** Read-only navigation; capabilities and entitlements are checked separately. */ private static function requestedPage(): string { return isset($_GET['page']) && is_string($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; } private static function requestedTab(): string { return isset($_GET['tab']) && is_string($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : ''; } private function page(): string { $slug = self::requestedPage(); if ($slug === 'abece-booking' && !current_user_can(Capabilities::MANAGE_RESOURCES)) $slug = $this->landingSlug; return match ( $slug ) { 'abece-booking-sports'=>'sports', 'abece-booking-editions'=>'editions', 'abece-booking-getting-started'=>'getting-started', 'abece-booking-system-status'=>'system-status', 'abece-booking-statistics' => 'statistics', 'abece-booking-customer-restrictions' => 'customer-restrictions', 'abece-booking-receivables' => 'receivables', 'abece-booking-external-contacts' => 'external-contacts', 'abece-booking-resources' => 'resources', 'abece-booking-bookings' => 'bookings', 'abece-booking-price-lists' => 'price-lists', 'abece-booking-credits' => 'credits', 'abece-booking-cancellation-policies' => 'cancellation-policies', 'abece-booking-seasons' => 'seasons', 'abece-booking-members' => 'members', 'abece-booking-usage-limits' => 'usage-limits', 'abece-booking-membership-types' => 'membership-types', 'abece-booking-settings' => 'settings', 'abece-booking-operations' => match ( self::requestedTab() ) { 'exceptions' => 'schedule-exceptions', 'blocks' => 'resource-blocks', default => 'opening-hours' }, 'abece-booking-opening-hours' => 'opening-hours', 'abece-booking-schedule-exceptions' => 'schedule-exceptions', 'abece-booking-resource-blocks' => 'resource-blocks', default => current_user_can( Capabilities::MANAGE_RESOURCES ) ? 'sports' : ( current_user_can( Capabilities::MANAGE_MEMBERS ) ? 'seasons' : 'bookings' ), }; } private function capability(): string { if(in_array($this->page(),['getting-started','system-status','editions'],true))return 'manage_options'; if ( 'settings' === $this->page() ) { return $this->settingsCapability(); } if ( 'cancellation-policies' === $this->page() ) { return Capabilities::MANAGE_CANCELLATION_POLICIES; } if ( 'credits' === $this->page() ) { return Capabilities::MANAGE_CREDIT; } if ( 'price-lists' === $this->page() ) { return Capabilities::MANAGE_PRICES; } if ( in_array($this->page(),[ 'statistics', 'bookings', 'external-contacts', 'receivables', 'customer-restrictions' ],true) ) { return Capabilities::MANAGE_BOOKINGS; } return in_array( $this->page(), [ 'seasons', 'members', 'membership-types', 'usage-limits' ], true ) ? Capabilities::MANAGE_MEMBERS : Capabilities::MANAGE_RESOURCES; } private function slugFeature(string $slug):?string{return match($slug){'abece-booking-members','abece-booking-seasons','abece-booking-membership-types'=>'memberships','abece-booking-usage-limits'=>'usage_limits','abece-booking-customer-restrictions'=>'customer_restrictions','abece-booking-credits'=>'credits',default=>null};} private function settingsCapability(): string { foreach ( [ Capabilities::MANAGE_RESOURCES, Capabilities::MANAGE_MEMBERS, Capabilities::MANAGE_CREDIT, Capabilities::MANAGE_CANCELLATION_POLICIES, Capabilities::MANAGE_BOOKINGS ] as $cap ) { if ( current_user_can( $cap ) ) { return $cap; } } return Capabilities::MANAGE_RESOURCES; } }