0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, ); public ?Lookup $level = null; public ?Lookup $badge = null; private array $texts_in = array(); private array $points_in = array(); private function __construct( public readonly Course $course ) {} public static function for( int $course_id ): ?self { if ( $course_id <= 0 ) { return null; } if ( isset( self::$memo[ $course_id ] ) ) { return false === self::$memo[ $course_id ] ? null : self::$memo[ $course_id ]; } $course = Services::course_repo()->get( $course_id ); if ( null === $course ) { self::$memo[ $course_id ] = false; return null; } $page = new self( $course ); self::$memo[ $course_id ] = $page; $page->load(); return $page; } public static function forget(): void { self::$memo = array(); } private function load(): void { global $wpdb; $id = $this->course->id; $parts = array( SqlPart::of( "SELECT 't' AS src, kind AS k, 0 AS n, 0 AS pos, html AS txt, '' AS aux FROM %i WHERE course_id = %d", Db::table( 'course_texts' ), $id ), SqlPart::of( "SELECT 'p', kind, id, position, text, '' FROM %i WHERE course_id = %d", Db::table( 'course_points' ), $id ), SqlPart::of( "SELECT 'f', 0, COUNT(*), 0, '', '' FROM %i WHERE target_type = 1 AND target_id = %d", Db::table( 'faq_links' ), $id ), SqlPart::of( "SELECT 'm', 0, COUNT(*), 0, '', '' FROM %i WHERE owner_type = %d AND owner_id = %d AND role = %d", Db::table( 'media' ), MediaOwner::COURSE->value, $id, MediaRole::INTRO->value ), SqlPart::of( "SELECT 'c', access, COUNT(*), 0, '', '' FROM %i WHERE course_id = %d GROUP BY access", Db::table( 'course_files' ), $id ), SqlPart::of( "SELECT 's', rating_count, learners, 0, COALESCE( CAST( rating_avg AS CHAR ), '' ), '' FROM %i WHERE course_id = %d", Db::table( 'course_stats' ), $id ), SqlPart::of( "SELECT 'i', user_id, 0, position, '', '' FROM %i WHERE course_id = %d AND role = %d ORDER BY position, user_id LIMIT 1", Db::table( 'course_staff' ), $id, \RoosterLMS\Content\StaffRole::PRINCIPAL->value ), SqlPart::of( "SELECT 'r', rating, COUNT(*), 0, '', '' FROM %i WHERE course_id = %d AND state = %d GROUP BY rating", Db::table( 'course_reviews' ), $id, ReviewState::APPROVED->value ), ); if ( $this->course->level_id > 0 ) { $parts[] = SqlPart::of( "SELECT 'l', id, state, builtin, label, slug FROM %i WHERE id = %d", Db::table( 'course_levels' ), $this->course->level_id ); } if ( $this->course->badge_id > 0 ) { $parts[] = SqlPart::of( "SELECT 'b', id, state, builtin, label, CONCAT_WS( CHAR(31), slug, color, icon ) FROM %i WHERE id = %d", Db::table( 'course_badges' ), $this->course->badge_id ); } $rows = Db::rows( SqlPart::of( '( ' )->add( SqlPart::join( ' ) UNION ALL ( ', $parts ) )->add( ' ) ORDER BY src, k, pos, n' ) ); foreach ( $rows as $row ) { $k = (int) $row['k']; switch ( (string) $row['src'] ) { case 't': $this->texts[ $k ] = (string) $row['txt']; break; case 'p': $this->points[ $k ][] = array( 'id' => (int) $row['n'], 'text' => (string) $row['txt'], ); break; case 'f': $this->faq_links = (int) $row['n']; break; case 'm': $this->has_intro = (int) $row['n'] > 0; break; case 'c': $this->files[ $k ] = (int) $row['n']; break; case 'i': $this->principal = $k; break; case 's': $this->rating_count = $k; $this->learners = (int) $row['n']; $this->rating_avg = '' === (string) $row['txt'] || 0 === $k ? null : (float) $row['txt']; break; case 'r': if ( isset( $this->distribution[ $k ] ) ) { $this->distribution[ $k ] = (int) $row['n']; } break; case 'l': case 'b': $aux = explode( "\x1f", (string) $row['aux'] ); $lookup = Lookup::from_row( array( 'id' => $k, 'slug' => $aux[0], 'label' => (string) $row['txt'], 'position' => 0, 'state' => (int) $row['n'], 'builtin' => (int) $row['pos'], 'color' => $aux[1] ?? '', 'icon' => $aux[2] ?? '', ) ); if ( 'l' === $row['src'] ) { $this->level = $lookup; } else { $this->badge = $lookup; } break; } } } public function texts_in( string $lang = '' ): array { $lang = '' !== $lang ? $lang : Names::display_language(); if ( ! isset( $this->texts_in[ $lang ] ) ) { $defaults = array(); foreach ( $this->texts as $kind => $html ) { $case = CourseTextKind::tryFrom( $kind ); if ( null !== $case && '' !== $html ) { $defaults[ $case->field() ] = $html; } } $this->texts_in[ $lang ] = array() === $defaults ? array() : Services::strings()->translate_fields( TextObject::COURSE, $this->course->id, $defaults, $lang ); } return $this->texts_in[ $lang ]; } public function text( CourseTextKind $kind, string $lang = '' ): string { return $this->texts_in( $lang )[ $kind->field() ] ?? ''; } public function points_of( CoursePointKind $kind, string $lang = '' ): array { $lang = '' !== $lang ? $lang : Names::display_language(); if ( ! isset( $this->points_in[ $lang ] ) ) { $defaults = array(); foreach ( $this->points as $list ) { foreach ( $list as $point ) { $defaults[ $point['id'] ] = $point['text']; } } $texts = array() === $defaults ? array() : Services::strings()->translate_many( TextObject::COURSE_POINT, $defaults, $lang, 'text' ); $out = array(); foreach ( $this->points as $k => $list ) { $case = CoursePointKind::tryFrom( (int) $k ); if ( null !== $case ) { $out[ $case->slug() ] = array_map( static fn( array $p ): string => (string) ( $texts[ $p['id'] ] ?? $p['text'] ), $list ); } } $this->points_in[ $lang ] = $out; } return $this->points_in[ $lang ][ $kind->slug() ] ?? array(); } public function level_label( string $lang = '' ): string { return null === $this->level ? '' : Services::course_levels()->labels( array( $this->level ), $lang )[ $this->level->id ] ?? ''; } public function badge_now( string $lang = '' ): ?array { if ( null === $this->badge || ! $this->course->badge_active( Transaction::now() ) ) { return null; } return array( 'label' => (string) ( Services::course_badges()->labels( array( $this->badge ), $lang )[ $this->badge->id ] ?? $this->badge->slug ), 'slug' => $this->badge->slug, 'color' => $this->badge->color, 'icon' => $this->badge->icon, ); } public function approved(): int { return (int) array_sum( $this->distribution ); } public function reviews_on(): bool { return match ( $this->course->reviews_mode ) { 1 => false, 2 => true, default => (bool) CommunitySettings::get( 'reviews_enabled' ), }; } public function coming_soon(): bool { return $this->course->is_coming_soon( Transaction::now() ); } public function hides_details( int $user_id ): bool { if ( ! $this->coming_soon() || 0 !== ( $this->course->coming_soon_flags & ComingSoonFlag::SHOW_DETAILS->value ) ) { return false; } return ! Services::course_details()->can_edit( $this->course->id, $user_id ); } public function public_files(): int { return (int) ( $this->files[ FileAccess::EVERYONE->value ] ?? 0 ); } public function enrolled_files(): int { return (int) ( $this->files[ FileAccess::ENROLLED->value ] ?? 0 ); } }