'', 'itc_emails' => '', ); $settings = get_option( 'hssm_notification_settings', false ); if ( ! is_array( $settings ) ) { $settings = $defaults; } else { $settings = 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; } /** * Save notification settings * * @param array $settings Array of settings * @return bool True if saved successfully */ public static function save_settings( $settings ) { $clean_settings = array( 'editor_emails' => sanitize_textarea_field( $settings['editor_emails'] ), 'itc_emails' => sanitize_textarea_field( $settings['itc_emails'] ) ); return update_option( 'hssm_notification_settings', $clean_settings ); } /** * Helper to get emails as array from string * * @param string $emails_string Comma or newline separated emails * @return array Array of valid email addresses */ private function get_emails_array( $emails_string ) { if ( empty( $emails_string ) ) { return array(); } // Replace newlines with commas $emails_string = str_replace( array( "\r", "\n" ), ',', $emails_string ); $emails = explode( ',', $emails_string ); $clean_emails = array(); foreach ( $emails as $email ) { $email = sanitize_email( trim( $email ) ); if ( is_email( $email ) ) { $clean_emails[] = $email; } } 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 * * @param int $slide_id Slide ID * @param string $status 'scheduled' or 'published' */ public function notify_editor_slide_created( $slide_id, $status ) { $settings = self::get_settings(); $emails = $this->get_emails_array( $settings['editor_emails'] ); if ( empty( $emails ) ) { hssm_debug_log( 'No Editor emails configured for notification.' ); return; } $slide = get_post( $slide_id ); if ( ! $slide ) { return; } $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"', $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 ); } } /** * Send notification to Creator (Author) when slide is Approved * * @param int $slide_id Slide ID */ public function notify_creator_slide_approved( $slide_id ) { $slide = get_post( $slide_id ); if ( ! $slide ) { return; } $author_email = get_the_author_meta( 'user_email', $slide->post_author ); 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 .= "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 ) ) { hssm_debug_log( 'No ITC emails configured for live notification.' ); return; } $slide = get_post( $slide_id ); if ( ! $slide ) { return; } $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:\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 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 ); } } /** * 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 false; } 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 = get_the_author_meta( 'user_email', $slide->post_author ); $recipients = array_unique( array_filter( array_merge( $editor_emails, array( $author_email ) ) ) ); 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"; $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 ) { if ( is_email( $email ) ) { wp_mail( $email, $subject, $message ); } } return true; } }