0 ? self::repo()->get( $id ) : null; } public static function for_units( array $unit_ids ): array { $repo = self::repo(); $live = self::live_units(); $unit_ids = array_values( array_unique( array_filter( array_map( 'intval', $unit_ids ), static fn( int $id ): bool => $id > 0 && isset( $live[ $id ] ) ) ) ); $miss = array_values( array_diff( $unit_ids, array_keys( self::$units ) ) ); if ( array() !== $miss ) { $read = $repo->for_units( $miss ); foreach ( $miss as $id ) { $list = array(); foreach ( (array) ( $read[ $id ] ?? array() ) as $session ) { if ( $session instanceof Session && ! self::trashed( $session ) ) { $list[] = $session; } } self::$units[ $id ] = $list; } } $out = array(); foreach ( $unit_ids as $id ) { if ( array() !== self::$units[ $id ] ) { $out[ $id ] = self::$units[ $id ]; } } return $out; } public static function live_units(): array { self::repo(); if ( null !== self::$live_units ) { return self::$live_units; } if ( null !== self::$source ) { $ids = (array) self::$source->live_unit_ids(); } else { $ids = get_option( self::UNITS_OPTION, null ); if ( ! is_array( $ids ) ) { try { $ids = self::read_units(); } catch ( \RoosterLMS\Data\DbError $e ) { unset( $e ); return array(); } update_option( self::UNITS_OPTION, $ids, true ); } } self::$live_units = array_fill_keys( array_map( 'intval', $ids ), true ); return self::$live_units; } public static function invalidate_units(): void { self::$live_units = null; if ( self::$units_deferred ) { self::$units_dirty = true; return; } if ( null === self::$source ) { update_option( self::UNITS_OPTION, self::read_units(), true ); } } public static function defer_units(): bool { $was = self::$units_deferred; self::$units_deferred = true; return $was; } public static function resume_units( bool $was ): void { if ( $was ) { return; } self::$units_deferred = false; if ( self::$units_dirty ) { self::$units_dirty = false; self::invalidate_units(); } } private static function read_units(): array { global $wpdb; return array_map( 'intval', \RoosterLMS\Data\Db::col( SqlPart::of( 'SELECT DISTINCT COALESCE(unit_id, 0) AS unit FROM %i WHERE state <> %d ORDER BY unit', \RoosterLMS\Data\Db::table( 'live_sessions' ), \RoosterLMS\Live\SessionState::TRASHED->value ) ) ); } public static function for_course( int $course_id ): array { if ( $course_id <= 0 ) { return array(); } $outline = Learner::outline( $course_id ); return self::for_units( array_map( static fn( \RoosterLMS\Content\Item $item ): int => $item->unit_id, $outline->items ) ); } public static function links( array $sessions ): array { $units = array(); $courses = array(); foreach ( $sessions as $session ) { if ( $session->placement->in_course() && $session->unit_id > 0 && $session->course_id > 0 ) { $units[] = $session->unit_id; $courses[] = $session->course_id; } } if ( array() === $units ) { return array(); } $lang = \RoosterLMS\I18n\Switcher::current(); $course_posts = Services::posts()->for_objects( \RoosterLMS\Content\ObjectType::COURSE, array_values( array_unique( $courses ) ), $lang ); $unit_posts = Services::posts()->for_objects( \RoosterLMS\Content\ObjectType::UNIT, array_values( array_unique( $units ) ), $lang ); $out = array(); foreach ( $sessions as $session ) { $url = \RoosterLMS\Routing\Urls::build( $course_posts[ $session->course_id ] ?? 0, $unit_posts[ $session->unit_id ] ?? 0 ); if ( '' !== $url ) { $out[ $session->id ] = $url; } } return $out; } public static function next_occurrences( array $sessions ): array { $repo = self::repo(); $miss = array(); foreach ( $sessions as $session ) { if ( ! array_key_exists( $session->id, self::$next ) && $session->is_scheduled() ) { $miss[] = $session->id; } } if ( array() !== $miss ) { $read = $repo->next_occurrences( $miss, self::now() ); foreach ( $miss as $id ) { $found = $read[ $id ] ?? null; self::$next[ $id ] = $found instanceof Occurrence ? $found : null; } } $out = array(); foreach ( $sessions as $session ) { if ( isset( self::$next[ $session->id ] ) ) { $out[ $session->id ] = self::$next[ $session->id ]; } } return $out; } public static function upcoming( int $user_id, int $limit, int $course_id = 0, int $org_id = 0 ): array { if ( $user_id <= 0 ) { return array(); } $out = array(); if ( array() === self::live_units() ) { return $out; } foreach ( (array) self::repo()->upcoming_for_user( $user_id, max( 1, min( 100, $limit ) ), self::now(), $course_id, $org_id ) as $entry ) { if ( $entry instanceof Upcoming ) { $out[] = $entry; self::$next[ $entry->session->id ] ??= $entry->occurrence; } } return $out; } public static function next( int $user_id, int $course_id = 0 ): ?Upcoming { if ( $user_id <= 0 || array() === self::live_units() ) { return null; } $entry = self::repo()->next_for_user( $user_id, self::now(), $course_id ); if ( ! $entry instanceof Upcoming ) { return null; } self::$next[ $entry->session->id ] ??= $entry->occurrence; return $entry; } public static function titles( array $sessions ): array { $repo = self::repo(); $lang = \RoosterLMS\I18n\Switcher::current(); $have = self::$titles[ $lang ] ?? array(); $miss = array(); foreach ( $sessions as $session ) { if ( ! isset( $have[ $session->id ] ) ) { $miss[ $session->id ] = $session; } } if ( array() !== $miss ) { foreach ( (array) $repo->titles( array_values( $miss ), $lang ) as $id => $title ) { $have[ (int) $id ] = (string) $title; } self::$titles[ $lang ] = $have; } $out = array(); foreach ( $sessions as $session ) { $out[ $session->id ] = $have[ $session->id ] ?? __( 'Live session', 'rooster-lms' ); } return $out; } public static function can_join( Session $session, int $user_id ): bool { if ( $user_id <= 0 ) { return false; } return null !== self::$source ? (bool) self::$source->can_join( $session, $user_id ) : Services::live_visibility()->can_join( $session, $user_id ); } public static function can_edit( Session $session, int $user_id ): bool { if ( $user_id <= 0 ) { return false; } return null !== self::$source ? (bool) self::$source->can_edit( $session, $user_id ) : Services::live_visibility()->can_edit( $session, $user_id ); } public static function join_url( Session $session, int $occurrence_id ): string { if ( null !== self::$source ) { return (string) self::$source->join_url( $session, $occurrence_id ); } return (string) \RoosterLMS\Live\JoinRoute::url( $session, $occurrence_id ); } public static function ics_url( Session $session ): string { return \RoosterLMS\Live\IcsRoute::url( $session ); } private static function repo(): object { $repo = self::$source ?? Services::live_session_repo(); if ( self::$owner !== $repo ) { self::reset(); self::$owner = $repo; } return $repo; } private static function trashed( Session $session ): bool { return \RoosterLMS\Live\SessionState::TRASHED === $session->state; } }