[a-zA-Z0-9-]+)', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_slides_for_site' ), 'permission_callback' => array( $this, 'check_api_permissions' ), 'args' => array( 'site' => array( 'required' => true, 'validate_callback' => array( $this, 'validate_site_slug' ), 'sanitize_callback' => 'sanitize_text_field', 'description' => 'Site slug (hi-school, one-stop, ace)', ), 'date' => array( 'required' => false, 'validate_callback' => array( $this, 'validate_date' ), 'sanitize_callback' => 'sanitize_text_field', 'description' => 'Date to get slides for (YYYY-MM-DD format)', ), 'limit' => array( 'required' => false, 'default' => 10, 'validate_callback' => array( $this, 'validate_limit' ), 'sanitize_callback' => 'absint', 'description' => 'Maximum number of slides to return (1-50)', ), 'status' => array( 'required' => false, 'default' => 'active', 'validate_callback' => array( $this, 'validate_status' ), 'sanitize_callback' => 'sanitize_text_field', 'description' => 'Slide status: active, scheduled, all', ), ), ) ); // Get all available sites register_rest_route( self::NAMESPACE, '/sites', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_available_sites' ), 'permission_callback' => array( $this, 'check_api_permissions' ), ) ); // Get slide by ID register_rest_route( self::NAMESPACE, '/slide/(?P\d+)', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_slide_by_id' ), 'permission_callback' => array( $this, 'check_api_permissions' ), 'args' => array( 'id' => array( 'required' => true, 'validate_callback' => array( $this, 'validate_slide_id' ), 'sanitize_callback' => 'absint', 'description' => 'Slide ID', ), ), ) ); // Health check endpoint register_rest_route( self::NAMESPACE, '/health', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'health_check' ), 'permission_callback' => '__return_true', // Public endpoint ) ); } /** * Get slides for a specific site * * @since 1.0.0 * @param WP_REST_Request $request Request object * @return WP_REST_Response|WP_Error Response object */ public function get_slides_for_site( $request ) { $site_slug = $request->get_param( 'site' ); $date = $request->get_param( 'date' ); $limit = $request->get_param( 'limit' ); $status = $request->get_param( 'status' ); // Get the site term $site_term = get_term_by( 'slug', $site_slug, 'hssm_site' ); if ( ! $site_term ) { return new WP_Error( 'invalid_site', 'Invalid site slug', array( 'status' => 404 ) ); } // Build query args $query_args = array( 'post_type' => 'hssm_slide', 'posts_per_page' => $limit, 'post_status' => 'publish', 'meta_key' => '_hssm_order', 'orderby' => 'meta_value_num', 'order' => 'ASC', 'tax_query' => array( array( 'taxonomy' => 'hssm_site', 'field' => 'term_id', 'terms' => $site_term->term_id, ), ), ); // Add date filtering if specified if ( $date ) { $target_date = $date . ' 23:59:59'; $query_args['meta_query'] = array( 'relation' => 'AND', array( 'relation' => 'OR', array( 'key' => '_hssm_start_date', 'value' => '', 'compare' => '=' ), array( 'key' => '_hssm_start_date', 'value' => $target_date, 'compare' => '<=' ), array( 'key' => '_hssm_start_date', 'compare' => 'NOT EXISTS' ) ), array( 'relation' => 'OR', array( 'key' => '_hssm_end_date', 'value' => '', 'compare' => '=' ), array( 'key' => '_hssm_end_date', 'value' => $target_date, 'compare' => '>=' ), array( 'key' => '_hssm_end_date', 'compare' => 'NOT EXISTS' ) ) ); } // Handle status filtering if ( $status === 'scheduled' ) { $current_time = current_time( 'mysql' ); $query_args['meta_query'] = array( array( 'key' => '_hssm_start_date', 'value' => $current_time, 'compare' => '>' ) ); } elseif ( $status === 'all' ) { $query_args['post_status'] = array( 'publish', 'future', 'draft' ); } $slides_query = new WP_Query( $query_args ); $slides_data = array(); if ( $slides_query->have_posts() ) { while ( $slides_query->have_posts() ) { $slides_query->the_post(); $slides_data[] = $this->format_slide_data( get_post() ); } wp_reset_postdata(); } $response_data = array( 'slides' => $slides_data, 'site' => array( 'slug' => $site_slug, 'name' => $site_term->name, 'description' => $site_term->description, ), 'meta' => array( 'total_slides' => count( $slides_data ), 'query_date' => $date ?: current_time( 'Y-m-d' ), 'status_filter' => $status, 'timestamp' => current_time( 'c' ), 'cache_duration' => get_option( 'hssm_cache_duration', 3600 ), ), ); return rest_ensure_response( $response_data ); } /** * Get all available sites * * @since 1.0.0 * @param WP_REST_Request $request Request object * @return WP_REST_Response Response object */ public function get_available_sites( $request ) { $sites = get_terms( array( 'taxonomy' => 'hssm_site', 'hide_empty' => false, ) ); $sites_data = array(); if ( ! is_wp_error( $sites ) ) { foreach ( $sites as $site ) { $sites_data[] = array( 'slug' => $site->slug, 'name' => $site->name, 'description' => $site->description, 'slide_count' => $this->get_site_slide_count( $site->term_id ), ); } } return rest_ensure_response( array( 'sites' => $sites_data, 'total' => count( $sites_data ), ) ); } /** * Get slide by ID * * @since 1.0.0 * @param WP_REST_Request $request Request object * @return WP_REST_Response|WP_Error Response object */ public function get_slide_by_id( $request ) { $slide_id = $request->get_param( 'id' ); $slide = get_post( $slide_id ); if ( ! $slide || $slide->post_type !== 'hssm_slide' ) { return new WP_Error( 'slide_not_found', 'Slide not found', array( 'status' => 404 ) ); } return rest_ensure_response( $this->format_slide_data( $slide ) ); } /** * Health check endpoint * * @since 1.0.0 * @param WP_REST_Request $request Request object * @return WP_REST_Response Response object */ public function health_check( $request ) { return rest_ensure_response( array( 'status' => 'healthy', 'plugin' => 'Hi-School Slide Manager', 'version' => HSSM_VERSION, 'timestamp' => current_time( 'c' ), 'endpoints' => array( 'slides' => rest_url( self::NAMESPACE . '/slides/{site}' ), 'sites' => rest_url( self::NAMESPACE . '/sites' ), 'slide' => rest_url( self::NAMESPACE . '/slide/{id}' ), ), ) ); } /** * Check API permissions * * @since 1.0.0 * @param WP_REST_Request $request Request object * @return bool|WP_Error Permission check result */ public function check_api_permissions( $request ) { // Get the API key from header or query parameter $api_key = $request->get_header( 'X-HSSM-API-Key' ); if ( ! $api_key ) { $api_key = $request->get_param( 'api_key' ); } if ( ! $api_key ) { return new WP_Error( 'missing_api_key', 'API key required', array( 'status' => 401 ) ); } // Validate API key $valid_keys = get_option( 'hssm_api_keys', array() ); if ( ! in_array( $api_key, $valid_keys ) ) { return new WP_Error( 'invalid_api_key', 'Invalid API key', array( 'status' => 403 ) ); } return true; } /** * Format slide data for API response * * @since 1.0.0 * @param WP_Post $slide Slide post object * @return array Formatted slide data */ private function format_slide_data( $slide ) { $slide_data = array( 'id' => $slide->ID, 'title' => $slide->post_title, 'subtitle' => get_post_meta( $slide->ID, '_hssm_subtitle', true ), 'content' => $slide->post_content, 'excerpt' => $slide->post_excerpt, 'buttons' => get_post_meta( $slide->ID, '_hssm_buttons', true ) ?: array(), 'order' => (int) get_post_meta( $slide->ID, '_hssm_order', true ), 'background_image' => $this->get_slide_background_image( $slide->ID ), 'schedule' => array( 'start_date' => get_post_meta( $slide->ID, '_hssm_start_date', true ), 'end_date' => get_post_meta( $slide->ID, '_hssm_end_date', true ), 'auto_publish' => get_post_meta( $slide->ID, '_hssm_auto_publish', true ) === '1', 'auto_unpublish' => get_post_meta( $slide->ID, '_hssm_auto_unpublish', true ) === '1', ), 'target_sites' => $this->get_slide_target_sites( $slide->ID ), 'client' => $this->get_slide_client( $slide->ID ), 'status' => $slide->post_status, 'created' => $slide->post_date, 'modified' => $slide->post_modified, ); return $slide_data; } /** * Get slide background image data * * @since 1.0.0 * @param int $slide_id Slide ID * @return array|null Background image data */ private function get_slide_background_image( $slide_id ) { $thumbnail_id = get_post_thumbnail_id( $slide_id ); if ( ! $thumbnail_id ) { return null; } $image_data = wp_get_attachment_image_src( $thumbnail_id, 'full' ); if ( ! $image_data ) { return null; } return array( 'id' => $thumbnail_id, 'url' => $image_data[0], 'width' => $image_data[1], 'height' => $image_data[2], 'alt' => get_post_meta( $thumbnail_id, '_wp_attachment_image_alt', true ), 'sizes' => array( 'full' => wp_get_attachment_image_src( $thumbnail_id, 'full' )[0], 'large' => wp_get_attachment_image_src( $thumbnail_id, 'large' )[0], 'medium' => wp_get_attachment_image_src( $thumbnail_id, 'medium' )[0], 'thumbnail' => wp_get_attachment_image_src( $thumbnail_id, 'thumbnail' )[0], ), ); } /** * Get slide target sites * * @since 1.0.0 * @param int $slide_id Slide ID * @return array Target sites data */ private function get_slide_target_sites( $slide_id ) { $sites = wp_get_post_terms( $slide_id, 'hssm_site' ); $sites_data = array(); if ( ! is_wp_error( $sites ) ) { foreach ( $sites as $site ) { $sites_data[] = array( 'slug' => $site->slug, 'name' => $site->name, ); } } return $sites_data; } /** * Get slide client * * @since 1.0.0 * @param int $slide_id Slide ID * @return array|null Client data */ private function get_slide_client( $slide_id ) { $clients = wp_get_post_terms( $slide_id, 'hssm_client' ); if ( ! is_wp_error( $clients ) && ! empty( $clients ) ) { $client = $clients[0]; return array( 'slug' => $client->slug, 'name' => $client->name, ); } return null; } /** * Get slide count for a site * * @since 1.0.0 * @param int $site_term_id Site term ID * @return int Slide count */ private function get_site_slide_count( $site_term_id ) { $query = new WP_Query( array( 'post_type' => 'hssm_slide', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', 'tax_query' => array( array( 'taxonomy' => 'hssm_site', 'field' => 'term_id', 'terms' => $site_term_id, ), ), ) ); return $query->found_posts; } /** * Validate site slug parameter * * @since 1.0.0 * @param string $value Site slug * @param WP_REST_Request $request Request object * @param string $param Parameter name * @return bool Validation result */ public function validate_site_slug( $value, $request, $param ) { $companion_sites = HSSM_Main::get_companion_sites(); $valid_sites = wp_list_pluck( $companion_sites, 'slug' ); return in_array( $value, $valid_sites ); } /** * Validate date parameter * * @since 1.0.0 * @param string $value Date string * @param WP_REST_Request $request Request object * @param string $param Parameter name * @return bool Validation result */ public function validate_date( $value, $request, $param ) { return (bool) strtotime( $value ); } /** * Validate limit parameter * * @since 1.0.0 * @param int $value Limit value * @param WP_REST_Request $request Request object * @param string $param Parameter name * @return bool Validation result */ public function validate_limit( $value, $request, $param ) { return is_numeric( $value ) && $value >= 1 && $value <= 50; } /** * Validate status parameter * * @since 1.0.0 * @param string $value Status value * @param WP_REST_Request $request Request object * @param string $param Parameter name * @return bool Validation result */ public function validate_status( $value, $request, $param ) { $valid_statuses = array( 'active', 'scheduled', 'all' ); return in_array( $value, $valid_statuses ); } /** * Validate slide ID parameter * * @since 1.0.0 * @param int $value Slide ID * @param WP_REST_Request $request Request object * @param string $param Parameter name * @return bool Validation result */ public function validate_slide_id( $value, $request, $param ) { return is_numeric( $value ) && $value > 0; } }