10, 'series' => '', 'speaker' => '', ), is_array( $atts ) ? $atts : array(), 'pulpitkit_sermons' ); PulpitKit::enqueue_styles(); $args = array( 'post_type' => 'pulpitkit_sermon', 'post_status' => 'publish', 'posts_per_page' => max( 1, absint( $atts['limit'] ) ), 'meta_key' => '_pulpitkit_date_preached', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'orderby' => 'meta_value', 'order' => 'DESC', ); $tax_query = array(); if ( '' !== $atts['series'] ) { $tax_query[] = array( 'taxonomy' => 'pulpitkit_sermon_series', 'field' => 'slug', 'terms' => sanitize_title( $atts['series'] ), ); } if ( '' !== $atts['speaker'] ) { $tax_query[] = array( 'taxonomy' => 'pulpitkit_sermon_speaker', 'field' => 'slug', 'terms' => sanitize_title( $atts['speaker'] ), ); } if ( ! empty( $tax_query ) ) { $args['tax_query'] = $tax_query; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query } $sermons = get_posts( $args ); if ( empty( $sermons ) ) { return '

' . esc_html__( 'No sermons yet — check back soon.', 'pulpitkit' ) . '

'; } ob_start(); echo '
'; foreach ( $sermons as $sermon ) { $audio = PulpitKit_Podcast::sermon_audio( $sermon->ID ); $scripture = (string) get_post_meta( $sermon->ID, '_pulpitkit_scripture', true ); $date = (string) get_post_meta( $sermon->ID, '_pulpitkit_date_preached', true ); $video_url = (string) get_post_meta( $sermon->ID, '_pulpitkit_video_url', true ); $notes_url = $this->notes_url( $sermon->ID ); $series = wp_get_post_terms( $sermon->ID, 'pulpitkit_sermon_series', array( 'fields' => 'names' ) ); $speakers = wp_get_post_terms( $sermon->ID, 'pulpitkit_sermon_speaker', array( 'fields' => 'names' ) ); ?>

'; return (string) ob_get_clean(); } /** * [pulpitkit_events] — upcoming events list (recurrence-aware). * * Attributes: limit (int). * * @param array $atts Shortcode/block attributes. * @return string HTML. */ public function render_events( $atts ) { $atts = shortcode_atts( array( 'limit' => 5 ), is_array( $atts ) ? $atts : array(), 'pulpitkit_events' ); PulpitKit::enqueue_styles(); $upcoming = PulpitKit_Events::get_upcoming( max( 1, absint( $atts['limit'] ) ) ); if ( empty( $upcoming ) ) { return '

' . esc_html__( 'No upcoming events scheduled.', 'pulpitkit' ) . '

'; } $date_format = get_option( 'date_format' ); $time_format = get_option( 'time_format' ); ob_start(); echo '
'; foreach ( $upcoming as $item ) { $post = $item['post']; $start = $item['start']; $end = $item['end']; $location = (string) get_post_meta( $post->ID, '_pulpitkit_location', true ); $rule = (string) get_post_meta( $post->ID, '_pulpitkit_recurrence', true ); $same_day = $start->format( 'Y-m-d' ) === $end->format( 'Y-m-d' ); ?>
format( $date_format ) ); ?> format( $time_format ) ); if ( $same_day ) { echo esc_html( ' – ' . $end->format( $time_format ) ); } ?>

post_excerpt ) : ?>

post_excerpt ); ?>

'; return (string) ob_get_clean(); } /** * [pulpitkit_live] — livestream panel with live-now / next-service state. * * State comes from the weekly service schedule in Settings → PulpitKit: * inside the live window (starting `live_window_before` minutes early) * the panel shows the embed; otherwise it shows the next service time. * * @param array $atts Unused. * @return string HTML. */ public function render_live( $atts ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- shortcode signature. PulpitKit::enqueue_styles(); $youtube_url = (string) PulpitKit::get_setting( 'live_youtube_url' ); $youtube_channel = (string) PulpitKit::get_setting( 'live_youtube_channel_id' ); $facebook_url = (string) PulpitKit::get_setting( 'live_facebook_url' ); $state = $this->service_state(); $is_live = 'live' === $state['status']; ob_start(); ?>

format( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) ) ); ?>

youtube_embed( $youtube_url, $youtube_channel ); if ( $embed ) : ?>

'; } /** * Compute the current service state from the weekly schedule. * * @return array{status:string,next:DateTimeImmutable|null} */ private function service_state() { $day = (int) PulpitKit::get_setting( 'service_day', 0 ); $time = (string) PulpitKit::get_setting( 'service_time', '10:30' ); $duration = max( 1, (int) PulpitKit::get_setting( 'service_duration', 90 ) ); $before = max( 0, (int) PulpitKit::get_setting( 'live_window_before', 15 ) ); if ( ! preg_match( '/^([01]\d|2[0-3]):[0-5]\d$/', $time ) ) { return array( 'status' => 'unscheduled', 'next' => null ); } list( $hour, $minute ) = array_map( 'intval', explode( ':', $time ) ); $now = new DateTimeImmutable( 'now', wp_timezone() ); // Candidate start: this week's service day at the configured time. $candidate = $now->setTime( $hour, $minute, 0 ); $diff = ( $day - (int) $now->format( 'w' ) + 7 ) % 7; $candidate = $candidate->modify( '+' . $diff . ' days' ); $window_start = $candidate->modify( '-' . $before . ' minutes' ); $window_end = $candidate->modify( '+' . $duration . ' minutes' ); if ( $now >= $window_start && $now <= $window_end ) { return array( 'status' => 'live', 'next' => $candidate ); } if ( $now > $window_end ) { $candidate = $candidate->modify( '+7 days' ); } return array( 'status' => 'upcoming', 'next' => $candidate ); } /** * Resolve the notes PDF URL for a sermon (uploaded file beats URL field). * * @param int $post_id Sermon ID. * @return string */ private function notes_url( $post_id ) { $notes_id = (int) get_post_meta( $post_id, '_pulpitkit_notes_id', true ); if ( $notes_id ) { $url = wp_get_attachment_url( $notes_id ); if ( $url ) { return $url; } } return (string) get_post_meta( $post_id, '_pulpitkit_notes_url', true ); } }