'org-dashboard', 'branding' => 'org-branding', 'structure' => 'org-structure', 'job-roles' => 'org-job-roles', 'registration-fields' => 'org-registration-fields', 'members' => 'org-members', 'assignments' => 'org-catalogue', 'matrix' => 'org-matrix', 'enroller' => 'org-enroller', 'progress' => 'org-progress', 'grading' => 'grading-queue', 'assessments' => 'org-assessments', 'sessions' => 'live-roster', ); public const TAB_ALIASES = array( 'locations' => 'structure' ); public const BLOCK_ALIASES = array( 'org-locations' => 'org-structure' ); public const REPORT_TABS = array( 'dashboard', 'structure', 'progress' ); public const GRADE_TABS = array( 'grading' ); public const UNIT_MANAGER_TABS = array( 'dashboard', 'members', 'progress', 'grading', 'structure', 'enroller' ); public const TABS_FILTER = 'rooster_lms_portal_tabs'; private const ACCESS = array( 'manage', 'reports', 'grade' ); private static array $memo = array(); public function __construct( public readonly Org $org, public readonly OrgScope $scope, public readonly bool $manage, public readonly bool $reports, public readonly bool $site_wide, public readonly bool $grade = false, public readonly ?array $unit_scope = null ) {} public static function resolve( int $org_attr = 0 ): ?self { $user_id = get_current_user_id(); if ( $user_id <= 0 ) { return null; } $picked = Request::int( 'rl_org' ); $key = $user_id . ':' . max( 0, $org_attr ) . ':' . $picked . ':' . ApparentRoot::format( ApparentRoot::resolve() ); if ( array_key_exists( $key, self::$memo ) ) { return self::$memo[ $key ]; } $scope = OrgScope::for_user( $user_id ); $site_wide = self::picks_org( $scope ); $own = self::own_org_id( $scope ); $org_id = $own; if ( $site_wide ) { $org_id = $picked > 0 ? $picked : ( $org_attr > 0 ? $org_attr : $own ); $branch = ApparentRoot::branch_org_ids(); if ( null !== $branch && ! in_array( $org_id, $branch, true ) ) { $org_id = (int) ( ApparentRoot::resolve()['org_id'] ?? 0 ); } } elseif ( $picked > 0 && $picked !== $own && in_array( $picked, self::pickable( $scope ), true ) ) { $org_id = $picked; } $context = null; $org = $org_id > 0 ? Services::org_repo()->get( $org_id ) : null; if ( null !== $org && ( $site_wide || $org->is_active() ) ) { $manage = $scope->can_manage_members( $org->id ); $reports = $scope->can_view_reports( $org->id ); $grade = $scope->can_grade( $org->id ); if ( $manage || $reports || $grade ) { $mask = $manage ? OrgScope::MANAGE_MASK : ( $reports ? OrgScope::REPORT_MASK : OrgScope::GRADE_MASK ); $context = new self( $org, $scope, $manage, $reports, $site_wide, $grade, $scope->unit_scope( $org->id, $mask ) ); } } self::$memo[ $key ] = $context; return $context; } public static function pending_org( int $user_id ): ?Org { $picked = Request::int( 'rl_org' ); if ( $user_id <= 0 || $picked <= 0 ) { return null; } $org = Services::org_repo()->get( $picked ); return null !== $org && OrgStatus::PENDING === $org->status && PendingScope::applies( $org, $user_id ) ? $org : null; } public function units( string $access ): ?array { $mask = match ( $access ) { 'manage' => OrgScope::MANAGE_MASK, 'grade' => OrgScope::GRADE_MASK, default => OrgScope::REPORT_MASK, }; return $this->scope->unit_scope( $this->org->id, $mask ); } public function manages_org(): bool { return $this->scope->can_manage( $this->org->id ); } public static function extra_tabs(): array { $raw = apply_filters( 'rooster_lms_portal_tabs', array() ); $out = array(); foreach ( is_array( $raw ) ? $raw : array() as $slug => $tab ) { if ( ! is_string( $slug ) || 1 !== preg_match( '/^[a-z0-9-]{1,40}$/', $slug ) || isset( self::TABS[ $slug ] ) || isset( self::TAB_ALIASES[ $slug ] ) || ! is_array( $tab ) ) { continue; } $label = is_string( $tab['label'] ?? null ) ? trim( wp_strip_all_tags( $tab['label'] ) ) : ''; $access = $tab['access'] ?? ''; $render = $tab['render'] ?? null; if ( '' === $label || ! is_string( $access ) || ! in_array( $access, self::ACCESS, true ) || ! is_callable( $render ) ) { continue; } $out[ $slug ] = array( 'label' => $label, 'access' => $access, 'render' => $render, ); foreach ( array( 'visible', 'count' ) as $optional ) { if ( is_callable( $tab[ $optional ] ?? null ) ) { $out[ $slug ][ $optional ] = $tab[ $optional ]; } } } return $out; } public function tab_count( string $slug ): int { $count = self::extra_tabs()[ $slug ]['count'] ?? null; return null === $count ? 0 : max( 0, (int) call_user_func( $count, $this->org->id, $this->scope->user_id() ) ); } public static function canonical_tab( string $tab ): string { return self::TAB_ALIASES[ $tab ] ?? $tab; } public static function picks_org( OrgScope $scope ): bool { return $scope->is_site_wide() || $scope->can_view_all_reports(); } public static function pickable( OrgScope $scope ): array { return $scope->scoped_org_ids( MemberRole::MANAGER->value | MemberRole::REPORTER->value | MemberRole::INSTRUCTOR->value ); } public static function own_org_id( OrgScope $scope ): int { $ids = $scope->scoped_org_ids( MemberRole::MANAGER->value | MemberRole::REPORTER->value | MemberRole::INSTRUCTOR->value ); if ( array() === $ids ) { $ids = $scope->org_ids( MemberRole::mask( ...MemberRole::cases() ) ); } return array() === $ids ? 0 : (int) $ids[0]; } public function rights_tabs(): array { if ( $this->manage ) { $tabs = array_keys( self::TABS ); if ( ! $this->manages_org() ) { $tabs = array_values( array_filter( $tabs, static fn( string $tab ): bool => in_array( $tab, self::UNIT_MANAGER_TABS, true ) ) ); } } else { $allowed = array_merge( $this->reports ? self::REPORT_TABS : array(), $this->grade ? self::GRADE_TABS : array() ); $tabs = array_values( array_filter( array_keys( self::TABS ), static fn( string $tab ): bool => in_array( $tab, $allowed, true ) ) ); } if ( ApparentRoot::unit_in( $this->org->id ) > 0 && ApparentRoot::hides() ) { $tabs = array_values( array_diff( $tabs, array( 'branding' ) ) ); } foreach ( self::extra_tabs() as $slug => $tab ) { $flag = match ( $tab['access'] ) { 'manage' => $this->manage, 'grade' => $this->grade, default => $this->reports, }; if ( $flag && ( ! isset( $tab['visible'] ) || (bool) call_user_func( $tab['visible'], $this->org->id, $this->scope->user_id() ) ) ) { $tabs[] = $slug; } } return $tabs; } public function tabs(): array { return PortalTabs::filter( $this->org, $this->rights_tabs() ); } public function allows_block( string $block ): ?bool { $tab = array_search( self::BLOCK_ALIASES[ $block ] ?? $block, self::TABS, true ); return false === $tab ? null : in_array( self::canonical_tab( (string) $tab ), $this->rights_tabs(), true ); } public function allows( string $tab ): bool { return in_array( self::canonical_tab( $tab ), $this->tabs(), true ); } public function name(): string { return OrgBranding::org_name( $this->org, Switcher::current() ); } public static function reset(): void { self::$memo = array(); PortalTabs::reset(); } }