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 );
}
}