ID ); if ( in_array( $slug, array( 'page-slide-dashboard.php', 'page-slide-preview.php', 'page-slide-confirmation.php' ), true ) ) { // First try the theme directory $theme_template = trailingslashit( get_template_directory() ) . $slug; if ( file_exists( $theme_template ) ) { return $theme_template; } // Fallback to our plugin templates $plugin_template = HSSM_PLUGIN_DIR . 'templates/' . $slug; if ( file_exists( $plugin_template ) ) { return $plugin_template; } } } return $template; } /** * Handle frontend page routing * * @since 1.0.0 */ public function handle_page_routing() { global $post; // Check if this is our slide dashboard page if ( is_page() && get_page_template_slug( $post->ID ) === 'page-slide-dashboard.php' ) { // Check authentication with Members plugin if ( ! $this->check_dashboard_access() ) { $this->redirect_to_login(); return; } } } /** * Check if user has access to the dashboard * * @since 1.0.0 * @param bool $check_upload Whether to also check upload capabilities * @return bool Access granted */ 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' ); return false; } $user_id = get_current_user_id(); error_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)' ); if ( $check_upload ) { $can_upload = current_user_can( 'upload_files' ); error_log( 'Upload files capability check: ' . ($can_upload ? 'PASS' : 'FAIL') ); return $can_upload; } return true; } // 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') ); if ( $check_upload ) { $can_upload = current_user_can( 'upload_files' ); error_log( 'Upload files capability check: ' . ($can_upload ? 'PASS' : 'FAIL') ); return $can_edit && $can_upload; } return $can_edit; } /** * Redirect to login page * * @since 1.0.0 */ private function redirect_to_login() { $login_url = wp_login_url( get_permalink() ); wp_redirect( $login_url ); exit; } /** * Enqueue frontend assets * * @since 1.0.0 */ public function enqueue_assets() { global $post; // Only load on our dashboard or preview pages if ( ! is_page() ) { return; } $template_slug = get_page_template_slug( $post->ID ); $is_dashboard = ( $template_slug === 'page-slide-dashboard.php' ); $is_preview = ( $template_slug === 'page-slide-preview.php' ); if ( ! $is_dashboard && ! $is_preview ) { return; } // Check access (preview page may be public, but dashboard requires auth) if ( $is_dashboard && ! $this->check_dashboard_access() ) { return; } // Enqueue styles (both pages need dashboard CSS) wp_enqueue_style( 'hssm-dashboard', HSSM_PLUGIN_URL . 'assets/css/dashboard.css', array(), HSSM_VERSION ); // Dashboard page needs full JS; preview page only needs styles if ( $is_dashboard ) { // Enqueue scripts wp_enqueue_script( 'jquery-ui-sortable' ); wp_enqueue_media(); // For image uploads wp_enqueue_script( 'hssm-dashboard', HSSM_PLUGIN_URL . 'assets/js/dashboard.js', array( 'jquery', 'jquery-ui-sortable' ), HSSM_VERSION, true ); // Localize script wp_localize_script( 'hssm-dashboard', 'hssmDashboard', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'hssm_dashboard_nonce' ), 'strings' => array( 'save_success' => __( 'Slide saved successfully!', 'hi-school-slide-manager' ), 'save_error' => __( 'Error saving slide. Please try again.', 'hi-school-slide-manager' ), 'delete_confirm' => __( 'Are you sure you want to delete this slide?', 'hi-school-slide-manager' ), 'delete_success' => __( 'Slide deleted successfully!', 'hi-school-slide-manager' ), 'required_fields' => __( 'Please fill in all required fields.', 'hi-school-slide-manager' ), 'max_forms' => __( 'Maximum 5 slide forms allowed at once.', 'hi-school-slide-manager' ), ), ) ); // Add sites data for JavaScript wp_localize_script( 'hssm-dashboard', 'hssmSites', $this->get_available_sites() ); } // Preview page specific JS if ( $is_preview ) { wp_enqueue_script( 'jquery-ui-datepicker' ); // Enqueue jQuery UI CSS (using a reliable CDN or local if available, // but WP doesn't ship with a default jQuery UI theme CSS) wp_enqueue_style( 'jquery-ui-style', 'https://code.jquery.com/ui/1.13.2/themes/smoothness/jquery-ui.css', array(), '1.13.2' ); wp_enqueue_script( 'hssm-preview', HSSM_PLUGIN_URL . 'assets/js/preview.js', array( 'jquery', 'jquery-ui-datepicker' ), HSSM_VERSION, true ); // Localize script for preview page too wp_localize_script( 'hssm-preview', 'hssmPreview', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'hssm_dashboard_nonce' ), 'strings' => array( 'approve_confirm' => __( 'Are you sure you want to approve this slide?', 'hi-school-slide-manager' ), 'approve_success' => __( 'Slide approved successfully!', 'hi-school-slide-manager' ), 'approve_error' => __( 'Error approving slide. Please try again.', 'hi-school-slide-manager' ), ), ) ); } } /** * AJAX handler for image uploads * * @since 1.0.0 */ public function ajax_upload_image() { if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'hssm_dashboard_nonce' ) || ! current_user_can( 'upload_files' ) ) { wp_send_json_error( array( 'message' => 'Unauthorized' ) ); return; } // This is handled by media_handle_upload in standard WP AJAX flow usually, // but if we need custom handling it goes here. // For now, assuming standard media uploader is used on frontend which might not need this // if using wp.media JS library directly. // However, if we need to process the upload specifically for this plugin: if ( ! empty( $_FILES['file'] ) ) { $file = $_FILES['file']; $attachment_id = media_handle_upload( 'file', 0 ); if ( is_wp_error( $attachment_id ) ) { wp_send_json_error( array( 'message' => 'Upload failed: ' . $attachment_id->get_error_message() ) ); } else { wp_send_json_success( array( 'attachment_id' => $attachment_id, 'url' => wp_get_attachment_url( $attachment_id ) ) ); } } else { wp_send_json_error( array( 'message' => 'No file provided' ) ); } } /** * AJAX handler for previewing slides * * @since 1.0.0 */ public function ajax_preview_slides() { if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'hssm_dashboard_nonce' ) || ! current_user_can( 'read' ) ) { wp_send_json_error( array( 'message' => 'Unauthorized' ) ); return; } $site_slug = isset( $_POST['site'] ) ? sanitize_text_field( $_POST['site'] ) : ''; $date = isset( $_POST['date'] ) ? sanitize_text_field( $_POST['date'] ) : current_time( 'Y-m-d' ); // This logic mirrors hssm_get_preview_slides_for_date in the template // We need to fetch slides that would be active on this date for this site $meta_query = array( 'relation' => 'AND', array( 'relation' => 'OR', array( 'key' => '_hssm_start_date', 'value' => '', 'compare' => '=', ), array( 'key' => '_hssm_start_date', 'value' => $date, 'compare' => '<=', ), array( 'key' => '_hssm_start_date', 'compare' => 'NOT EXISTS', ), ), array( 'relation' => 'OR', array( 'key' => '_hssm_end_date', 'value' => $date, 'compare' => '>=', ), array( 'key' => '_hssm_end_date', 'compare' => 'NOT EXISTS', ), array( 'key' => '_hssm_end_date', 'value' => '', 'compare' => '=', ), ), ); $tax_query = array(); if ( ! empty( $site_slug ) ) { $tax_query[] = array( 'taxonomy' => 'hssm_site', 'field' => 'slug', 'terms' => $site_slug, ); } $args = array( 'post_type' => 'hssm_slide', 'post_status' => array( 'publish', 'future' ), 'posts_per_page' => -1, 'orderby' => 'meta_value_num', 'meta_key' => '_hssm_order', 'order' => 'ASC', 'meta_query' => $meta_query, 'tax_query' => $tax_query ); $slides = get_posts( $args ); $formatted_slides = array(); foreach ( $slides as $slide ) { $formatted_slides[] = $this->format_slide_for_frontend( $slide->ID ); } wp_send_json_success( array( 'slides' => $formatted_slides ) ); } /** * AJAX handler for saving slides * * @since 1.0.0 */ public function ajax_save_slide() { if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'hssm_dashboard_nonce' ) || ! $this->check_dashboard_access() ) { wp_send_json_error( array( 'message' => 'Unauthorized' ) ); return; } // Sanitize and validate input $slide_data = $this->sanitize_slide_data( $_POST ); if ( ! $this->validate_slide_data( $slide_data ) ) { wp_send_json_error( array( 'message' => 'Invalid slide data' ) ); } // Create the slide post $post_args = array( 'post_type' => 'hssm_slide', 'post_title' => $slide_data['title'], 'post_content' => $slide_data['content'], 'post_excerpt' => $slide_data['content'], 'post_status' => $slide_data['post_status'], 'post_author' => get_current_user_id(), ); // If scheduling for future, set the post date if ( $slide_data['post_status'] === 'future' && ! empty( $slide_data['start_date'] ) ) { $post_args['post_date'] = $slide_data['start_date'] . ' 00:00:00'; $post_args['post_date_gmt'] = get_gmt_from_date( $post_args['post_date'] ); } $slide_id = wp_insert_post( $post_args ); if ( is_wp_error( $slide_id ) ) { wp_send_json_error( array( 'message' => 'Failed to create slide' ) ); } // Save meta data $this->save_slide_meta( $slide_id, $slide_data ); // Save taxonomies $this->save_slide_taxonomies( $slide_id, $slide_data ); // Set featured image if provided if ( ! empty( $slide_data['featured_image_id'] ) ) { set_post_thumbnail( $slide_id, $slide_data['featured_image_id'] ); } wp_send_json_success( array( 'message' => 'Slide saved successfully!', 'slide_id' => $slide_id, 'slide_data' => $this->format_slide_for_frontend( $slide_id ) ) ); } /** * AJAX handler for image uploads * * @since 1.0.0 */ /** * Sanitize slide data from form * * @since 1.0.0 * @param array $data Raw form data * @return array Sanitized data */ private function sanitize_slide_data( $data ) { error_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(); if ( isset( $data['target_sites'] ) ) { if ( is_array( $data['target_sites'] ) ) { $target_sites = $data['target_sites']; } else { // If it's a single value, make it an array $target_sites = array( $data['target_sites'] ); } } // Handle buttons - decode JSON if it's a string $buttons = $data['buttons'] ?? array(); if ( is_string( $buttons ) ) { $decoded = json_decode( stripslashes( $buttons ), true ); if ( is_array( $decoded ) ) { $buttons = $decoded; } } $sanitized = array( 'title' => sanitize_text_field( $data['title'] ?? '' ), 'subtitle' => sanitize_text_field( $data['subtitle'] ?? '' ), 'content' => wp_kses_post( $data['content'] ?? '' ), 'buttons' => $this->sanitize_buttons( $buttons ), 'target_sites' => array_map( 'sanitize_text_field', $target_sites ), 'client' => sanitize_text_field( $data['client'] ?? '' ), 'start_date' => sanitize_text_field( $data['start_date'] ?? '' ), 'end_date' => sanitize_text_field( $data['end_date'] ?? '' ), 'post_status' => in_array( $data['post_status'] ?? 'draft', array( 'draft', 'publish', 'future' ) ) ? $data['post_status'] : 'draft', 'auto_publish' => ! empty( $data['auto_publish'] ), 'auto_unpublish' => ! empty( $data['auto_unpublish'] ), 'order' => intval( $data['order'] ?? 0 ), 'featured_image_id' => intval( $data['featured_image_id'] ?? 0 ), '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'] ) ); return $sanitized; } /** * Sanitize buttons array * * @since 1.0.0 * @param array $buttons Raw buttons data * @return array Sanitized buttons */ private function sanitize_buttons( $buttons ) { $sanitized = array(); if ( is_array( $buttons ) ) { foreach ( $buttons as $button ) { if ( ! empty( $button['text'] ) || ! empty( $button['url'] ) ) { $sanitized[] = array( 'text' => sanitize_text_field( $button['text'] ?? 'Learn More' ), 'url' => esc_url_raw( $button['url'] ?? '' ), ); } } } // Ensure at least one button if ( empty( $sanitized ) ) { $sanitized[] = array( 'text' => 'Learn More', 'url' => '' ); } return array_slice( $sanitized, 0, 5 ); // Max 5 buttons } /** * Validate slide data * * @since 1.0.0 * @param array $data Sanitized slide data * @return bool Is valid */ private function validate_slide_data( $data ) { error_log( 'Validating slide data: ' . print_r( $data, true ) ); // Required fields if ( empty( $data['title'] ) ) { error_log( 'Validation failed: Missing title' ); return false; } // Validate target sites if ( ! empty( $data['target_sites'] ) && is_array( $data['target_sites'] ) ) { // Check if each site slug exists as a taxonomy term 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 ); 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 ) ); return false; } error_log( 'Validation passed' ); return true; } /** * Save slide meta data * * @since 1.0.0 * @param int $slide_id Slide ID * @param array $data Slide data */ private function save_slide_meta( $slide_id, $data ) { $meta_fields = array( '_hssm_subtitle' => $data['subtitle'], '_hssm_buttons' => $data['buttons'], '_hssm_start_date' => $data['start_date'], '_hssm_end_date' => $data['end_date'], '_hssm_auto_publish' => $data['auto_publish'] ? '1' : '0', '_hssm_auto_unpublish' => $data['auto_unpublish'] ? '1' : '0', '_hssm_order' => $data['order'], '_hssm_hide_all_text' => $data['hide_all_text'] ? '1' : '0', ); foreach ( $meta_fields as $key => $value ) { update_post_meta( $slide_id, $key, $value ); } } /** * Save slide taxonomies * * @since 1.0.0 * @param int $slide_id Slide ID * @param array $data Slide data */ 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 ) ); // Ensure we're using term slugs, not IDs $term_slugs = array(); foreach ( $data['target_sites'] as $site_slug ) { // Check if term exists, if not create it $term = get_term_by( 'slug', $site_slug, 'hssm_site' ); if ( ! $term || is_wp_error( $term ) ) { // Try to create the term if it doesn't exist $term_result = wp_insert_term( $site_slug, 'hssm_site', array( 'slug' => $site_slug ) ); if ( ! is_wp_error( $term_result ) ) { $term_slugs[] = $site_slug; } } else { $term_slugs[] = $site_slug; } } 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 ) ); } else { error_log( 'No valid term slugs to save' ); } } else { error_log( 'No target sites to save for slide ' . $slide_id ); } // Set client if ( ! empty( $data['client'] ) ) { wp_set_post_terms( $slide_id, array( $data['client'] ), 'hssm_client' ); } } /** * Format slide data for frontend display * * @since 1.0.0 * @param int $slide_id Slide ID * @return array Formatted slide data */ private function format_slide_for_frontend( $slide_id ) { $slide = get_post( $slide_id ); return array( 'id' => $slide->ID, 'title' => $slide->post_title, 'subtitle' => get_post_meta( $slide_id, '_hssm_subtitle', true ), 'content' => $slide->post_content, 'buttons' => get_post_meta( $slide_id, '_hssm_buttons', true ), 'featured_image' => get_the_post_thumbnail_url( $slide_id, 'medium' ), 'target_sites' => wp_get_post_terms( $slide_id, 'hssm_site', array( 'fields' => 'slugs' ) ), 'client' => wp_get_post_terms( $slide_id, 'hssm_client', array( 'fields' => 'slugs' ) ), 'status' => $slide->post_status, 'created' => $slide->post_date, ); } /** * AJAX handler for getting slides * * @since 1.0.0 */ public function ajax_get_slides() { 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 view slides.' ) ); return; } // Get pagination parameters $page = isset( $_POST['page'] ) ? intval( $_POST['page'] ) : 1; $per_page = isset( $_POST['per_page'] ) ? intval( $_POST['per_page'] ) : 25; $offset = ( $page - 1 ) * $per_page; // Get filter parameters $status_filter = isset( $_POST['status_filter'] ) ? sanitize_text_field( $_POST['status_filter'] ) : ''; $site_filter = isset( $_POST['site_filter'] ) ? sanitize_text_field( $_POST['site_filter'] ) : ''; $client_filter = isset( $_POST['client_filter'] ) ? sanitize_text_field( $_POST['client_filter'] ) : ''; $month_filter = isset( $_POST['month_filter'] ) ? sanitize_text_field( $_POST['month_filter'] ) : ''; // Format: YYYY-MM $year_filter = isset( $_POST['year_filter'] ) ? intval( $_POST['year_filter'] ) : 0; // Build query args $args = array( 'post_type' => 'hssm_slide', 'post_status' => array( 'publish', 'draft', 'pending', 'future' ), // Include all statuses 'posts_per_page' => $per_page, 'offset' => $offset, 'orderby' => 'date', 'order' => 'DESC', 'tax_query' => array(), 'date_query' => array() ); // Add status filter if ( ! empty( $status_filter ) ) { $args['post_status'] = array( $status_filter ); } // Add site filter using taxonomy query if ( ! empty( $site_filter ) ) { // Get site term by slug $site_term = get_term_by( 'slug', $site_filter, 'hssm_site' ); if ( $site_term && ! is_wp_error( $site_term ) ) { $args['tax_query'][] = array( 'taxonomy' => 'hssm_site', 'field' => 'term_id', 'terms' => $site_term->term_id, ); } } // Add client filter using taxonomy query if ( ! empty( $client_filter ) ) { // Get client term by slug or ID $client_term = is_numeric( $client_filter ) ? get_term( intval( $client_filter ), 'hssm_client' ) : get_term_by( 'slug', $client_filter, 'hssm_client' ); if ( $client_term && ! is_wp_error( $client_term ) ) { $args['tax_query'][] = array( 'taxonomy' => 'hssm_client', 'field' => 'term_id', 'terms' => $client_term->term_id, ); } } // Add month/year filter using date query if ( ! empty( $month_filter ) ) { // Format: YYYY-MM $date_parts = explode( '-', $month_filter ); if ( count( $date_parts ) === 2 && strlen( $date_parts[0] ) === 4 && strlen( $date_parts[1] ) === 2 ) { $year = intval( $date_parts[0] ); $month = intval( $date_parts[1] ); if ( $year > 0 && $month >= 1 && $month <= 12 ) { $args['date_query'][] = array( 'year' => $year, 'month' => $month, ); } } } elseif ( $year_filter > 0 ) { // If only year is specified $args['date_query'][] = array( 'year' => $year_filter, ); } // Set relation if we have multiple taxonomy queries if ( count( $args['tax_query'] ) > 1 ) { $args['tax_query']['relation'] = 'AND'; } elseif ( empty( $args['tax_query'] ) ) { // Remove empty tax_query to avoid unnecessary query conditions unset( $args['tax_query'] ); } // Remove empty date_query if no date filters were applied if ( empty( $args['date_query'] ) ) { unset( $args['date_query'] ); } // Get total count for pagination $count_args = $args; $count_args['posts_per_page'] = -1; $count_args['fields'] = 'ids'; unset( $count_args['offset'] ); $total_slides = count( get_posts( $count_args ) ); // Get slides $slides = get_posts( $args ); $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; } } // 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( 'slides' => $formatted_slides, 'total' => $total_slides, 'page' => $page, 'per_page' => $per_page, 'total_pages' => ceil( $total_slides / $per_page ), 'has_more' => ( $page * $per_page ) < $total_slides ) ); } /** * AJAX handler for approving slides * * @since 1.0.0 */ public function ajax_approve_slide() { 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 ( ! isset( $_POST['slide_id'] ) || '' === trim( (string) $_POST['slide_id'] ) ) { wp_send_json_error( array( 'message' => 'Slide ID is required' ) ); return; } $slide_id = intval( $_POST['slide_id'] ); $approve_action = isset( $_POST['approve_action'] ) ? sanitize_text_field( $_POST['approve_action'] ) : 'publish'; // Check if slide exists $slide = get_post( $slide_id ); if ( ! $slide || $slide->post_type !== 'hssm_slide' ) { wp_send_json_error( array( 'message' => 'Slide not found' ) ); return; } // Check permissions if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error( array( 'message' => 'Permission denied' ) ); return; } $slide_meta = get_post_meta( $slide_id ); $start_date = isset( $slide_meta['_hssm_start_date'][0] ) ? $slide_meta['_hssm_start_date'][0] : ''; if ( $approve_action === 'schedule' && ! empty( $start_date ) ) { // Schedule for future publication $post_data = array( 'ID' => $slide_id, 'post_status' => 'future', 'post_date' => $start_date . ' 00:00:00' ); } else { // Publish immediately $post_data = array( 'ID' => $slide_id, 'post_status' => 'publish' ); } $result = wp_update_post( $post_data ); if ( $result ) { error_log( "Slide $slide_id approved with action: $approve_action" ); // Trigger 2: Notification to Creator (Author) when approved $notifications = new HSSM_Notifications(); $notifications->notify_creator_slide_approved( $slide_id ); wp_send_json_success( array( 'message' => 'Slide approved successfully', 'new_status' => $post_data['post_status'] ) ); } else { wp_send_json_error( array( 'message' => 'Failed to approve slide' ) ); } } /** * AJAX handler for deleting slides * * @since 1.0.0 */ public function ajax_delete_slide() { 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 ( ! isset( $_POST['slide_id'] ) || '' === trim( (string) $_POST['slide_id'] ) ) { wp_send_json_error( array( 'message' => 'Slide ID is required' ) ); return; } $slide_id = intval( $_POST['slide_id'] ); // Check if slide exists and user can delete it $slide = get_post( $slide_id ); if ( ! $slide || $slide->post_type !== 'hssm_slide' ) { wp_send_json_error( array( 'message' => 'Slide not found' ) ); } if ( ! current_user_can( 'delete_post', $slide_id ) ) { wp_send_json_error( array( 'message' => 'Insufficient permissions' ) ); } // Delete the slide $result = wp_delete_post( $slide_id, true ); if ( $result ) { wp_send_json_success( array( 'message' => 'Slide deleted successfully!' ) ); } else { wp_send_json_error( array( 'message' => 'Failed to delete slide' ) ); } } /** * AJAX handler for updating slides * * @since 1.0.0 */ public function ajax_update_slide() { 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 ( ! isset( $_POST['slide_id'] ) || '' === trim( (string) $_POST['slide_id'] ) ) { wp_send_json_error( array( 'message' => 'Slide ID is required' ) ); return; } $slide_id = intval( $_POST['slide_id'] ); // Check if slide exists and user can edit it $slide = get_post( $slide_id ); if ( ! $slide || $slide->post_type !== 'hssm_slide' ) { wp_send_json_error( array( 'message' => 'Slide not found' ) ); } if ( ! current_user_can( 'edit_post', $slide_id ) ) { wp_send_json_error( array( 'message' => 'Insufficient permissions' ) ); } // Sanitize and validate input $slide_data = $this->sanitize_slide_data( $_POST ); if ( ! $this->validate_slide_data( $slide_data ) ) { wp_send_json_error( array( 'message' => 'Invalid slide data' ) ); } // Get post status from request (default to draft if not specified) $post_status = isset( $_POST['post_status'] ) ? sanitize_text_field( $_POST['post_status'] ) : 'draft'; // Validate post status $allowed_statuses = array( 'draft', 'pending', 'publish', 'future' ); if ( ! in_array( $post_status, $allowed_statuses, true ) ) { $post_status = 'draft'; } // Update the slide post $post_args = array( 'ID' => $slide_id, 'post_title' => $slide_data['title'], 'post_content' => $slide_data['content'], 'post_excerpt' => $slide_data['content'], 'post_status' => $post_status, ); // If scheduling for future and start date is set, use that as post_date if ( $post_status === 'future' && ! empty( $slide_data['start_date'] ) ) { $post_args['post_date'] = $slide_data['start_date'] . ' 00:00:00'; $post_args['post_date_gmt'] = get_gmt_from_date( $post_args['post_date'] ); } $result = wp_update_post( $post_args ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => 'Failed to update slide' ) ); } // Update meta data $this->save_slide_meta( $slide_id, $slide_data ); // Update taxonomies $this->save_slide_taxonomies( $slide_id, $slide_data ); // Update featured image if provided if ( ! empty( $slide_data['featured_image_id'] ) ) { set_post_thumbnail( $slide_id, $slide_data['featured_image_id'] ); } // Send approval notification if status is pending if ( $post_status === 'pending' ) { // Trigger 1 (variant): Notification to Editor when updated slide is scheduled for approval $notifications = new HSSM_Notifications(); $notifications->notify_editor_slide_created( $slide_id, 'scheduled' ); } $status_message = ( $post_status === 'pending' ) ? 'Slide updated and scheduled for approval!' : 'Slide updated successfully!'; wp_send_json_success( array( 'message' => $status_message, 'slide_data' => $this->format_slide_for_frontend( $slide_id ) ) ); } /** * AJAX handler for scheduling a slide for approval * * @since 1.0.0 */ public function ajax_schedule_slide() { error_log( 'Schedule slide AJAX called' ); error_log( 'POST data: ' . print_r( $_POST, true ) ); // Check nonce if ( ! check_ajax_referer( 'hssm_dashboard_nonce', 'nonce', false ) ) { error_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' ); wp_send_json_error( array( 'message' => 'Insufficient permissions' ) ); } // Sanitize and validate input $slide_data = $this->sanitize_slide_data( $_POST ); if ( ! $this->validate_slide_data( $slide_data ) ) { error_log( 'Slide data validation failed' ); wp_send_json_error( array( 'message' => 'Invalid slide data' ) ); } // Create slide with 'pending' status for approval $post_args = array( 'post_title' => $slide_data['title'], 'post_content' => $slide_data['content'], 'post_excerpt' => $slide_data['content'], 'post_type' => 'hssm_slide', 'post_status' => 'pending', 'meta_input' => array( '_hssm_scheduled_for_approval' => current_time( 'timestamp' ), '_hssm_submitted_by' => get_current_user_id(), ) ); $slide_id = wp_insert_post( $post_args ); if ( is_wp_error( $slide_id ) || ! $slide_id ) { wp_send_json_error( array( 'message' => 'Failed to schedule slide' ) ); } // Save meta data and taxonomies $this->save_slide_meta( $slide_id, $slide_data ); $this->save_slide_taxonomies( $slide_id, $slide_data ); // Set featured image if ( ! empty( $slide_data['featured_image_id'] ) ) { set_post_thumbnail( $slide_id, $slide_data['featured_image_id'] ); } // Send approval email notifications // Trigger 1 (variant): Notification to Editor when slide is scheduled for approval $notifications = new HSSM_Notifications(); $notifications->notify_editor_slide_created( $slide_id, 'scheduled' ); wp_send_json_success( array( 'message' => 'Slide scheduled for approval. Email notifications sent.', 'slide_id' => $slide_id ) ); } /** * AJAX handler for publishing a slide immediately * * @since 1.0.0 */ public function ajax_publish_slide() { // Check nonce if ( ! check_ajax_referer( 'hssm_dashboard_nonce', 'nonce', false ) ) { wp_send_json_error( array( 'message' => 'Security check failed' ) ); } // Check user permissions if ( ! current_user_can( 'publish_posts' ) ) { wp_send_json_error( array( 'message' => 'Insufficient permissions' ) ); } // Sanitize and validate input $slide_data = $this->sanitize_slide_data( $_POST ); if ( ! $this->validate_slide_data( $slide_data ) ) { wp_send_json_error( array( 'message' => 'Invalid slide data' ) ); } // Create slide with 'publish' status $post_args = array( 'post_title' => $slide_data['title'], 'post_content' => $slide_data['content'], 'post_excerpt' => $slide_data['content'], 'post_type' => 'hssm_slide', 'post_status' => 'publish', 'meta_input' => array( '_hssm_published_immediately' => current_time( 'timestamp' ), '_hssm_published_by' => get_current_user_id(), ) ); $slide_id = wp_insert_post( $post_args ); if ( is_wp_error( $slide_id ) || ! $slide_id ) { wp_send_json_error( array( 'message' => 'Failed to publish slide' ) ); } // Save meta data and taxonomies $this->save_slide_meta( $slide_id, $slide_data ); $this->save_slide_taxonomies( $slide_id, $slide_data ); // Set featured image if ( ! empty( $slide_data['featured_image_id'] ) ) { set_post_thumbnail( $slide_id, $slide_data['featured_image_id'] ); } // Send approval/notification emails // Trigger 1: Notification to Editor when slide is scheduled or published $notifications = new HSSM_Notifications(); $notifications->notify_editor_slide_created( $slide_id, 'published' ); wp_send_json_success( array( 'message' => 'Slide published successfully. Approval notifications sent.', 'slide_id' => $slide_id ) ); } /** * Send email notifications for slide approval workflow * * @since 1.0.0 * @param int $slide_id The slide ID * @param string $action The action taken ('scheduled' or 'published') */ private function send_approval_notification( $slide_id, $action ) { // Use the new Notification class $notifications = new HSSM_Notifications(); $notifications->notify_editor_slide_created( $slide_id, $action ); } /** * Get formatted text of target sites for a slide * * @since 1.0.0 * @param int $slide_id The slide ID * @return string Formatted target sites text */ private function get_slide_target_sites_text( $slide_id ) { $target_sites = get_post_meta( $slide_id, '_hssm_target_sites', true ); if ( empty( $target_sites ) || ! is_array( $target_sites ) ) { return 'None selected'; } $site_names = array(); foreach ( $target_sites as $site_id ) { // Get site name - this could be enhanced to use actual site data $site_names[] = ucfirst( str_replace( '-', ' ', $site_id ) ); } return implode( ', ', $site_names ); } /** * Get client name for a slide from taxonomy * * @since 1.0.0 * @param int $slide_id Slide ID * @return string Client name or empty string */ private function get_slide_client_name( $slide_id ) { $client_terms = wp_get_post_terms( $slide_id, 'hssm_client', array( 'fields' => 'all' ) ); if ( ! empty( $client_terms ) && ! is_wp_error( $client_terms ) ) { return $client_terms[0]->name; } return ''; } /** * Get available sites for targeting * * @since 1.0.0 * @return array Available sites data */ private function get_available_sites() { // Use centralized method from HSSM_Main if available if ( class_exists( 'HSSM_Main' ) && method_exists( 'HSSM_Main', 'get_companion_sites' ) ) { $sites = HSSM_Main::get_companion_sites(); $formatted_sites = array(); foreach ( $sites as $site ) { $formatted_sites[] = array( 'id' => $site['slug'], 'name' => $site['name'] ); } return $formatted_sites; } // Fallback for safety return array( array( 'id' => 'hi-school', 'name' => 'Hi-School Pharmacy' ), array( 'id' => 'one-stop', 'name' => 'One Stop Hardware' ), array( 'id' => 'ace', 'name' => 'Ace Hardware' ) ); } /** * AJAX handler for duplicating slides * * @since 1.0.0 */ public function ajax_duplicate_slide() { if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'hssm_dashboard_nonce' ) || ! $this->check_dashboard_access() ) { wp_send_json_error( array( 'message' => 'Unauthorized' ) ); return; } if ( ! isset( $_POST['slide_id'] ) || '' === trim( (string) $_POST['slide_id'] ) ) { wp_send_json_error( array( 'message' => 'Slide ID is required' ) ); return; } $original_id = intval( $_POST['slide_id'] ); $original_post = get_post( $original_id ); if ( ! $original_post || $original_post->post_type !== 'hssm_slide' ) { wp_send_json_error( array( 'message' => 'Slide not found' ) ); } // Create new post $new_post_data = array( 'post_title' => $original_post->post_title . ' (Copy)', 'post_content' => $original_post->post_content, 'post_status' => 'draft', 'post_type' => 'hssm_slide', 'post_author' => get_current_user_id(), ); $new_id = wp_insert_post( $new_post_data ); if ( is_wp_error( $new_id ) ) { wp_send_json_error( array( 'message' => 'Failed to duplicate slide' ) ); } // Copy all meta fields $meta_fields = get_post_meta( $original_id ); foreach ( $meta_fields as $key => $values ) { foreach ( $values as $value ) { add_post_meta( $new_id, $key, maybe_unserialize( $value ) ); } } wp_send_json_success( array( 'message' => 'Slide duplicated successfully', 'new_id' => $new_id ) ); } /** * AJAX handler for toggling slide status * * @since 1.0.0 */ public function ajax_toggle_slide_status() { if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'hssm_dashboard_nonce' ) || ! $this->check_dashboard_access() ) { wp_send_json_error( array( 'message' => 'Unauthorized' ) ); return; } if ( ! isset( $_POST['slide_id'] ) || '' === trim( (string) $_POST['slide_id'] ) ) { wp_send_json_error( array( 'message' => 'Slide ID is required' ) ); return; } $slide_id = intval( $_POST['slide_id'] ); $post = get_post( $slide_id ); if ( ! $post || $post->post_type !== 'hssm_slide' ) { wp_send_json_error( array( 'message' => 'Slide not found' ) ); } // Toggle status $new_status = ( $post->post_status === 'publish' ) ? 'draft' : 'publish'; $updated = wp_update_post( array( 'ID' => $slide_id, 'post_status' => $new_status ) ); if ( is_wp_error( $updated ) ) { wp_send_json_error( array( 'message' => 'Failed to update slide status' ) ); } // Trigger notification if published via toggle? // Logic check: If toggled to publish, it means it's Live. if ( $new_status === 'publish' ) { // Trigger 3: Notification to ITCGuys when slide goes Live (via manual toggle) $notifications = new HSSM_Notifications(); $notifications->notify_itc_slide_live( $slide_id ); } wp_send_json_success( array( 'message' => 'Slide status updated successfully', 'new_status' => $new_status ) ); } /** * AJAX handler to get notification settings */ public function ajax_get_notification_settings() { if ( ! check_ajax_referer( 'hssm_dashboard_nonce', 'nonce', false ) || ! $this->check_dashboard_access() ) { wp_send_json_error( array( 'message' => 'Unauthorized' ) ); } $settings = HSSM_Notifications::get_settings(); wp_send_json_success( $settings ); } /** * AJAX handler to save notification settings */ public function ajax_save_notification_settings() { if ( ! check_ajax_referer( 'hssm_dashboard_nonce', 'nonce', false ) || ! $this->check_dashboard_access() ) { wp_send_json_error( array( 'message' => 'Unauthorized' ) ); } // Pass raw values to the class method which handles sanitization $settings = array( 'editor_emails' => isset($_POST['editor_emails']) ? wp_unslash( $_POST['editor_emails'] ) : '', 'itc_emails' => isset($_POST['itc_emails']) ? wp_unslash( $_POST['itc_emails'] ) : '' ); if ( HSSM_Notifications::save_settings( $settings ) ) { wp_send_json_success( array( 'message' => 'Settings saved' ) ); } else { // It might fail if no changes were made, which is fine, but we'll return success anyway or handle accordingly wp_send_json_success( array( 'message' => 'Settings saved (or no changes)' ) ); } } /** * Filter Slides Query on Preview Page * We use pre_get_posts because it's more reliable at catching all queries. * * @since 1.0.0 * @param WP_Query $query The query object. */ public function filter_preview_slides_query( $query ) { if ( is_admin() ) { return; } // --- DEBUG --- // error_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(); if ( ! $obj_id ) { global $post; $obj_id = $post->ID ?? 0; } $template = get_page_template_slug( $obj_id ); if ( $template !== 'page-slide-preview.php' ) { return; } // Don't filter the main query (the page itself) if ( $query->is_main_query() ) { return; } // Check if this query is intended for our slides. // We look for queries that either already target hssm_slide // OR queries from Divi loops that might need redirection. $post_type = $query->get( 'post_type' ); // If it's a menu or something else we recognize, bail if ( $query->get( 'post_type' ) === 'nav_menu_item' ) { return; } // Force the query to slides for the preview $query->set( 'post_type', 'hssm_slide' ); $query->set( 'posts_per_page', -1 ); $query->set( 'post_status', array( 'publish', 'future', 'pending' ) ); // Filter parameters from URL $date_param = isset( $_GET['hssm_date'] ) ? sanitize_text_field( $_GET['hssm_date'] ) : current_time( 'Y-m-d' ); $site_param = isset( $_GET['hssm_site'] ) ? sanitize_text_field( $_GET['hssm_site'] ) : ''; // Apply Meta Query for Date (evergreen: no start/end = always show) $meta_query = array( 'relation' => 'AND', array( 'relation' => 'OR', array( 'key' => '_hssm_start_date', 'value' => '', 'compare' => '=', ), array( 'key' => '_hssm_start_date', 'value' => $date_param, 'compare' => '<=', ), array( 'key' => '_hssm_start_date', 'compare' => 'NOT EXISTS', ), ), array( 'relation' => 'OR', array( 'key' => '_hssm_end_date', 'value' => $date_param, 'compare' => '>=', ), array( 'key' => '_hssm_end_date', 'compare' => 'NOT EXISTS', ), array( 'key' => '_hssm_end_date', 'value' => '', 'compare' => '=', ), ), ); $query->set( 'meta_query', $meta_query ); // Apply Tax Query for Site if ( ! empty( $site_param ) ) { $tax_query = array( array( 'taxonomy' => 'hssm_site', 'field' => 'slug', 'terms' => $site_param, ) ); $query->set( 'tax_query', $tax_query ); } // Ensure ordering matches $query->set( 'orderby', 'meta_value_num' ); $query->set( 'meta_key', '_hssm_order' ); $query->set( 'order', 'ASC' ); $query->set( 'ignore_sticky_posts', true ); } }