Files
Hi-School-Slide-Manager/includes/class-hssm-notifications.php

518 lines
16 KiB
PHP

<?php
/**
* HSSM Notifications Class
*
* Handles all email notifications for the slide manager
*
* @package Hi_School_Slide_Manager
* @since 1.0.0
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit( 'Direct access forbidden.' );
}
/**
* HSSM Notifications Class
*/
class HSSM_Notifications {
/**
* Constructor
*
* @since 1.0.0
*/
public function __construct() {
// We'll hook into actions that trigger notifications
}
/**
* Get notification settings
*
* @return array Array of email addresses for different roles
*/
public static function get_settings() {
$defaults = array(
'editor_emails' => '',
'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;
}
}