. * * @package ThreeCal * @since 1.7.0 */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Feed class. */ class ThreeCal_Feed { /** * Most events in one answer. */ const MAX_LIMIT = 200; /** * Hooks. */ public static function init() { add_action( 'rest_api_init', array( __CLASS__, 'register' ) ); add_action( 'wp_head', array( __CLASS__, 'alternate_link' ), 2 ); } /** * Address of the feed. * * @return string */ public static function url() { return rest_url( 'threecal/v1/events' ); } /** * Register the route. */ public static function register() { register_rest_route( 'threecal/v1', '/events', array( 'methods' => 'GET', 'callback' => array( __CLASS__, 'respond' ), 'permission_callback' => '__return_true', 'args' => array( 'from' => array( 'description' => 'First day (YYYY-MM-DD), default today. Events still running on that day are included.', 'type' => 'string', 'validate_callback' => array( __CLASS__, 'validate_date' ), ), 'to' => array( 'description' => 'Last day (YYYY-MM-DD).', 'type' => 'string', 'validate_callback' => array( __CLASS__, 'validate_date' ), ), 'category' => array( 'description' => 'Category slug or ID.', 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), 'limit' => array( 'description' => 'How many events, default 50, at most 200.', 'type' => 'integer', 'default' => 50, 'sanitize_callback' => 'absint', ), 'search' => array( 'description' => 'Word or part of a word in title, description, location name or city; at most 80 characters. Since 1.8.0.', 'type' => 'string', 'sanitize_callback' => array( 'ThreeCal_Event', 'sanitize_search' ), ), 'day' => array( 'description' => 'One day (YYYY-MM-DD): every event that takes place on that day, past days included. Replaces from and to. Since 1.8.0.', 'type' => 'string', 'validate_callback' => array( __CLASS__, 'validate_date' ), ), ), ) ); } /** * YYYY-MM-DD or empty. * * @param mixed $value Parameter. * @return true|WP_Error */ public static function validate_date( $value ) { if ( null === $value || '' === $value ) { return true; } if ( is_string( $value ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $value ) && false !== strtotime( $value ) ) { return true; } return new WP_Error( 'rest_invalid_param', __( 'Dates must be written as YYYY-MM-DD.', '3task-calendar' ), array( 'status' => 400 ) ); } /** * Answer the request. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function respond( $request ) { $from = (string) $request->get_param( 'from' ); $to = (string) $request->get_param( 'to' ); $category = (string) $request->get_param( 'category' ); $search = ThreeCal_Event::sanitize_search( $request->get_param( 'search' ) ); $day = (string) $request->get_param( 'day' ); $limit = min( self::MAX_LIMIT, max( 1, (int) $request->get_param( 'limit' ) ) ); $args = array( 'status' => ThreeCal_Event::visible_statuses(), 'orderby' => 'start_date', 'order' => 'ASC', 'per_page' => $limit, ); if ( '' !== $day ) { // Every event that touches that day, also in the past. $args['range_start'] = $day . ' 00:00:00'; $args['range_end'] = $day . ' 23:59:59'; } else { $args['ends_after'] = ( '' !== $from ? $from : current_time( 'Y-m-d' ) ) . ' 00:00:00'; if ( '' !== $to ) { $args['start_before'] = $to . ' 23:59:59'; } } if ( '' !== $search ) { $args['search'] = $search; } if ( '' !== $category ) { $cat = ctype_digit( $category ) ? ThreeCal_Category::get( (int) $category ) : ThreeCal_Category::get_by_slug( $category ); $args['category_id'] = ( $cat && ! empty( $cat->id ) ) ? (int) $cat->id : -1; // -1 finds nothing: unknown category gives an empty list, not everything. } $events = -1 === ( isset( $args['category_id'] ) ? $args['category_id'] : 0 ) ? array() : ThreeCal_Event::get_all( $args ); $cats = $events ? ThreeCal_Event::get_categories_for_events( wp_list_pluck( $events, 'id' ) ) : array(); $list = array(); foreach ( $events as $event ) { $list[] = self::format( $event, isset( $cats[ $event->id ] ) ? $cats[ $event->id ] : array() ); } $body = array( 'name' => get_bloginfo( 'name' ), 'url' => home_url( '/' ), 'generated' => wp_date( DATE_ATOM ), 'count' => count( $list ), 'events' => $list, ); $response = new WP_REST_Response( $body, 200 ); $response->header( 'Cache-Control', 'public, max-age=900' ); return $response; } /** * One event for the feed. * * @param ThreeCal_Event $event Event. * @param array $categories Category rows. * @return array */ public static function format( $event, $categories ) { $month_only = $event->is_month_only(); $location = null; if ( $event->location_id ) { $loc = ThreeCal_Location::get( $event->location_id ); if ( $loc ) { $location = array( 'name' => (string) $loc->name, 'address' => (string) $loc->address, 'postal_code' => (string) $loc->postal_code, 'city' => (string) $loc->city, 'country' => (string) $loc->country, ); } } $organizer = $event->get_organizer(); if ( '' === $organizer['name'] && '' === $organizer['url'] ) { $organizer = null; } $description = trim( preg_replace( '/\s+/u', ' ', html_entity_decode( wp_strip_all_tags( (string) $event->description ), ENT_QUOTES | ENT_HTML5, 'UTF-8' ) ) ); return array( 'id' => (int) $event->id, 'title' => (string) $event->title, 'start' => $month_only ? substr( (string) $event->start_date, 0, 7 ) : self::iso( $event->start_date, $event->all_day ), 'end' => ( $month_only || ! $event->end_date ) ? null : self::iso( $event->end_date, $event->all_day ), 'all_day' => (bool) $event->all_day, 'month_only' => $month_only, 'status' => 'cancelled' === $event->status ? 'cancelled' : 'scheduled', 'url' => $event->url ? esc_url_raw( $event->url ) : null, 'description' => '' === $description ? null : $description, 'image' => $event->featured_image ? ( wp_get_attachment_url( $event->featured_image ) ?: null ) : null, 'location' => $location, 'organizer' => $organizer, 'categories' => array_values( array_map( function ( $cat ) { return array( 'id' => (int) $cat->id, 'name' => (string) $cat->name, 'slug' => (string) $cat->slug ); }, (array) $categories ) ), 'ics' => $month_only ? null : ThreeCal_ICS::event_url( $event->id ), ); } /** * Stored local date as ISO 8601; all-day dates as plain date. * * @param string $value Y-m-d H:i:s in site time. * @param bool $all_day All-day event. * @return string */ private static function iso( $value, $all_day ) { if ( $all_day ) { return substr( (string) $value, 0, 10 ); } try { $dt = new DateTimeImmutable( (string) $value, wp_timezone() ); return $dt->format( DATE_ATOM ); } catch ( Exception $e ) { return (string) $value; } } /** * on pages that show a calendar. */ public static function alternate_link() { if ( ! is_singular() ) { return; } $post = get_post(); if ( ! $post || ! threecal()->page_has_calendar( $post ) ) { return; } echo '' . "\n"; } }