' . esc_html( $title ) . '' : esc_html( $title ); $image = $post['image'] ?? null; $classes = 'rooster-lms-card rooster-lms-events__card' . ( empty( $a['showPrice'] ) ? ' is-no-price' : '' ); $filter = ''; if ( null !== $facet ) { $filter = ' data-f-category="' . esc_attr( implode( ' ', array_map( 'intval', (array) $facet['category'] ) ) ) . '" data-f-trainer="' . esc_attr( implode( ' ', array_map( 'intval', (array) $facet['trainer'] ) ) ) . '"' . ' data-f-city="' . esc_attr( (string) $facet['city'] ) . '" data-f-month="' . esc_attr( (string) $facet['month'] ) . '"' . ( empty( $facet['hidden'] ) ? '' : ' hidden' ); } $html = '
  • ' . '
    '; if ( is_array( $image ) && '' !== (string) ( $image['src'] ?? '' ) ) { $html .= '' . esc_attr( (string) $image['alt'] ) . ' 0 ? ' width="' . esc_attr( (string) (int) $image['width'] ) . '" height="' . esc_attr( (string) (int) $image['height'] ) . '"' : '' ) . ' loading="lazy" decoding="async" />'; } $html .= '
    ' . '

    ' . self::badges( $e, ! empty( $a['showPlaces'] ) ) . '

    ' . '' . $name . ''; $course = (string) ( $post['course_title'] ?? '' ); if ( '' !== $course && $course !== $title ) { $html .= '

    ' . esc_html( $course ) . '

    '; } $html .= '

    ' . self::time( $e, $e->occurrence->start(), true ) . '

    ' . '

    ' . esc_html( LiveMarkup::duration( $e->session->duration_min ) ) . '' . '

    '; if ( Delivery::ONLINE !== $e->delivery() && null !== $e->venue ) { $html .= '

    ' . esc_html__( 'Where:', 'rooster-lms' ) . ' ' . esc_html( $e->venue_line() ) . '

    '; } if ( $e->more_count > 0 ) { $html .= self::more( $e ); } $html .= Notify::notice( $e->id(), $e->course_id ) . '
    ' . $cta['html'] . ' ' . self::calendar_menu( $e, $post, $user ) . ' ' . self::subscribe_menu( $e, $post, $user ) . '
    ' . '
  • '; return $html; } public static function badges( Event $e, bool $places ): string { $city = null === $e->venue ? '' : (string) $e->venue['city']; $out = ''; if ( Delivery::IN_PERSON !== $e->delivery() ) { $out .= Ui::badge( self::provider( $e->session->provider ), 'secondary sm' ) . ' '; } $out .= CourseFront::badges( Delivery::ONLINE === $e->delivery() ? Delivery::SELF_PACED : $e->delivery(), $city, $places ? $e->places_left : null ); return trim( $out ); } public static function provider( ProviderKey $key ): string { return match ( $key ) { ProviderKey::ZOOM, ProviderKey::EROOM => __( 'Zoom', 'rooster-lms' ), ProviderKey::TEAMS => __( 'Microsoft Teams', 'rooster-lms' ), ProviderKey::MEET => __( 'Google Meet', 'rooster-lms' ), ProviderKey::STREAM => __( 'Live stream', 'rooster-lms' ), ProviderKey::IN_PERSON => __( 'In person', 'rooster-lms' ), default => __( 'Online link', 'rooster-lms' ), }; } public static function time( Event $e, \DateTimeImmutable $start, bool $end = false ): string { return ''; } private static function more( Event $e ): string { $items = ''; foreach ( $e->more as $start ) { $items .= '
  • ' . self::time( $e, new \DateTimeImmutable( $start, new \DateTimeZone( 'UTC' ) ) ) . '
  • '; } $rest = $e->more_count - count( $e->more ); if ( $rest > 0 ) { /* translators: %s: number of further dates. */ $items .= '
  • ' . esc_html( sprintf( _n( 'and %s more date', 'and %s more dates', $rest, 'rooster-lms' ), Format::number( $rest ) ) ) . '
  • '; } /* translators: %s: number of further dates. */ return '
    ' . esc_html( sprintf( _n( '%s more date', '%s more dates', $e->more_count, 'rooster-lms' ), Format::number( $e->more_count ) ) ) . '
    '; } public static function calendar_menu( Event $e, array $post, int $user ): string { return CalendarLinks::menu( array( 'title' => (string) ( $post['title'] ?? '' ), 'start' => $e->occurrence->start(), 'end' => $e->occurrence->end(), 'page' => (string) ( $post['url'] ?? '' ), 'location' => $e->location(), 'zone' => $e->zone()->getName(), 'ics' => self::ics_url( $e, $user ), ) ); } public static function subscribe_menu( Event $e, array $post, int $user ): string { $feed = Feed::course_url( $e->course_id, $e->visibility, $user ); $name = (string) ( $post['course_title'] ?? '' ); return '' === $feed ? '' : CalendarLinks::subscribe( $feed, '' !== $name ? $name : (string) ( $post['title'] ?? '' ), 'sm', 'ghost' ); } public static function ics_url( Event $e, int $user ): string { if ( CourseVisibility::PUBLIC === $e->visibility ) { return Feed::url( array( 'occurrence' => $e->id() ) ); } return $user > 0 ? Feed::url( array( 'course' => $e->course_id, 'occurrence' => $e->id(), 'u' => $user, 't' => FeedTokens::sign( $user, $e->course_id ), ) ) : ''; } public static function calendar( array $events, array $posts, string $month, array $nav ): string { $site = wp_timezone(); $first = \DateTimeImmutable::createFromFormat( '!Y-m-d', $month . '-01', $site ); if ( false === $first ) { return ''; } $by_day = array(); foreach ( $events as $e ) { $day = $e->occurrence->start()->setTimezone( $site )->format( 'Y-m-d' ); $by_day[ $day ][] = $e; } $start_of_week = (int) get_option( 'start_of_week', 1 ); $days = (int) $first->format( 't' ); $lead = ( (int) $first->format( 'w' ) - $start_of_week + 7 ) % 7; $today = wp_date( 'Y-m-d', \RoosterLMS\Core\Clock::timestamp(), $site ); global $wp_locale; $head = ''; for ( $i = 0; $i < 7; $i++ ) { $index = ( $start_of_week + $i ) % 7; $full = $wp_locale instanceof \WP_Locale ? (string) $wp_locale->get_weekday( $index ) : (string) $index; $short = $wp_locale instanceof \WP_Locale ? (string) $wp_locale->get_weekday_abbrev( $full ) : $full; $head .= '' . esc_html( $short ) . ''; } $cells = array_fill( 0, $lead, '' ); for ( $d = 1; $d <= $days; $d++ ) { $date = $first->format( 'Y-m-' ) . sprintf( '%02d', $d ); $list = $by_day[ $date ] ?? array(); $label = (string) wp_date( (string) get_option( 'date_format', 'j F Y' ), $first->modify( '+' . ( $d - 1 ) . ' days' )->getTimestamp(), $site ); $items = ''; foreach ( $list as $e ) { $post = $posts[ $e->id() ] ?? array(); $url = (string) ( $post['url'] ?? '' ); $time = ''; $text = $time . ' ' . esc_html( (string) ( $post['title'] ?? '' ) ) . ''; $items .= '
  • ' . ( '' !== $url ? '' . $text . '' : $text ) . '
  • '; } $class = trim( ( array() === $list ? 'is-quiet' : 'has-events' ) . ( $date === $today ? ' is-today' : '' ) ); $cells[] = '' . '' . esc_html( $label ) . '' . ( '' !== $items ? '' : '' ) . ''; } $fill = ( 7 - count( $cells ) % 7 ) % 7; for ( $i = 0; $i < $fill; $i++ ) { $cells[] = ''; } $rows = ''; foreach ( array_chunk( $cells, 7 ) as $week ) { $rows .= '' . implode( '', $week ) . ''; } $caption = (string) wp_date( 'F Y', $first->getTimestamp(), $site ); $links = ''; if ( '' !== $nav[0] ) { $links .= '' . esc_html__( 'Previous month', 'rooster-lms' ) . ''; } if ( '' !== $nav[1] ) { $links .= '' . esc_html__( 'Next month', 'rooster-lms' ) . ''; } $none = array() === $events ? '

    ' . esc_html__( 'No live lessons this month.', 'rooster-lms' ) . '

    ' : ''; return '
    ' . ( '' !== $links ? '' : '' ) . '' . $head . '' . $rows . '
    ' . esc_html( $caption ) . '
    ' . $none . '
    '; } public static function filters( EventQuery $q, array $a, array $facets, \DateTimeImmutable $now, bool $local = false ): string { $fields = ''; $f = static fn( string $key ): string => ' data-rl-ev-f="' . esc_attr( $key ) . '"'; $set = false; if ( array() !== $facets['categories'] ) { $fields .= Markup::select( EventQuery::ARG_CATEGORY, __( 'Category', 'rooster-lms' ), array( '' => __( 'All categories', 'rooster-lms' ) ) + $facets['categories'], $q->filter_term > 0 ? (string) $q->filter_term : '', $f( 'category' ) ); $set = $set || $q->filter_term > 0; } if ( '' === (string) ( $a['delivery'] ?? '' ) && count( $facets['deliveries'] ) > 1 ) { $options = array( '' => __( 'Any way', 'rooster-lms' ) ); foreach ( $facets['deliveries'] as $slug ) { $options[ $slug ] = self::delivery_label( (string) $slug ); } $fields .= Markup::select( EventQuery::ARG_DELIVERY, __( 'How', 'rooster-lms' ), $options, $q->delivery, ' data-rl-ev-f="delivery"' ); $set = $set || '' !== $q->delivery; } if ( '' === (string) ( $a['city'] ?? '' ) && array() !== $facets['cities'] ) { $fields .= Markup::select( EventQuery::ARG_CITY, __( 'City', 'rooster-lms' ), array( '' => __( 'Any city', 'rooster-lms' ) ) + array_combine( $facets['cities'], $facets['cities'] ), $q->city, $f( 'city' ) ); $set = $set || '' !== $q->city; } if ( 'calendar' !== ( $a['view'] ?? '' ) ) { $months = array( '' => __( 'Any month', 'rooster-lms' ) ); $site = $now->setTimezone( wp_timezone() ); $first = \DateTimeImmutable::createFromFormat( '!Y-m-d', $site->format( 'Y-m-01' ), wp_timezone() ); for ( $i = 0; $first instanceof \DateTimeImmutable && $i < (int) ( $a['months'] ?? 3 ); $i++ ) { $m = $first->modify( '+' . $i . ' months' ); $months[ $m->format( 'Y-m' ) ] = (string) wp_date( 'F Y', $m->getTimestamp(), wp_timezone() ); } $fields .= Markup::select( EventQuery::ARG_MONTH, __( 'Month', 'rooster-lms' ), $months, $q->month, $f( 'month' ) ); $set = $set || '' !== $q->month; } if ( 0 === (int) ( $a['trainer'] ?? 0 ) && array() !== $facets['trainers'] ) { $fields .= Markup::select( EventQuery::ARG_TRAINER, __( 'Trainer', 'rooster-lms' ), array( '' => __( 'Any trainer', 'rooster-lms' ) ) + $facets['trainers'], $q->trainer > 0 ? (string) $q->trainer : '', $f( 'trainer' ) ); $set = $set || $q->trainer > 0; } if ( '' === $fields ) { return ''; } $skip = array( EventQuery::ARG_CATEGORY, EventQuery::ARG_DELIVERY, EventQuery::ARG_CITY, EventQuery::ARG_TRAINER, EventQuery::ARG_PAGE, Notify::ARG_OUTCOME, Notify::FIELD_OCCURRENCE ); $skip = 'calendar' === ( $a['view'] ?? '' ) ? $skip : array_merge( $skip, array( EventQuery::ARG_MONTH ) ); $clear = Request::url( array(), array_merge( $skip, array( EventQuery::ARG_MONTH ) ) ); $instant = ! empty( $a['applyOnChange'] ); $show = ''; $reset = $set || $local ? '' . esc_html__( 'Clear filters', 'rooster-lms' ) . '' : ''; return ''; } public static function pager( int $page, bool $next ): string { if ( $page <= 1 && ! $next ) { return ''; } $links = ''; if ( $page > 1 ) { $url = 2 === $page ? Request::url( array(), array( EventQuery::ARG_PAGE, Notify::ARG_OUTCOME, Notify::FIELD_OCCURRENCE ) ) : Request::url( array( EventQuery::ARG_PAGE => $page - 1 ), array( Notify::ARG_OUTCOME, Notify::FIELD_OCCURRENCE ) ); $links .= '
  • '; } /* translators: %s: page number. */ $links .= '
  • ' . esc_html( sprintf( __( 'Page %s', 'rooster-lms' ), Format::number( $page ) ) ) . '
  • '; if ( $next ) { $links .= '
  • '; } return ''; } public static function delivery_label( string $slug ): string { return match ( $slug ) { 'in_person' => __( 'In person', 'rooster-lms' ), 'hybrid' => __( 'Hybrid', 'rooster-lms' ), default => __( 'Online', 'rooster-lms' ), }; } }