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; } /** * Prevent ?hssm_slide= from hijacking the preview page. * * The slide CPT used query_var "hssm_slide", so that GET param turned * the page request into a missing slide query and WordPress 404'd. * * @since 1.1.3 * @param array $query_vars Parsed request vars. * @return array */ public function keep_preview_page_query( $query_vars ) { if ( empty( $query_vars['pagename'] ) ) { return $query_vars; } unset( $query_vars['hssm_slide'], $query_vars['hssm_slide_item'] ); if ( isset( $query_vars['name'] ) && isset( $query_vars['post_type'] ) && 'hssm_slide' === $query_vars['post_type'] ) { unset( $query_vars['name'], $query_vars['post_type'] ); } return $query_vars; } /** * Handle frontend page routing * * @since 1.0.0 */ public function handle_page_routing() { global $post; if ( ! is_page() || ! $post ) { return; } $template = get_page_template_slug( $post->ID ); if ( ! in_array( $template, array( 'page-slide-dashboard.php', 'page-slide-preview.php' ), true ) ) { return; } if ( ! $this->check_dashboard_access() ) { $this->redirect_to_login(); } } /** * Mark dashboard and preview as plugin tool pages for CSS. * * @since 1.1.5 * @param array $classes Body classes. * @return array */ public function add_tool_page_body_class( $classes ) { if ( ! is_page() ) { return $classes; } $template = get_page_template_slug(); if ( in_array( $template, array( 'page-slide-dashboard.php', 'page-slide-preview.php' ), true ) ) { $classes[] = 'hssm-tool-page'; } return $classes; } /** * 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() ) { hssm_debug_log( 'Dashboard access denied: User not logged in' ); return false; } $user_id = get_current_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' ) ) { hssm_debug_log( 'Members plugin check: PASS (hssm_manage_slides)' ); if ( $check_upload ) { $can_upload = current_user_can( 'upload_files' ); hssm_debug_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' ); 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' ); hssm_debug_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() { $redirect = home_url( add_query_arg( array() ) ); wp_safe_redirect( wp_login_url( $redirect ) ); 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; } if ( ! $this->check_dashboard_access() ) { return; } wp_enqueue_style( 'dashicons' ); // 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' ), 'previewUrl' => hssm_get_page_url_by_template( 'page-slide-preview.php', '/slide-preview/' ), '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' ), 'preview_save_first' => __( 'Save the slide first, then preview it as a banner.', 'hi-school-slide-manager' ), 'approve_confirm' => __( 'Approve this slide?', 'hi-school-slide-manager' ), 'approve_success' => __( 'Slide approved.', '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' ), ), ) ); } } /** * 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 ) { 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(); 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 ), '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' ), true ) ? ( $data['post_status'] ?? 'draft' ) : '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', ); hssm_debug_log( 'Sanitized slide data: ' . print_r( $sanitized, true ) ); hssm_debug_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 ) { hssm_debug_log( 'Validating slide data: ' . print_r( $data, true ) ); // Required fields if ( empty( $data['title'] ) ) { hssm_debug_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 ) ) { hssm_debug_log( 'Validation failed: Invalid site slug (not a taxonomy term): ' . $site_slug ); return false; } } } else { 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; } hssm_debug_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'] ) ) { 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(); 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' ); hssm_debug_log( 'wp_set_post_terms result: ' . print_r( $result, true ) ); } else { hssm_debug_log( 'No valid term slugs to save' ); } } else { hssm_debug_log( 'No target sites to save for slide ' . $slide_id ); } } /** * 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' ) ), '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'] ) : ''; if ( 'scheduled' === $status_filter ) { $status_filter = 'future'; } $site_filter = isset( $_POST['site_filter'] ) ? sanitize_text_field( $_POST['site_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 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 ) { $formatted = $this->format_slide_for_dashboard( $slide ); if ( $formatted ) { $formatted_slides[] = $formatted; } } 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 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(); $site_slugs = array(); if ( ! empty( $site_terms ) && ! is_wp_error( $site_terms ) ) { foreach ( $site_terms as $site_term ) { $site_names[] = $site_term->name; $site_slugs[] = $site_term->slug; } } $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, 'site_slugs' => $site_slugs, 'target_sites' => $site_names, '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 * * @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; } if ( ! $this->check_dashboard_access() ) { 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 ) { hssm_debug_log( "Slide $slide_id approved with action: $approve_action" ); $notifications = new HSSM_Notifications(); $notifications->notify_creator_slide_approved( $slide_id ); $notifications->notify_itc_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() { 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 ) ) { hssm_debug_log( 'Nonce verification failed' ); wp_send_json_error( array( 'message' => 'Security check failed' ) ); } // Check user permissions if ( ! current_user_can( 'edit_posts' ) ) { hssm_debug_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 ) ) { hssm_debug_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 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' ) ); } /** * Nest preview and confirmation pages under the slide dashboard. * * @since 1.1.3 */ public function maybe_nest_plugin_pages() { if ( ! current_user_can( 'manage_options' ) ) { return; } $dashboard_query = new WP_Query( array( 'post_type' => 'page', 'post_status' => array( 'publish', 'private' ), 'posts_per_page' => 1, 'no_found_rows' => true, 'meta_key' => '_wp_page_template', 'meta_value' => 'page-slide-dashboard.php', ) ); if ( ! $dashboard_query->have_posts() ) { wp_reset_postdata(); return; } $parent_id = (int) $dashboard_query->posts[0]->ID; wp_reset_postdata(); $this->maybe_create_confirmation_page( $parent_id ); $child_templates = array( 'page-slide-preview.php', 'page-slide-confirmation.php' ); foreach ( $child_templates as $template ) { $pages_query = new WP_Query( array( 'post_type' => 'page', 'post_status' => array( 'publish', 'private' ), 'posts_per_page' => 20, 'no_found_rows' => true, 'meta_key' => '_wp_page_template', 'meta_value' => $template, ) ); if ( ! $pages_query->have_posts() ) { wp_reset_postdata(); continue; } foreach ( $pages_query->posts as $page ) { if ( (int) $page->ID === $parent_id ) { continue; } if ( (int) $page->post_parent !== $parent_id ) { wp_update_post( array( 'ID' => $page->ID, 'post_parent' => $parent_id, ) ); } } wp_reset_postdata(); } } /** * Create the ITC confirmation page when it is missing. * * @since 1.1.3 * @param int $parent_id Dashboard page ID. */ private function maybe_create_confirmation_page( $parent_id ) { $existing = new WP_Query( array( 'post_type' => 'page', 'post_status' => array( 'publish', 'private' ), 'posts_per_page' => 1, 'no_found_rows' => true, 'meta_key' => '_wp_page_template', 'meta_value' => 'page-slide-confirmation.php', ) ); if ( $existing->have_posts() ) { wp_reset_postdata(); return; } wp_reset_postdata(); wp_insert_post( array( 'post_title' => 'Slide Confirmation', 'post_name' => 'slide-confirmation', 'post_status' => 'publish', 'post_type' => 'page', 'post_parent' => absint( $parent_id ), 'meta_input' => array( '_wp_page_template' => 'page-slide-confirmation.php', ), ) ); } /** * 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' ) ); } 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 --- // 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(); 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 ); } }