email working - remove clients - preview polish

This commit is contained in:
stack_admn committed 2026-11-01 21:41:37 -08:00
1 parent 2b1424d127
commit da83beaec3
19 files changed
+1342 -1060

No files matched your search

+5 -29
View File
@@ -90,7 +90,6 @@ class HSSM_Admin {
register_setting( 'hssm_settings', 'hssm_plugin_mode' );
register_setting( 'hssm_settings', 'hssm_api_url' );
register_setting( 'hssm_settings', 'hssm_client_site_slug' );
register_setting( 'hssm_settings', 'hssm_notification_emails' );
register_setting( 'hssm_settings', 'hssm_cache_duration' );
register_setting( 'hssm_settings', 'hssm_api_keys' );
register_setting( 'hssm_settings', 'hssm_client_api_key' );
@@ -158,14 +157,6 @@ class HSSM_Admin {
'hssm_general'
);
add_settings_field(
'hssm_notification_emails',
__( 'Notification Recipients', 'hi-school-slide-manager' ),
array( $this, 'notification_emails_field' ),
'hssm-settings',
'hssm_notifications'
);
add_settings_field(
'hssm_api_keys',
__( 'API Keys', 'hi-school-slide-manager' ),
@@ -312,10 +303,6 @@ Headers: X-HSSM-API-Key: your-api-key</code></pre>
"name": "Hi-School Pharmacy"
}
],
"client": {
"slug": "client-name",
"name": "Client Name"
},
"status": "publish",
"created": "2025-01-01 12:00:00",
"modified": "2025-01-01 12:00:00"
@@ -415,12 +402,6 @@ Headers: X-HSSM-API-Key: your-api-key</code></pre>
if ( isset( $_POST['hssm_cache_duration'] ) ) {
update_option( 'hssm_cache_duration', absint( $_POST['hssm_cache_duration'] ) );
}
if ( isset( $_POST['hssm_notification_emails'] ) ) {
$emails = explode( "\n", str_replace( "\r", "", $_POST['hssm_notification_emails'] ) );
$emails = array_map( 'sanitize_email', $emails );
$emails = array_filter( $emails );
update_option( 'hssm_notification_emails', $emails );
}
add_settings_error( 'hssm_settings', 'settings_saved', __( 'Settings saved successfully.', 'hi-school-slide-manager' ), 'success' );
}
@@ -456,7 +437,11 @@ Headers: X-HSSM-API-Key: your-api-key</code></pre>
* Notifications section callback
*/
public function notifications_section_callback() {
echo '<p>' . __( 'Configure email notifications for new slides.', 'hi-school-slide-manager' ) . '</p>';
$dashboard_url = hssm_get_page_url_by_template( 'page-slide-dashboard.php', '/slide-dashboard/' );
echo '<p>' . esc_html__( 'Email recipients are managed on the Slide Dashboard Notifications tab.', 'hi-school-slide-manager' ) . '</p>';
if ( $dashboard_url ) {
echo '<p><a href="' . esc_url( $dashboard_url ) . '">' . esc_html__( 'Open Slide Dashboard', 'hi-school-slide-manager' ) . '</a></p>';
}
}
/**
@@ -526,15 +511,6 @@ Headers: X-HSSM-API-Key: your-api-key</code></pre>
echo '<span class="description">' . __( 'Recommended cache duration for API responses (seconds)', 'hi-school-slide-manager' ) . '</span>';
}
/**
* Notification emails field
*/
public function notification_emails_field() {
$emails = get_option( 'hssm_notification_emails', array() );
echo '<textarea name="hssm_notification_emails" rows="5" class="regular-text">' . esc_textarea( implode( "\n", $emails ) ) . '</textarea><br>';
echo '<span class="description">' . __( 'Enter email addresses (one per line) to receive notifications when new slides are published.', 'hi-school-slide-manager' ) . '</span>';
}
/**
* API keys field
*/
+25 -3
View File
@@ -44,10 +44,11 @@ class HSSM_API_Client {
$full_url = trailingslashit( $api_url ) . $endpoint;
$args = array(
'timeout' => 15,
'headers' => array(
'timeout' => 15,
'sslverify' => $this->should_verify_ssl( $full_url ),
'headers' => array(
'X-HSSM-API-Key' => $api_key,
'Accept' => 'application/json',
'Accept' => 'application/json',
),
);
@@ -82,6 +83,27 @@ class HSSM_API_Client {
*
* @since 1.1.0
*/
/**
* Verify SSL except on local Local/WP Engine hosts with self-signed certs.
*
* @since 1.1.3
* @param string $url Request URL.
* @return bool
*/
private function should_verify_ssl( $url ) {
$host = wp_parse_url( $url, PHP_URL_HOST );
$local_hosts = array( 'localhost', '127.0.0.1' );
$is_local = in_array( $host, $local_hosts, true ) || ( is_string( $host ) && preg_match( '/\.local$/', $host ) );
/**
* Filter whether the client API should verify SSL certificates.
*
* @param bool $verify Whether to verify SSL. False for *.local hosts.
* @param string $url Request URL.
*/
return (bool) apply_filters( 'hssm_api_sslverify', ! $is_local, $url );
}
public function clear_cache() {
$site_slug = get_option( 'hssm_client_site_slug' );
if ( ! empty( $site_slug ) ) {
+186 -250
View File
@@ -38,13 +38,12 @@ class HSSM_Frontend {
// Register page template
add_filter( 'theme_page_templates', array( $this, 'register_page_template' ) );
add_filter( 'page_template', array( $this, 'load_page_template' ) );
add_filter( 'request', array( $this, 'keep_preview_page_query' ) );
// Handle page routing
add_action( 'template_redirect', array( $this, 'handle_page_routing' ) );
// AJAX handlers
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' ) );
@@ -54,7 +53,6 @@ class HSSM_Frontend {
add_action( 'wp_ajax_hssm_duplicate_slide', array( $this, 'ajax_duplicate_slide' ) );
add_action( 'wp_ajax_hssm_toggle_slide_status', array( $this, 'ajax_toggle_slide_status' ) );
add_action( 'wp_ajax_hssm_approve_slide', array( $this, 'ajax_approve_slide' ) );
add_action( 'wp_ajax_hssm_preview_slides', array( $this, 'ajax_preview_slides' ) );
// Notification Settings AJAX
add_action( 'wp_ajax_hssm_get_notification_settings', array( $this, 'ajax_get_notification_settings' ) );
@@ -63,9 +61,12 @@ class HSSM_Frontend {
// Frontend assets
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_filter( 'body_class', array( $this, 'add_tool_page_body_class' ) );
// Filter slides query on preview page
add_action( 'pre_get_posts', array( $this, 'filter_preview_slides_query' ) );
add_action( 'admin_init', array( $this, 'maybe_nest_plugin_pages' ) );
}
/**
@@ -114,6 +115,30 @@ class HSSM_Frontend {
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
@@ -122,15 +147,39 @@ class HSSM_Frontend {
*/
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;
}
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;
}
/**
@@ -182,8 +231,8 @@ class HSSM_Frontend {
* @since 1.0.0
*/
private function redirect_to_login() {
$login_url = wp_login_url( get_permalink() );
wp_redirect( $login_url );
$redirect = home_url( add_query_arg( array() ) );
wp_safe_redirect( wp_login_url( $redirect ) );
exit;
}
@@ -207,12 +256,13 @@ class HSSM_Frontend {
if ( ! $is_dashboard && ! $is_preview ) {
return;
}
// Check access (preview page may be public, but dashboard requires auth)
if ( $is_dashboard && ! $this->check_dashboard_access() ) {
if ( ! $this->check_dashboard_access() ) {
return;
}
wp_enqueue_style( 'dashicons' );
// Enqueue styles (both pages need dashboard CSS)
wp_enqueue_style(
'hssm-dashboard',
@@ -239,6 +289,7 @@ class HSSM_Frontend {
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' ),
@@ -246,6 +297,9 @@ class HSSM_Frontend {
'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' ),
),
) );
@@ -286,188 +340,6 @@ class HSSM_Frontend {
}
}
/**
* 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
*
@@ -504,10 +376,9 @@ class HSSM_Frontend {
'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',
'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 ),
@@ -646,11 +517,6 @@ class HSSM_Frontend {
} else {
hssm_debug_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' );
}
}
/**
@@ -671,7 +537,6 @@ class HSSM_Frontend {
'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,
);
@@ -699,8 +564,10 @@ class HSSM_Frontend {
// 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'] ) : '';
$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;
@@ -734,21 +601,6 @@ class HSSM_Frontend {
}
}
// 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
@@ -876,9 +728,11 @@ class HSSM_Frontend {
$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;
}
}
@@ -900,8 +754,8 @@ class HSSM_Frontend {
'status' => $slide->post_status,
'status_display' => $status_display,
'sites' => $site_names,
'site_slugs' => $site_slugs,
'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] : '',
@@ -965,8 +819,7 @@ class HSSM_Frontend {
return;
}
// Check permissions
if ( ! current_user_can( 'edit_posts' ) ) {
if ( ! $this->check_dashboard_access() ) {
wp_send_json_error( array( 'message' => 'Permission denied' ) );
return;
}
@@ -994,9 +847,9 @@ class HSSM_Frontend {
if ( $result ) {
hssm_debug_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 );
$notifications->notify_itc_slide_approved( $slide_id );
wp_send_json_success( array(
'message' => 'Slide approved successfully',
@@ -1301,21 +1154,6 @@ class HSSM_Frontend {
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
@@ -1355,6 +1193,112 @@ class HSSM_Frontend {
)
);
}
/**
* 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
@@ -1439,14 +1383,6 @@ class HSSM_Frontend {
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',
+24 -12
View File
@@ -177,6 +177,10 @@ class HSSM_Main {
// Cron hooks for scheduled slides
add_action( 'hssm_check_scheduled_slides', array( $this, 'check_scheduled_slides' ) );
if ( 'manager' === $plugin_mode && $this->email_notifications ) {
add_action( 'transition_post_status', array( $this->email_notifications, 'on_slide_status_change' ), 10, 3 );
}
}
/**
@@ -232,7 +236,7 @@ class HSSM_Main {
*/
public function enqueue_frontend_assets() {
// Prevent slider assets from loading on admin-facing "frontend" pages
if ( is_page_template('page-slide-dashboard.php') || is_page_template('page-slide-preview.php') ) {
if ( is_page_template('page-slide-dashboard.php') || is_page_template('page-slide-preview.php') || is_page_template('page-slide-confirmation.php') ) {
// These pages will load their own specific styles (admin-slides-dashboard.css) or rely on explicit shortcode enqueueing.
return;
}
@@ -312,23 +316,28 @@ class HSSM_Main {
* @return array List of companion sites
*/
public static function get_companion_sites() {
return array(
$sites = array(
array(
'slug' => 'hi-school',
'name' => 'Hi-School Pharmacy',
'description' => 'Hi-School Pharmacy companion site'
'slug' => 'hi-school',
'name' => 'Hi-School Pharmacy',
'description' => 'Hi-School Pharmacy companion site',
'url' => '',
),
array(
'slug' => 'one-stop',
'name' => 'One Stop Hardware',
'description' => 'One Stop Hardware companion site'
'slug' => 'one-stop',
'name' => 'One Stop Hardware',
'description' => 'One Stop Hardware companion site',
'url' => '',
),
array(
'slug' => 'ace',
'name' => 'Ace Hardware',
'description' => 'Ace Hardware companion site'
'slug' => 'ace',
'name' => 'Ace Hardware',
'description' => 'Ace Hardware companion site',
'url' => '',
)
);
return apply_filters( 'hssm_companion_sites', $sites );
}
/**
@@ -447,7 +456,10 @@ class HSSM_Main {
'hssm_plugin_mode' => 'manager',
'hssm_api_url' => '',
'hssm_client_site_slug' => '',
'hssm_notification_emails' => array( get_option( 'admin_email' ) ),
'hssm_notification_settings' => array(
'editor_emails' => get_option( 'admin_email' ),
'itc_emails' => '',
),
'hssm_default_slide_duration' => 30, // days
'hssm_cache_duration' => 3600, // 1 hour
'hssm_api_version' => 'v1',
+359 -116
View File
@@ -35,17 +35,42 @@ class HSSM_Notifications {
public static function get_settings() {
$defaults = array(
'editor_emails' => '',
'itc_emails' => ''
'itc_emails' => '',
);
$settings = get_option( 'hssm_notification_settings', $defaults );
// Ensure we have an array and it contains our expected keys
$settings = get_option( 'hssm_notification_settings', false );
if ( ! is_array( $settings ) ) {
return $defaults;
$settings = $defaults;
} else {
$settings = wp_parse_args( $settings, $defaults );
}
return wp_parse_args( $settings, $defaults );
$has_current = '' !== trim( (string) $settings['editor_emails'] ) || '' !== trim( (string) $settings['itc_emails'] );
if ( $has_current ) {
return $settings;
}
$legacy = get_option( 'hssm_notification_emails', array() );
if ( empty( $legacy ) ) {
return $settings;
}
if ( is_array( $legacy ) ) {
$legacy_emails = array_filter( array_map( 'sanitize_email', $legacy ) );
$legacy_string = implode( ',', $legacy_emails );
} else {
$legacy_string = sanitize_text_field( (string) $legacy );
}
if ( '' === $legacy_string ) {
return $settings;
}
$settings['editor_emails'] = $legacy_string;
update_option( 'hssm_notification_settings', $settings );
delete_option( 'hssm_notification_emails' );
return $settings;
}
/**
@@ -89,6 +114,215 @@ class HSSM_Notifications {
return array_unique( $clean_emails );
}
/**
* Confirmation token for ITC live emails (does not depend on a logged-in session).
*
* @since 1.1.3
* @param int $slide_id Slide ID.
* @return string
*/
public static function get_confirm_token( $slide_id ) {
return hash( 'sha256', absint( $slide_id ) . '|' . wp_salt( 'auth' ) );
}
/**
* Whether a confirmation token is valid.
*
* @since 1.1.3
* @param int $slide_id Slide ID.
* @param string $token Token from the email link.
* @return bool
*/
public static function is_valid_confirm_token( $slide_id, $token ) {
return is_string( $token ) && hash_equals( self::get_confirm_token( $slide_id ), $token );
}
/**
* Preview page URL for a slide.
*
* @since 1.1.3
* @param int $slide_id Slide ID.
* @return string
*/
private function get_preview_link( $slide_id ) {
$start_date = get_post_meta( $slide_id, '_hssm_start_date', true );
if ( $start_date && preg_match( '/^(\d{4}-\d{2}-\d{2})/', $start_date, $matches ) ) {
$date = $matches[1];
} else {
$date = current_time( 'Y-m-d' );
}
$sites = wp_get_post_terms( $slide_id, 'hssm_site', array( 'fields' => 'slugs' ) );
$first_site = ( ! is_wp_error( $sites ) && ! empty( $sites ) ) ? $sites[0] : '';
return add_query_arg(
array(
'hssm_date' => $date,
'hssm_site' => $first_site,
'hssm_preview' => $slide_id,
),
hssm_get_page_url_by_template( 'page-slide-preview.php', '/slide-preview/' )
);
}
/**
* ITC confirmation page URL.
*
* @since 1.1.3
* @param int $slide_id Slide ID.
* @return string
*/
private function get_confirmation_link( $slide_id ) {
return add_query_arg(
array(
'slide_id' => $slide_id,
'hssm_token' => self::get_confirm_token( $slide_id ),
),
hssm_get_page_url_by_template( 'page-slide-confirmation.php', '/slide-confirmation/' )
);
}
/**
* Front dashboard URL.
*
* @since 1.1.3
* @return string
*/
private function get_dashboard_link( $slide_id = 0 ) {
$url = hssm_get_page_url_by_template( 'page-slide-dashboard.php', '/slide-dashboard/' );
if ( $slide_id ) {
$url = add_query_arg( 'hssm_focus', absint( $slide_id ), $url );
}
return $url;
}
/**
* Whether the slide is currently visible on companion sites.
*
* @since 1.1.3
* @param int $slide_id Slide ID.
* @return bool
*/
private function is_slide_currently_live( $slide_id ) {
$post = get_post( $slide_id );
if ( ! $post || 'publish' !== $post->post_status ) {
return false;
}
$now = current_time( 'timestamp' );
$start = get_post_meta( $slide_id, '_hssm_start_date', true );
if ( $start ) {
$start_ts = strtotime( $start );
if ( false !== $start_ts && $start_ts > $now ) {
return false;
}
}
$end = get_post_meta( $slide_id, '_hssm_end_date', true );
if ( $end ) {
$end_ts = strtotime( $end );
if ( false !== $end_ts && $end_ts < $now ) {
return false;
}
}
return true;
}
/**
* Human-readable site list, with URLs when configured.
*
* @since 1.1.3
* @param int $slide_id Slide ID.
* @return string
*/
private function format_site_lines( $slide_id ) {
$terms = wp_get_post_terms( $slide_id, 'hssm_site' );
if ( is_wp_error( $terms ) || empty( $terms ) ) {
return 'None';
}
$catalog = array();
if ( class_exists( 'HSSM_Main' ) ) {
foreach ( HSSM_Main::get_companion_sites() as $site ) {
$catalog[ $site['slug'] ] = $site;
}
}
$lines = array();
foreach ( $terms as $term ) {
$line = $term->name . ' (' . $term->slug . ')';
if ( ! empty( $catalog[ $term->slug ]['url'] ) ) {
$line .= ' - ' . $catalog[ $term->slug ]['url'];
}
$lines[] = $line;
}
return implode( "\n", $lines );
}
/**
* Mark a one-time notification as sent.
*
* @since 1.1.3
* @param int $slide_id Slide ID.
* @param string $key Meta key.
* @return bool True if this is the first send.
*/
private function mark_sent_once( $slide_id, $key ) {
if ( get_post_meta( $slide_id, $key, true ) ) {
return false;
}
update_post_meta( $slide_id, $key, current_time( 'mysql' ) );
return true;
}
/**
* Send ITC the go-live email when a slide first reaches publish.
*
* @since 1.1.3
* @param string $new_status New status.
* @param string $old_status Previous status.
* @param WP_Post $post Post object.
*/
public function on_slide_status_change( $new_status, $old_status, $post ) {
if ( ! $post instanceof WP_Post || 'hssm_slide' !== $post->post_type ) {
return;
}
if ( 'publish' !== $new_status || 'publish' === $old_status ) {
return;
}
$this->notify_itc_slide_live( $post->ID );
}
/**
* Notify ITC of already-published slides whose start date just arrived.
*
* @since 1.1.3
*/
public function notify_due_live_slides() {
$slides = get_posts(
array(
'post_type' => 'hssm_slide',
'post_status' => 'publish',
'posts_per_page' => 50,
'meta_query' => array(
array(
'key' => '_hssm_itc_live_sent',
'compare' => 'NOT EXISTS',
),
),
)
);
foreach ( $slides as $slide ) {
$this->notify_itc_slide_live( $slide->ID );
}
}
/**
* Send notification to Editor when slide is Scheduled or Published Immediately
*
@@ -105,47 +339,23 @@ class HSSM_Notifications {
}
$slide = get_post( $slide_id );
if ( ! $slide ) return;
$target_sites = wp_get_post_terms( $slide_id, 'hssm_site', array( 'fields' => 'slugs' ) );
$site_slugs = ! empty( $target_sites ) ? implode( ',', $target_sites ) : '';
// Determine publish date
$publish_date = ( $status === 'scheduled' )
? get_post_meta( $slide_id, '_hssm_start_date', true )
: current_time( 'Y-m-d' );
// Construct preview link
// page-slide-preview.php logic usually takes 'date' and 'site' params
// We'll link to the preview page. Assuming there is a page with the template, or we use a custom URL structure if defined.
// The instructions mentioned: "generated link to the slide-preview page with params containing the slide-title, the publish-date... and the site slugs"
$preview_page_url = home_url( '/slide-preview/' ); // Basic assumption, adjust if needed based on setup
// Better: find the page using the template
$pages = get_pages(array(
'meta_key' => '_wp_page_template',
'meta_value' => 'page-slide-preview.php'
));
if ( ! empty( $pages ) ) {
$preview_page_url = get_permalink( $pages[0]->ID );
if ( ! $slide ) {
return;
}
$preview_link = add_query_arg( array(
'preview_title' => urlencode( $slide->post_title ),
'date' => $publish_date,
'sites' => $site_slugs,
'preview_mode' => 'editor_check'
), $preview_page_url );
$start_date = get_post_meta( $slide_id, '_hssm_start_date', true );
$publish_date = $start_date ? $start_date : current_time( 'Y-m-d' );
$status_label = ( 'scheduled' === $status ) ? 'Scheduled' : 'Published Immediately';
$subject = sprintf( 'New Slide %s: "%s"', ucfirst( $status ), $slide->post_title );
$message = "A new slide has been " . ( $status === 'scheduled' ? 'Scheduled' : 'Published Immediately' ) . ".\n\n";
$message .= "Title: " . $slide->post_title . "\n";
$message .= "Target Date: " . $publish_date . "\n";
$message .= "Target Sites: " . implode( ', ', $target_sites ) . "\n\n";
$message .= "Preview Link: " . $preview_link . "\n\n";
$message .= "Please review the slide.";
$subject = sprintf( 'New Slide %s: "%s"', $status_label, $slide->post_title );
$message = "A new slide has been {$status_label}.\n\n";
$message .= 'Title: ' . $slide->post_title . "\n";
$message .= 'Target Date: ' . $publish_date . "\n";
$message .= "Target Sites:\n" . $this->format_site_lines( $slide_id ) . "\n\n";
$message .= 'Preview: ' . $this->get_preview_link( $slide_id ) . "\n";
$message .= 'Dashboard: ' . $this->get_dashboard_link( $slide_id ) . "\n\n";
$message .= 'Please review the slide.';
foreach ( $emails as $email ) {
wp_mail( $email, $subject, $message );
@@ -159,80 +369,102 @@ class HSSM_Notifications {
*/
public function notify_creator_slide_approved( $slide_id ) {
$slide = get_post( $slide_id );
if ( ! $slide ) return;
if ( ! $slide ) {
return;
}
$author_email = get_the_author_meta( 'user_email', $slide->post_author );
if ( ! is_email( $author_email ) ) return;
if ( ! is_email( $author_email ) ) {
return;
}
$subject = 'Your Slide Has Been Approved: "' . $slide->post_title . '"';
$message = "Good news! Your slide has been approved.\n\n";
$message .= "Title: " . $slide->post_title . "\n";
$message .= "It is now scheduled/published according to your settings.";
$message = "Good news! Your slide has been approved.\n\n";
$message .= 'Title: ' . $slide->post_title . "\n";
$message .= "Target Sites:\n" . $this->format_site_lines( $slide_id ) . "\n\n";
$message .= 'Preview: ' . $this->get_preview_link( $slide_id ) . "\n";
$message .= 'Dashboard: ' . $this->get_dashboard_link( $slide_id ) . "\n\n";
$message .= 'It is now scheduled or published according to your settings.';
wp_mail( $author_email, $subject, $message );
}
/**
* Notify ITC to review date range after approval.
*
* @since 1.1.3
* @param int $slide_id Slide ID.
*/
public function notify_itc_slide_approved( $slide_id ) {
if ( ! $this->mark_sent_once( $slide_id, '_hssm_itc_approved_sent' ) ) {
return;
}
$settings = self::get_settings();
$emails = $this->get_emails_array( $settings['itc_emails'] );
if ( empty( $emails ) ) {
hssm_debug_log( 'No ITC emails configured for approval notification.' );
return;
}
$slide = get_post( $slide_id );
if ( ! $slide ) {
return;
}
$start_date = get_post_meta( $slide_id, '_hssm_start_date', true );
$end_date = get_post_meta( $slide_id, '_hssm_end_date', true );
$subject = 'Slide Approved for Review: "' . $slide->post_title . '"';
$message = "A slide has been approved. Please review the date range and preview before it goes live.\n\n";
$message .= 'Title: ' . $slide->post_title . "\n";
$message .= 'Start Date: ' . ( $start_date ? $start_date : 'Immediate' ) . "\n";
$message .= 'End Date: ' . ( $end_date ? $end_date : 'None' ) . "\n";
$message .= "Target Sites:\n" . $this->format_site_lines( $slide_id ) . "\n\n";
$message .= 'Preview: ' . $this->get_preview_link( $slide_id ) . "\n";
$message .= 'Dashboard: ' . $this->get_dashboard_link( $slide_id );
foreach ( $emails as $email ) {
wp_mail( $email, $subject, $message );
}
}
/**
* Send notification to ITCGuys when slide goes LIVE
*
* @param int $slide_id Slide ID
*/
public function notify_itc_slide_live( $slide_id ) {
if ( ! $this->is_slide_currently_live( $slide_id ) ) {
return;
}
if ( ! $this->mark_sent_once( $slide_id, '_hssm_itc_live_sent' ) ) {
return;
}
$settings = self::get_settings();
$emails = $this->get_emails_array( $settings['itc_emails'] );
if ( empty( $emails ) ) return;
$emails = $this->get_emails_array( $settings['itc_emails'] );
if ( empty( $emails ) ) {
hssm_debug_log( 'No ITC emails configured for live notification.' );
return;
}
$slide = get_post( $slide_id );
if ( ! $slide ) return;
if ( ! $slide ) {
return;
}
// Generate Confirmation Link
// This link needs to trigger the "Slide is Live" confirmation page
// We probably need a specialized page or endpoint for this.
// Let's assume we use the Dashboard page with a specific action param or a separate page.
// User requested: "We need a page 'Slide is Live' ... with a green confirmation button"
// Let's use the preview page or dashboard page with a specific query arg to show this UI.
// Or better, a dedicated endpoint. For now, let's point to the dashboard with a "confirm_live" param.
$confirm_url = home_url( '/slide-live-confirmation/' ); // Ideally we create this page or rewrite rule
// For now, let's use the dashboard url with arguments, and we'll handle the UI there or create a rewrite.
// Actually, creating a specific page 'Slide is Live' was requested.
// I'll create a new template `page-slide-confirmation.php` later.
// Find confirmation page if exists, or construct URL
$pages = get_pages(array(
'meta_key' => '_wp_page_template',
'meta_value' => 'page-slide-confirmation.php'
));
$confirmation_page_url = !empty($pages) ? get_permalink($pages[0]->ID) : home_url('/?hssm_action=confirm_live');
$confirm_link = add_query_arg( array(
'slide_id' => $slide_id,
'token' => wp_create_nonce( 'hssm_confirm_live_' . $slide_id ) // Note: Nonces depend on user session, might be tricky for email links if user isn't logged in.
// Better to use a hash that doesn't expire or relies on a shared secret if ITC guys are not logged in.
// Assuming ITC guys are logged in users for now or we just use a hash.
), $confirmation_page_url );
// Since email links are clicked outside sessions often, maybe just a hash of slide ID + secret salt.
$token = hash( 'sha256', $slide_id . 'hssm_secret_salt' );
$confirm_link = add_query_arg( array(
'slide_id' => $slide_id,
'hssm_token' => $token
), $confirmation_page_url );
$target_sites = wp_get_post_terms( $slide_id, 'hssm_site', array( 'fields' => 'slugs' ) );
$subject = 'ACTION REQUIRED: Slide is LIVE - "' . $slide->post_title . '"';
$message = "A slide has just gone LIVE.\n\n";
$message .= "Title: " . $slide->post_title . "\n";
$message .= "Sites: " . implode( ', ', $target_sites ) . "\n\n";
$message = "A slide has just gone LIVE.\n\n";
$message .= 'Title: ' . $slide->post_title . "\n";
$message .= "Sites:\n" . $this->format_site_lines( $slide_id ) . "\n\n";
$message .= "Please check the sites to ensure it is displaying correctly.\n";
$message .= "Once verified, click the link below to confirm to the Editor and Author:\n\n";
$message .= $confirm_link;
$message .= "Once verified, click the confirmation link to notify the Editor and Author:\n\n";
$message .= $this->get_confirmation_link( $slide_id );
foreach ( $emails as $email ) {
wp_mail( $email, $subject, $message );
@@ -243,32 +475,43 @@ class HSSM_Notifications {
* Send FINAL notification to Editor and Author (triggered by ITC confirmation)
*
* @param int $slide_id Slide ID
* @return bool True if the confirmation email was sent.
*/
public function notify_all_confirmed_live( $slide_id ) {
$slide = get_post( $slide_id );
if ( ! $slide ) return;
if ( ! $slide ) {
return false;
}
// Editor Emails
$settings = self::get_settings();
if ( ! $this->mark_sent_once( $slide_id, '_hssm_live_confirmed' ) ) {
return false;
}
$settings = self::get_settings();
$editor_emails = $this->get_emails_array( $settings['editor_emails'] );
// Author Email
$author_email = get_the_author_meta( 'user_email', $slide->post_author );
$recipients = array_merge( $editor_emails, array( $author_email ) );
$recipients = array_unique( array_filter( $recipients ) );
$author_email = get_the_author_meta( 'user_email', $slide->post_author );
$recipients = array_unique( array_filter( array_merge( $editor_emails, array( $author_email ) ) ) );
if ( empty( $recipients ) ) return;
if ( empty( $recipients ) ) {
hssm_debug_log( 'No recipients for live confirmation email.' );
return true;
}
$subject = 'CONFIRMED: Slide is Live and Verified - "' . $slide->post_title . '"';
$message = "The slide has been verified as LIVE by the ITC team.\n\n";
$message .= "Title: " . $slide->post_title . "\n";
$message .= "Status: Live & Verified\n\n";
$message .= "You can view the slide on the live sites.";
$message = "The slide has been verified as LIVE by the ITC team.\n\n";
$message .= 'Title: ' . $slide->post_title . "\n";
$message .= "Status: Live & Verified\n";
$message .= "Sites:\n" . $this->format_site_lines( $slide_id ) . "\n\n";
$message .= 'Dashboard: ' . $this->get_dashboard_link( $slide_id ) . "\n";
$message .= 'Preview: ' . $this->get_preview_link( $slide_id );
foreach ( $recipients as $email ) {
wp_mail( $email, $subject, $message );
if ( is_email( $email ) ) {
wp_mail( $email, $subject, $message );
}
}
return true;
}
}
-22
View File
@@ -360,7 +360,6 @@ class HSSM_REST_API {
'auto_unpublish' => get_post_meta( $slide->ID, '_hssm_auto_unpublish', true ) === '1',
),
'target_sites' => $this->get_slide_target_sites( $slide->ID ),
'client' => $this->get_slide_client( $slide->ID ),
'status' => $slide->post_status,
'created' => $slide->post_date,
'modified' => $slide->post_modified,
@@ -427,27 +426,6 @@ class HSSM_REST_API {
return $sites_data;
}
/**
* Get slide client
*
* @since 1.0.0
* @param int $slide_id Slide ID
* @return array|null Client data
*/
private function get_slide_client( $slide_id ) {
$clients = wp_get_post_terms( $slide_id, 'hssm_client' );
if ( ! is_wp_error( $clients ) && ! empty( $clients ) ) {
$client = $clients[0];
return array(
'slug' => $client->slug,
'name' => $client->name,
);
}
return null;
}
/**
* Get slide count for a site
*
+106 -16
View File
@@ -22,6 +22,7 @@ class HSSM_Shortcode {
*/
public function __construct() {
add_shortcode( 'hssm_slider', array( $this, 'render_shortcode' ) );
add_shortcode( 'hssm_slides', array( $this, 'render_shortcode' ) );
}
/**
@@ -49,14 +50,37 @@ class HSSM_Shortcode {
$slides = $result;
} else {
// Manager mode - Fetch locally
$site_slug = isset( $atts['site'] ) ? sanitize_text_field( $atts['site'] ) : get_option( 'hssm_client_site_slug', 'hi-school' );
$date = isset( $atts['date'] ) ? sanitize_text_field( $atts['date'] ) : current_time( 'Y-m-d' );
if ( isset( $atts['count'] ) && ! isset( $atts['limit'] ) ) {
$atts['limit'] = $atts['count'];
}
$atts = shortcode_atts(
array(
'site' => get_option( 'hssm_client_site_slug', 'hi-school' ),
'date' => current_time( 'Y-m-d' ),
'limit' => 10,
'slide' => 0,
'include_pending' => '0',
),
$atts,
'hssm_slider'
);
$site_slug = sanitize_text_field( $atts['site'] );
$date = sanitize_text_field( $atts['date'] );
$focus_slide_id = absint( $atts['slide'] );
$include_pending = filter_var( $atts['include_pending'], FILTER_VALIDATE_BOOLEAN );
$post_status = array( 'publish', 'future' );
if ( $include_pending ) {
$post_status[] = 'pending';
}
// Build query args similar to what's in the REST API or Frontend class
$args = array(
'post_type' => 'hssm_slide',
'posts_per_page' => isset( $atts['limit'] ) ? intval( $atts['limit'] ) : 10,
'post_status' => array( 'publish', 'future', 'pending' ),
'posts_per_page' => intval( $atts['limit'] ),
'post_status' => $post_status,
'meta_key' => '_hssm_order',
'orderby' => 'meta_value_num',
'order' => 'ASC',
@@ -94,21 +118,14 @@ class HSSM_Shortcode {
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
$slide_id = get_the_ID();
// Format data to match what the API returns
$slides[] = array(
'id' => $slide_id,
'title' => get_the_title(),
'subtitle' => get_post_meta( $slide_id, '_hssm_subtitle', true ),
'content' => get_the_content(),
'buttons' => get_post_meta( $slide_id, '_hssm_buttons', true ),
'featured_image' => get_the_post_thumbnail_url( $slide_id, 'full' ),
'hide_all_text' => get_post_meta( $slide_id, '_hssm_hide_all_text', true ) === '1',
);
$slides[] = $this->format_slide_for_slider( get_the_ID() );
}
wp_reset_postdata();
}
if ( $focus_slide_id ) {
$slides = $this->prepend_focus_slide( $slides, $focus_slide_id, $include_pending );
}
}
if ( empty( $slides ) ) {
@@ -118,4 +135,77 @@ class HSSM_Shortcode {
$slider = new HSSM_Slider();
return $slider->render( $slides, $atts );
}
/**
* Format a slide for the public slider.
*
* @since 1.1.3
* @param int $slide_id Slide ID.
* @return array|null
*/
private function format_slide_for_slider( $slide_id ) {
$slide = get_post( $slide_id );
if ( ! $slide || 'hssm_slide' !== $slide->post_type ) {
return null;
}
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, 'full' ),
'hide_all_text' => get_post_meta( $slide->ID, '_hssm_hide_all_text', true ) === '1',
);
}
/**
* Put a specific slide first in the slider.
*
* Pending and draft slides are only prepended when include_pending is true
* (preview). The public slider stays publish + future.
*
* @since 1.1.3
* @param array $slides Existing slides.
* @param int $slide_id Focus slide ID.
* @param bool $include_pending Whether pending/draft focus slides are allowed.
* @return array
*/
private function prepend_focus_slide( $slides, $slide_id, $include_pending = false ) {
foreach ( $slides as $index => $slide ) {
if ( (int) $slide['id'] === (int) $slide_id ) {
if ( 0 !== $index ) {
$focus = $slides[ $index ];
unset( $slides[ $index ] );
array_unshift( $slides, $focus );
}
return array_values( $slides );
}
}
$post = get_post( $slide_id );
if ( ! $post || 'hssm_slide' !== $post->post_type ) {
return $slides;
}
$allowed_statuses = array( 'publish', 'future' );
if ( $include_pending ) {
$allowed_statuses[] = 'pending';
if ( current_user_can( 'edit_post', $slide_id ) ) {
$allowed_statuses[] = 'draft';
}
}
if ( ! in_array( $post->post_status, $allowed_statuses, true ) ) {
return $slides;
}
$formatted = $this->format_slide_for_slider( $slide_id );
if ( $formatted ) {
array_unshift( $slides, $formatted );
}
return $slides;
}
}
+25 -16
View File
@@ -51,9 +51,25 @@ class HSSM_Slider {
}
$hide_text = isset( $slide['hide_all_text'] ) && $slide['hide_all_text'];
$valid_buttons = array();
if ( ! empty( $slide['buttons'] ) && is_array( $slide['buttons'] ) ) {
$valid_buttons = array_filter(
$slide['buttons'],
function( $button ) {
return ! empty( $button['text'] ) && ! empty( $button['url'] );
}
);
}
$has_text = ! $hide_text && (
! empty( $slide['title'] ) ||
! empty( $slide['subtitle'] ) ||
! empty( $slide['content'] ) ||
! empty( $valid_buttons )
);
?>
<div class="hssm-slide" data-index="<?php echo esc_attr( $index ); ?>">
<div class="hssm-slide-inner <?php echo $hide_text ? 'hssm-hide-text' : ''; ?>" style="background-image: url('<?php echo esc_url( $bg_image ); ?>');">
<?php if ( $has_text ) : ?>
<div class="hssm-slide-content-wrapper">
<div class="hssm-slide-content">
<?php if ( ! empty( $slide['title'] ) ) : ?>
@@ -70,25 +86,18 @@ class HSSM_Slider {
</div>
<?php endif; ?>
<?php if ( ! empty( $slide['buttons'] ) && is_array( $slide['buttons'] ) ) : ?>
<?php
// Filter out empty buttons
$valid_buttons = array_filter($slide['buttons'], function($b) {
return !empty($b['text']) && !empty($b['url']);
});
?>
<?php if ( ! empty( $valid_buttons ) ) : ?>
<div class="hssm-button-container">
<?php foreach ( $valid_buttons as $button ) : ?>
<a href="<?php echo esc_url( $button['url'] ); ?>" class="hssm-button">
<?php echo esc_html( $button['text'] ); ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if ( ! empty( $valid_buttons ) ) : ?>
<div class="hssm-button-container">
<?php foreach ( $valid_buttons as $button ) : ?>
<a href="<?php echo esc_url( $button['url'] ); ?>" class="hssm-button">
<?php echo esc_html( $button['text'] ); ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
+26 -123
View File
@@ -36,14 +36,6 @@ class HSSM_Slides_CPT {
*/
const SITE_TAXONOMY = 'hssm_site';
/**
* Client taxonomy slug
*
* @since 1.0.0
* @var string
*/
const CLIENT_TAXONOMY = 'hssm_client';
/**
* Constructor
*
@@ -99,7 +91,7 @@ class HSSM_Slides_CPT {
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'query_var' => 'hssm_slide_item',
'rewrite' => array( 'slug' => 'slide' ),
'capability_type' => 'post',
'has_archive' => true,
@@ -147,33 +139,6 @@ class HSSM_Slides_CPT {
register_taxonomy( self::SITE_TAXONOMY, array( self::POST_TYPE ), $site_args );
// Client taxonomy for organizing slides by client (optional)
$client_labels = array(
'name' => _x( 'Clients', 'taxonomy general name', 'hi-school-slide-manager' ),
'singular_name' => _x( 'Client', 'taxonomy singular name', 'hi-school-slide-manager' ),
'search_items' => __( 'Search Clients', 'hi-school-slide-manager' ),
'all_items' => __( 'All Clients', 'hi-school-slide-manager' ),
'parent_item' => __( 'Parent Client', 'hi-school-slide-manager' ),
'parent_item_colon' => __( 'Parent Client:', 'hi-school-slide-manager' ),
'edit_item' => __( 'Edit Client', 'hi-school-slide-manager' ),
'update_item' => __( 'Update Client', 'hi-school-slide-manager' ),
'add_new_item' => __( 'Add New Client', 'hi-school-slide-manager' ),
'new_item_name' => __( 'New Client Name', 'hi-school-slide-manager' ),
'menu_name' => __( 'Clients', 'hi-school-slide-manager' ),
);
$client_args = array(
'hierarchical' => false,
'labels' => $client_labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'slide-client' ),
'show_in_rest' => true,
);
register_taxonomy( self::CLIENT_TAXONOMY, array( self::POST_TYPE ), $client_args );
// Add default terms for the three companion sites
$this->create_default_site_terms();
}
@@ -535,7 +500,6 @@ class HSSM_Slides_CPT {
public function slide_targeting_meta_box( $post ) {
// Get current terms
$current_sites = wp_get_post_terms( $post->ID, self::SITE_TAXONOMY, array( 'fields' => 'ids' ) );
$current_clients = wp_get_post_terms( $post->ID, self::CLIENT_TAXONOMY, array( 'fields' => 'ids' ) );
// Get all available site terms
$site_terms = get_terms( array(
@@ -543,12 +507,6 @@ class HSSM_Slides_CPT {
'hide_empty' => false,
) );
// Get all available client terms
$client_terms = get_terms( array(
'taxonomy' => self::CLIENT_TAXONOMY,
'hide_empty' => false,
) );
?>
<div class="hssm-targeting-section">
<p><strong><?php _e( 'Target Sites', 'hi-school-slide-manager' ); ?></strong></p>
@@ -569,27 +527,6 @@ class HSSM_Slides_CPT {
<?php endif; ?>
</div>
<div class="hssm-targeting-section" style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;">
<p><strong><?php _e( 'Client (Optional)', 'hi-school-slide-manager' ); ?></strong></p>
<?php if ( ! empty( $client_terms ) && ! is_wp_error( $client_terms ) ) : ?>
<select name="hssm_client" style="width: 100%;">
<option value=""><?php _e( 'Select a client (optional)', 'hi-school-slide-manager' ); ?></option>
<?php foreach ( $client_terms as $term ) : ?>
<option value="<?php echo esc_attr( $term->term_id ); ?>" <?php selected( in_array( $term->term_id, $current_clients ) ); ?>>
<?php echo esc_html( $term->name ); ?>
</option>
<?php endforeach; ?>
</select>
<?php else : ?>
<p class="description">
<?php _e( 'No clients available.', 'hi-school-slide-manager' ); ?>
<a href="<?php echo admin_url( 'edit-tags.php?taxonomy=' . self::CLIENT_TAXONOMY . '&post_type=' . self::POST_TYPE ); ?>">
<?php _e( 'Add clients here', 'hi-school-slide-manager' ); ?>
</a>
</p>
<?php endif; ?>
</div>
<style>
.hssm-targeting-section label {
display: flex;
@@ -682,14 +619,6 @@ class HSSM_Slides_CPT {
wp_set_post_terms( $post_id, array(), self::SITE_TAXONOMY );
}
// Save client taxonomy
if ( isset( $_POST['hssm_client'] ) && ! empty( $_POST['hssm_client'] ) ) {
$client_id = intval( $_POST['hssm_client'] );
wp_set_post_terms( $post_id, array( $client_id ), self::CLIENT_TAXONOMY );
} else {
wp_set_post_terms( $post_id, array(), self::CLIENT_TAXONOMY );
}
// Send email notification for new slides
if ( get_post_status( $post_id ) === 'publish' && ! get_post_meta( $post_id, '_hssm_notification_sent', true ) ) {
$this->send_new_slide_notification( $post_id );
@@ -709,7 +638,6 @@ class HSSM_Slides_CPT {
$new_columns['cb'] = $columns['cb'];
$new_columns['title'] = $columns['title'];
$new_columns['target_sites'] = __( 'Target Sites', 'hi-school-slide-manager' );
$new_columns['client'] = __( 'Client', 'hi-school-slide-manager' );
$new_columns['schedule'] = __( 'Schedule', 'hi-school-slide-manager' );
$new_columns['order'] = __( 'Order', 'hi-school-slide-manager' );
$new_columns['date'] = $columns['date'];
@@ -741,15 +669,6 @@ class HSSM_Slides_CPT {
}
break;
case 'client':
$client_terms = wp_get_post_terms( $post_id, self::CLIENT_TAXONOMY );
if ( ! empty( $client_terms ) && ! is_wp_error( $client_terms ) ) {
echo esc_html( $client_terms[0]->name );
} else {
echo '<em>' . __( 'No client', 'hi-school-slide-manager' ) . '</em>';
}
break;
case 'schedule':
$start_date = get_post_meta( $post_id, '_hssm_start_date', true );
$end_date = get_post_meta( $post_id, '_hssm_end_date', true );
@@ -782,30 +701,9 @@ class HSSM_Slides_CPT {
* @param int $post_id Post ID
*/
private function send_new_slide_notification( $post_id ) {
$notification_emails = get_option( 'hssm_notification_emails', array() );
if ( empty( $notification_emails ) ) {
return;
}
$post = get_post( $post_id );
$subject = sprintf( __( 'New Slide Created: %s', 'hi-school-slide-manager' ), $post->post_title );
$message = sprintf(
__( 'A new slide has been created and published on %s.' . "\n\n" .
'Title: %s' . "\n" .
'Content: %s' . "\n" .
'Edit URL: %s', 'hi-school-slide-manager' ),
get_bloginfo( 'name' ),
$post->post_title,
wp_strip_all_tags( $post->post_content ),
admin_url( 'post.php?post=' . $post_id . '&action=edit' )
);
$headers = array( 'Content-Type: text/plain; charset=UTF-8' );
foreach ( $notification_emails as $email ) {
wp_mail( $email, $subject, $message, $headers );
if ( class_exists( 'HSSM_Notifications' ) ) {
$notifications = new HSSM_Notifications();
$notifications->notify_editor_slide_created( $post_id, 'published' );
}
}
@@ -816,31 +714,31 @@ class HSSM_Slides_CPT {
*/
public function check_scheduled_slides() {
$current_time = current_time( 'mysql' );
// Auto-publish slides that should start now
$slides_to_publish = get_posts( array(
'post_type' => self::POST_TYPE,
// WordPress publishes `future` posts. Cron only handles auto-publish drafts.
$auto_publish = get_posts( array(
'post_type' => self::POST_TYPE,
'post_status' => 'draft',
'meta_query' => array(
'meta_query' => array(
'relation' => 'AND',
array(
'key' => '_hssm_auto_publish',
'value' => '1',
'compare' => '='
'key' => '_hssm_auto_publish',
'value' => '1',
'compare' => '=',
),
array(
'key' => '_hssm_start_date',
'value' => $current_time,
'compare' => '<='
)
'key' => '_hssm_start_date',
'value' => $current_time,
'compare' => '<=',
),
),
'numberposts' => -1
'numberposts' => -1,
) );
foreach ( $slides_to_publish as $slide ) {
foreach ( $auto_publish as $slide ) {
wp_update_post( array(
'ID' => $slide->ID,
'post_status' => 'publish'
'ID' => $slide->ID,
'post_status' => 'publish',
) );
}
@@ -870,6 +768,11 @@ class HSSM_Slides_CPT {
'post_status' => 'draft'
) );
}
if ( class_exists( 'HSSM_Notifications' ) ) {
$notifications = new HSSM_Notifications();
$notifications->notify_due_live_slides();
}
}
/**