diff --git a/assets/js/dashboard.js b/assets/js/dashboard.js index ddbc0ef..f37cb47 100644 --- a/assets/js/dashboard.js +++ b/assets/js/dashboard.js @@ -1082,7 +1082,7 @@
- Get single slide - Health check (no auth required)+
if ( isset( $_POST['hssm_client_site_slug'] ) ) { update_option( 'hssm_client_site_slug', sanitize_text_field( $_POST['hssm_client_site_slug'] ) ); } + if ( isset( $_POST['hssm_client_api_key'] ) ) { + update_option( 'hssm_client_api_key', sanitize_text_field( wp_unslash( $_POST['hssm_client_api_key'] ) ) ); + } if ( isset( $_POST['hssm_cache_duration'] ) ) { update_option( 'hssm_cache_duration', absint( $_POST['hssm_cache_duration'] ) ); } @@ -437,7 +463,7 @@ Headers: X-HSSM-API-Key: your-api-key * API section callback */ public function api_section_callback() { - echo '
' . __( 'Manage API keys for companion sites.', 'hi-school-slide-manager' ) . '
'; + echo '' . __( 'Generate keys on the Manager site, then paste one into each companion site under Client API Key.', 'hi-school-slide-manager' ) . '
'; } /** @@ -482,6 +508,15 @@ Headers: X-HSSM-API-Key: your-api-key '; + echo '' . esc_html__( 'Used only in Client mode. Copy a key from the Manager site (Settings → API Keys) and paste it here. Requests fail with 403 until this matches a manager key.', 'hi-school-slide-manager' ) . '
'; + } + /** * Cache duration field */ @@ -531,7 +566,7 @@ Headers: X-HSSM-API-Key: your-api-key } echo ''; - echo '' . __( 'API keys are used by companion sites to authenticate and fetch slides.', 'hi-school-slide-manager' ) . '
'; + echo '' . __( 'Generate a key on this Manager site, then paste it into each companion site under Settings → Client API Key.', 'hi-school-slide-manager' ) . '
'; echo ''; diff --git a/includes/class-hssm-api-client.php b/includes/class-hssm-api-client.php index 42b0038..63d78eb 100644 --- a/includes/class-hssm-api-client.php +++ b/includes/class-hssm-api-client.php @@ -24,11 +24,11 @@ class HSSM_API_Client { public function get_slides() { $api_url = get_option( 'hssm_api_url' ); $site_slug = get_option( 'hssm_client_site_slug' ); - $api_key = 'HI_SCHOOL_FIXED_API_KEY'; // Hardcoded fixed API key + $api_key = get_option( 'hssm_client_api_key', '' ); $cache_duration = get_option( 'hssm_cache_duration', 3600 ); - if ( empty( $api_url ) || empty( $site_slug ) || empty( $api_key ) ) { // Added $api_key check here - return new WP_Error( 'missing_config', __( 'API URL, Site Slug, or API Key not configured.', 'hi-school-slide-manager' ) ); // Updated error message + if ( empty( $api_url ) || empty( $site_slug ) || empty( $api_key ) ) { + return new WP_Error( 'missing_config', __( 'API URL, site slug, or client API key is not configured.', 'hi-school-slide-manager' ) ); } // Check cache first diff --git a/includes/class-hssm-frontend.php b/includes/class-hssm-frontend.php index d8c3c3e..562dab1 100644 --- a/includes/class-hssm-frontend.php +++ b/includes/class-hssm-frontend.php @@ -46,6 +46,7 @@ class HSSM_Frontend { add_action( 'wp_ajax_hssm_save_slide', array( $this, 'ajax_save_slide' ) ); add_action( 'wp_ajax_hssm_upload_image', array( $this, 'ajax_upload_image' ) ); add_action( 'wp_ajax_hssm_get_slides', array( $this, 'ajax_get_slides' ) ); + add_action( 'wp_ajax_hssm_get_slide_for_edit', array( $this, 'ajax_get_slide_for_edit' ) ); add_action( 'wp_ajax_hssm_delete_slide', array( $this, 'ajax_delete_slide' ) ); add_action( 'wp_ajax_hssm_update_slide', array( $this, 'ajax_update_slide' ) ); add_action( 'wp_ajax_hssm_schedule_slide', array( $this, 'ajax_schedule_slide' ) ); @@ -142,20 +143,20 @@ class HSSM_Frontend { private function check_dashboard_access( $check_upload = false ) { // Check if user is logged in if ( ! is_user_logged_in() ) { - error_log( 'Dashboard access denied: User not logged in' ); + hssm_debug_log( 'Dashboard access denied: User not logged in' ); return false; } $user_id = get_current_user_id(); - error_log( 'Checking dashboard access for user ID: ' . $user_id ); + hssm_debug_log( 'Checking dashboard access for user ID: ' . $user_id ); // Check Members plugin capability FIRST if ( function_exists( 'members_user_can' ) && members_user_can( $user_id, 'hssm_manage_slides' ) ) { - error_log( 'Members plugin check: PASS (hssm_manage_slides)' ); + hssm_debug_log( 'Members plugin check: PASS (hssm_manage_slides)' ); if ( $check_upload ) { $can_upload = current_user_can( 'upload_files' ); - error_log( 'Upload files capability check: ' . ($can_upload ? 'PASS' : 'FAIL') ); + hssm_debug_log( 'Upload files capability check: ' . ($can_upload ? 'PASS' : 'FAIL') ); return $can_upload; } @@ -164,11 +165,11 @@ class HSSM_Frontend { // Fallback to WordPress capabilities - be more permissive $can_edit = current_user_can( 'edit_posts' ) || current_user_can( 'publish_posts' ) || current_user_can( 'manage_options' ); - error_log( 'WordPress capability check (edit_posts/publish_posts/manage_options): ' . ($can_edit ? 'PASS' : 'FAIL') ); + hssm_debug_log( 'WordPress capability check (edit_posts/publish_posts/manage_options): ' . ($can_edit ? 'PASS' : 'FAIL') ); if ( $check_upload ) { $can_upload = current_user_can( 'upload_files' ); - error_log( 'Upload files capability check: ' . ($can_upload ? 'PASS' : 'FAIL') ); + hssm_debug_log( 'Upload files capability check: ' . ($can_upload ? 'PASS' : 'FAIL') ); return $can_edit && $can_upload; } @@ -475,7 +476,7 @@ class HSSM_Frontend { * @return array Sanitized data */ private function sanitize_slide_data( $data ) { - error_log( 'Raw form data received: ' . print_r( $data, true ) ); + hssm_debug_log( 'Raw form data received: ' . print_r( $data, true ) ); // Handle target_sites - it might come as an array or need to be extracted $target_sites = array(); @@ -514,8 +515,8 @@ class HSSM_Frontend { 'hide_all_text' => ! empty( $data['hide_all_text'] ) && $data['hide_all_text'] === '1', ); - error_log( 'Sanitized slide data: ' . print_r( $sanitized, true ) ); - error_log( 'Target sites count: ' . count( $sanitized['target_sites'] ) ); + hssm_debug_log( 'Sanitized slide data: ' . print_r( $sanitized, true ) ); + hssm_debug_log( 'Target sites count: ' . count( $sanitized['target_sites'] ) ); return $sanitized; } @@ -556,11 +557,11 @@ class HSSM_Frontend { * @return bool Is valid */ private function validate_slide_data( $data ) { - error_log( 'Validating slide data: ' . print_r( $data, true ) ); + hssm_debug_log( 'Validating slide data: ' . print_r( $data, true ) ); // Required fields if ( empty( $data['title'] ) ) { - error_log( 'Validation failed: Missing title' ); + hssm_debug_log( 'Validation failed: Missing title' ); return false; } @@ -570,17 +571,17 @@ class HSSM_Frontend { foreach ( $data['target_sites'] as $site_slug ) { $term = get_term_by( 'slug', $site_slug, 'hssm_site' ); if ( ! $term || is_wp_error( $term ) ) { - error_log( 'Validation failed: Invalid site slug (not a taxonomy term): ' . $site_slug ); + hssm_debug_log( 'Validation failed: Invalid site slug (not a taxonomy term): ' . $site_slug ); return false; } } } else { - error_log( 'Validation failed: No target sites selected or target_sites is not an array' ); - error_log( 'target_sites value: ' . print_r( $data['target_sites'] ?? 'NOT SET', true ) ); + hssm_debug_log( 'Validation failed: No target sites selected or target_sites is not an array' ); + hssm_debug_log( 'target_sites value: ' . print_r( $data['target_sites'] ?? 'NOT SET', true ) ); return false; } - error_log( 'Validation passed' ); + hssm_debug_log( 'Validation passed' ); return true; } @@ -618,7 +619,7 @@ class HSSM_Frontend { private function save_slide_taxonomies( $slide_id, $data ) { // Set target sites if ( ! empty( $data['target_sites'] ) && is_array( $data['target_sites'] ) ) { - error_log( 'Saving target sites for slide ' . $slide_id . ': ' . print_r( $data['target_sites'], true ) ); + hssm_debug_log( 'Saving target sites for slide ' . $slide_id . ': ' . print_r( $data['target_sites'], true ) ); // Ensure we're using term slugs, not IDs $term_slugs = array(); @@ -638,12 +639,12 @@ class HSSM_Frontend { if ( ! empty( $term_slugs ) ) { $result = wp_set_post_terms( $slide_id, $term_slugs, 'hssm_site' ); - error_log( 'wp_set_post_terms result: ' . print_r( $result, true ) ); + hssm_debug_log( 'wp_set_post_terms result: ' . print_r( $result, true ) ); } else { - error_log( 'No valid term slugs to save' ); + hssm_debug_log( 'No valid term slugs to save' ); } } else { - error_log( 'No target sites to save for slide ' . $slide_id ); + hssm_debug_log( 'No target sites to save for slide ' . $slide_id ); } // Set client @@ -794,54 +795,10 @@ class HSSM_Frontend { $formatted_slides = array(); foreach ( $slides as $slide ) { - $slide_meta = get_post_meta( $slide->ID ); - - // Get selected sites from taxonomy terms - $site_terms = wp_get_post_terms( $slide->ID, 'hssm_site', array( 'fields' => 'all' ) ); - $site_names = array(); - if ( ! empty( $site_terms ) && ! is_wp_error( $site_terms ) ) { - foreach ( $site_terms as $site_term ) { - $site_names[] = $site_term->name; - } + $formatted = $this->format_slide_for_dashboard( $slide ); + if ( $formatted ) { + $formatted_slides[] = $formatted; } - - // Get featured image - $featured_image_url = get_the_post_thumbnail_url( $slide->ID, 'thumbnail' ); - - // Get schedule info - $start_date = isset( $slide_meta['_hssm_start_date'][0] ) ? $slide_meta['_hssm_start_date'][0] : ''; - $end_date = isset( $slide_meta['_hssm_end_date'][0] ) ? $slide_meta['_hssm_end_date'][0] : ''; - $auto_publish = isset( $slide_meta['_hssm_auto_publish'][0] ) ? $slide_meta['_hssm_auto_publish'][0] : ''; - $auto_unpublish = isset( $slide_meta['_hssm_auto_unpublish'][0] ) ? $slide_meta['_hssm_auto_unpublish'][0] : ''; - - // Format status for display - $status_display = ucfirst( $slide->post_status ); - if ( $slide->post_status === 'pending' ) { - $status_display = 'Pending Approval'; - } elseif ( $slide->post_status === 'future' ) { - $status_display = 'Scheduled'; - } - - $formatted_slides[] = array( - 'id' => $slide->ID, - 'title' => $slide->post_title, - 'subtitle' => isset( $slide_meta['_hssm_subtitle'][0] ) ? $slide_meta['_hssm_subtitle'][0] : '', - 'content' => $slide->post_content, - 'status' => $slide->post_status, - 'status_display' => $status_display, - 'sites' => $site_names, - 'target_sites' => $site_names, - 'client' => $this->get_slide_client_name( $slide->ID ), - 'start_date' => $start_date, - 'end_date' => $end_date, - 'auto_publish' => $auto_publish, - 'auto_unpublish' => $auto_unpublish, - 'featured_image_url' => $featured_image_url, - 'created' => $slide->post_date, - 'modified' => $slide->post_modified, - 'buttons' => isset( $slide_meta['_hssm_buttons'][0] ) ? maybe_unserialize( $slide_meta['_hssm_buttons'][0] ) : array(), - 'hide_all_text' => isset( $slide_meta['_hssm_hide_all_text'][0] ) && $slide_meta['_hssm_hide_all_text'][0] === '1' - ); } wp_send_json_success( array( @@ -853,6 +810,134 @@ class HSSM_Frontend { 'has_more' => ( $page * $per_page ) < $total_slides ) ); } + + /** + * AJAX handler for loading a single slide into the dashboard edit form. + * + * Used when the slide is not already in the in-memory list (pagination, filters, deep link). + * + * @since 1.1.2 + */ + public function ajax_get_slide_for_edit() { + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'hssm_dashboard_nonce' ) ) { + wp_send_json_error( array( 'message' => 'Nonce verification failed' ) ); + return; + } + + if ( ! current_user_can( 'edit_posts' ) ) { + wp_send_json_error( array( 'message' => 'You do not have permission to edit slides.' ) ); + return; + } + + if ( ! isset( $_POST['slide_id'] ) || '' === trim( (string) $_POST['slide_id'] ) ) { + wp_send_json_error( array( 'message' => 'Slide ID is required' ) ); + return; + } + + $slide_id = absint( $_POST['slide_id'] ); + $slide = get_post( $slide_id ); + + if ( ! $slide || 'hssm_slide' !== $slide->post_type ) { + wp_send_json_error( array( 'message' => 'Slide not found' ) ); + return; + } + + if ( ! current_user_can( 'edit_post', $slide_id ) ) { + wp_send_json_error( array( 'message' => 'Insufficient permissions' ) ); + return; + } + + $formatted = $this->format_slide_for_dashboard( $slide ); + if ( ! $formatted ) { + wp_send_json_error( array( 'message' => 'Slide not found' ) ); + return; + } + + wp_send_json_success( array( 'slide' => $formatted ) ); + } + + /** + * Format a slide for the dashboard list and edit form. + * + * @since 1.1.2 + * @param int|WP_Post $slide Slide ID or post object. + * @return array|null Formatted slide data. + */ + private function format_slide_for_dashboard( $slide ) { + if ( is_numeric( $slide ) ) { + $slide = get_post( $slide ); + } + + if ( ! $slide instanceof WP_Post || 'hssm_slide' !== $slide->post_type ) { + return null; + } + + $slide_meta = get_post_meta( $slide->ID ); + + $site_terms = wp_get_post_terms( $slide->ID, 'hssm_site', array( 'fields' => 'all' ) ); + $site_names = array(); + if ( ! empty( $site_terms ) && ! is_wp_error( $site_terms ) ) { + foreach ( $site_terms as $site_term ) { + $site_names[] = $site_term->name; + } + } + + $status_display = ucfirst( $slide->post_status ); + if ( 'pending' === $slide->post_status ) { + $status_display = 'Pending Approval'; + } elseif ( 'future' === $slide->post_status ) { + $status_display = 'Scheduled'; + } + + $start_date = isset( $slide_meta['_hssm_start_date'][0] ) ? $slide_meta['_hssm_start_date'][0] : ''; + $end_date = isset( $slide_meta['_hssm_end_date'][0] ) ? $slide_meta['_hssm_end_date'][0] : ''; + + return array( + 'id' => $slide->ID, + 'title' => $slide->post_title, + 'subtitle' => isset( $slide_meta['_hssm_subtitle'][0] ) ? $slide_meta['_hssm_subtitle'][0] : '', + 'content' => $slide->post_content, + 'status' => $slide->post_status, + 'status_display' => $status_display, + 'sites' => $site_names, + 'target_sites' => $site_names, + 'client' => $this->get_slide_client_name( $slide->ID ), + 'start_date' => $this->format_dashboard_date( $start_date ), + 'end_date' => $this->format_dashboard_date( $end_date ), + 'auto_publish' => isset( $slide_meta['_hssm_auto_publish'][0] ) ? $slide_meta['_hssm_auto_publish'][0] : '', + 'auto_unpublish' => isset( $slide_meta['_hssm_auto_unpublish'][0] ) ? $slide_meta['_hssm_auto_unpublish'][0] : '', + 'featured_image' => (int) get_post_thumbnail_id( $slide->ID ), + 'featured_image_url' => get_the_post_thumbnail_url( $slide->ID, 'thumbnail' ), + 'created' => $slide->post_date, + 'modified' => $slide->post_modified, + 'buttons' => isset( $slide_meta['_hssm_buttons'][0] ) ? maybe_unserialize( $slide_meta['_hssm_buttons'][0] ) : array(), + 'hide_all_text' => isset( $slide_meta['_hssm_hide_all_text'][0] ) && '1' === $slide_meta['_hssm_hide_all_text'][0], + ); + } + + /** + * Normalize stored dates for HTML date inputs (YYYY-MM-DD). + * + * @since 1.1.2 + * @param string $value Stored date or datetime. + * @return string + */ + private function format_dashboard_date( $value ) { + if ( empty( $value ) ) { + return ''; + } + + if ( preg_match( '/^(\d{4}-\d{2}-\d{2})/', $value, $matches ) ) { + return $matches[1]; + } + + $timestamp = strtotime( $value ); + if ( ! $timestamp ) { + return ''; + } + + return wp_date( 'Y-m-d', $timestamp ); + } /** * AJAX handler for approving slides @@ -907,7 +992,7 @@ class HSSM_Frontend { $result = wp_update_post( $post_data ); if ( $result ) { - error_log( "Slide $slide_id approved with action: $approve_action" ); + hssm_debug_log( "Slide $slide_id approved with action: $approve_action" ); // Trigger 2: Notification to Creator (Author) when approved $notifications = new HSSM_Notifications(); @@ -1057,18 +1142,18 @@ class HSSM_Frontend { * @since 1.0.0 */ public function ajax_schedule_slide() { - error_log( 'Schedule slide AJAX called' ); - error_log( 'POST data: ' . print_r( $_POST, true ) ); + hssm_debug_log( 'Schedule slide AJAX called' ); + hssm_debug_log( 'POST data: ' . print_r( $_POST, true ) ); // Check nonce if ( ! check_ajax_referer( 'hssm_dashboard_nonce', 'nonce', false ) ) { - error_log( 'Nonce verification failed' ); + hssm_debug_log( 'Nonce verification failed' ); wp_send_json_error( array( 'message' => 'Security check failed' ) ); } // Check user permissions if ( ! current_user_can( 'edit_posts' ) ) { - error_log( 'User permissions check failed' ); + hssm_debug_log( 'User permissions check failed' ); wp_send_json_error( array( 'message' => 'Insufficient permissions' ) ); } @@ -1076,7 +1161,7 @@ class HSSM_Frontend { $slide_data = $this->sanitize_slide_data( $_POST ); if ( ! $this->validate_slide_data( $slide_data ) ) { - error_log( 'Slide data validation failed' ); + hssm_debug_log( 'Slide data validation failed' ); wp_send_json_error( array( 'message' => 'Invalid slide data' ) ); } @@ -1416,7 +1501,7 @@ class HSSM_Frontend { } // --- DEBUG --- - // error_log('HSSM Query Filter Check: is_main=' . ($query->is_main_query()?'Y':'N') . ' post_type=' . print_r($query->get('post_type'), true)); + // hssm_debug_log('HSSM Query Filter Check: is_main=' . ($query->is_main_query()?'Y':'N') . ' post_type=' . print_r($query->get('post_type'), true)); // Only on preview page $obj_id = get_queried_object_id(); diff --git a/includes/class-hssm-main.php b/includes/class-hssm-main.php index 49f8994..385610b 100644 --- a/includes/class-hssm-main.php +++ b/includes/class-hssm-main.php @@ -173,9 +173,7 @@ class HSSM_Main { add_action( 'admin_menu', array( $this, 'add_admin_menus' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) ); - // AJAX hooks for authenticated users - // Note: Main AJAX handlers are in HSSM_Frontend class - add_action( 'wp_ajax_hssm_send_preview_email', array( $this, 'ajax_send_preview_email' ) ); + // AJAX hooks live on HSSM_Frontend. Preview email is still a coming-soon UI stub. // Cron hooks for scheduled slides add_action( 'hssm_check_scheduled_slides', array( $this, 'check_scheduled_slides' ) ); @@ -456,6 +454,7 @@ class HSSM_Main { 'hssm_preview_page_slug' => 'slide-preview', 'hssm_manager_page_slug' => 'slide-manager', 'hssm_api_keys' => array( wp_generate_password( 32, false ) ), // Generate initial API key + 'hssm_client_api_key' => '', ); foreach ( $default_options as $option_name => $option_value ) { @@ -482,6 +481,7 @@ class HSSM_Main { 'hssm_preview_page_slug', 'hssm_manager_page_slug', 'hssm_api_keys', + 'hssm_client_api_key', 'hssm_notification_settings', ); diff --git a/includes/class-hssm-notifications.php b/includes/class-hssm-notifications.php index 4f822d2..0f16501 100644 --- a/includes/class-hssm-notifications.php +++ b/includes/class-hssm-notifications.php @@ -100,7 +100,7 @@ class HSSM_Notifications { $emails = $this->get_emails_array( $settings['editor_emails'] ); if ( empty( $emails ) ) { - error_log( 'HSSM: No Editor emails configured for notification.' ); + hssm_debug_log( 'No Editor emails configured for notification.' ); return; } diff --git a/includes/class-hssm-rest-api.php b/includes/class-hssm-rest-api.php index 436344c..a6251fa 100644 --- a/includes/class-hssm-rest-api.php +++ b/includes/class-hssm-rest-api.php @@ -145,9 +145,10 @@ class HSSM_REST_API { ); // Add date filtering if specified + $date_meta_query = null; if ( $date ) { $target_date = $date . ' 23:59:59'; - $query_args['meta_query'] = array( + $date_meta_query = array( 'relation' => 'AND', array( 'relation' => 'OR', @@ -184,18 +185,26 @@ class HSSM_REST_API { ) ) ); + $query_args['meta_query'] = $date_meta_query; } - // Handle status filtering + // Handle status filtering without dropping an existing date window if ( $status === 'scheduled' ) { - $current_time = current_time( 'mysql' ); - $query_args['meta_query'] = array( - array( - 'key' => '_hssm_start_date', - 'value' => $current_time, - 'compare' => '>' - ) + $scheduled_clause = array( + 'key' => '_hssm_start_date', + 'value' => current_time( 'mysql' ), + 'compare' => '>' ); + + if ( $date_meta_query ) { + $query_args['meta_query'] = array( + 'relation' => 'AND', + $date_meta_query, + $scheduled_clause, + ); + } else { + $query_args['meta_query'] = array( $scheduled_clause ); + } } elseif ( $status === 'all' ) { $query_args['post_status'] = array( 'publish', 'future', 'draft' ); }