array( 'lessons' => [...], 'topics' => [...], 'quizzes' => [...] ) ] * * @var array * * @since 5.6.0 */ private static $before_course_steps = array(); /** * Course metadata store before updating metadata. * ! used specifically to track changes in the _sfwd-courses metadata. * * Structure: [ (string) $post_id => array( 'certificate' => $cert_id, 'price_type' => $price_type ) ] * * @var array * * @since 5.6.0 */ private static $before_course_meta = array(); /** * Group metadata store before updating metadata. * Used specifically to track changes in the _groups metadata. * * Structure: [ (string) $post_id => array( 'groups_group_start_date' => $timestamp, 'groups_group_end_date' => $timestamp ) ] * * @var array * * @since 5.6.0 */ private static $before_group_meta = array(); /** * Miscellaneous single-value metadata store for tracking before-state of metadata updates. * Structure: [ (string) $post_id ][ $meta_key ] = $meta_value * * @var array * * @since 5.6.0 */ private static $ld_misc_metadata = array(); /** * Course users before update. * Structure: [ (int) $course_id => array( $user_id_1, $user_id_2, ... ) ] * * @var array * * @since 5.6.0 */ private static $before_course_users = array(); /** * Group users before update. * Structure: [ (int) $group_id => array( $user_id_1, $user_id_2, ... ) ] * * @var array * * @since 5.6.0 */ private static $before_group_users = array(); /** * Stores the source post ID during a duplication/cloning request. * * @var int|null * * @since 5.6.0 */ private static $duplication_source_id = null; /** * Original LearnDash dates captured early in admin_init before any meta updates. * * Structure: [ (int) $post_id => array( * 'post_type' => 'sfwd-courses' or 'groups', * 'start_date' => $timestamp, * 'end_date' => $timestamp, * )] * * @var array * * @since 5.6.0 */ private static $before_ld_start_end_dates = array(); /** * Pending date change events to be checked after post save completes. * Used to prevent duplicate events from LearnDash's multiple meta updates, * since Learndash will trigger update meta twice: one for start date and one for end date. * * Structure: [ (int) $post_id => array( * 'meta_key' => '_sfwd-courses' or '_groups', * 'checked' => false, * )] * * @var array * * @since 5.6.0 */ private static $pending_date_events = array(); /** * Loads all the plugin dependencies early. * * @return void * * @since 5.6.0 */ public static function early_init() { if ( LearnDash_Helper::is_learndash_active() ) { \add_filter( 'wsal_default_disabled_alerts', array( LearnDash_Custom_Alerts::class, 'add_default_disabled_alerts' ) ); self::maybe_apply_first_detection_disabled_alerts(); \add_filter( 'wsal_event_objects', array( LearnDash_Helper::class, 'wsal_learndash_add_custom_event_objects' ), 10, 2 ); \add_filter( 'wsal_ignored_custom_post_types', array( LearnDash_Helper::class, 'wsal_learndash_add_custom_ignored_cpt' ) ); } } /** * Hook events related to sensor. * * @return void * * @since 5.6.0 */ public static function init() { if ( LearnDash_Helper::is_learndash_active() && \is_user_logged_in() ) { \add_action( 'admin_init', array( __CLASS__, 'store_prev_user_activity_data' ) ); \add_action( 'admin_init', array( __CLASS__, 'maybe_save_duplicate_action_post_id' ) ); \add_action( 'wp_after_insert_post', array( __CLASS__, 'ld_saved_post_event_triggers' ), 10, 4 ); \add_action( 'wp_trash_post', array( __CLASS__, 'ld_post_trashed' ), 10, 1 ); \add_action( 'before_delete_post', array( __CLASS__, 'ld_post_perma_deleted' ), 10, 2 ); \add_action( 'untrash_post', array( __CLASS__, 'ld_post_restored' ), 10, 1 ); \add_action( 'create_term', array( __CLASS__, 'ld_taxonomy_creation_triggers' ), 10, 4 ); \add_action( 'delete_term', array( __CLASS__, 'ld_taxonomy_deletion_triggers' ), 10, 5 ); \add_action( 'wp_head', array( __CLASS__, 'ld_post_viewed' ), 10 ); \add_action( 'admin_action_edit', array( __CLASS__, 'ld_post_opened_in_gutenberg_editor' ), 10 ); \add_filter( 'post_edit_form_tag', array( __CLASS__, 'ld_post_opened_in_classic_editor' ), 10, 1 ); } } /** * Triggered when a user accesses the admin area. * * @since 5.6.0 */ public static function store_prev_user_activity_data() { // Verify nonce before accessing any POST data. $nonce = isset( $_POST['_wpnonce'] ) ? \sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ) ) : ''; $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0; if ( empty( $nonce ) || ! \wp_verify_nonce( $nonce, 'update-post_' . $post_id ) ) { return; } $post_type = \sanitize_text_field( \wp_unslash( $_POST['post_type'] ?? '' ) ); if ( 'sfwd-courses' === $post_type ) { if ( isset( $_POST['post_ID'] ) && isset( $_POST['learndash_course_users_nonce'] ) ) { $post_id = (int) $_POST['post_ID']; if ( \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['learndash_course_users_nonce'] ) ), 'learndash_course_users_nonce_' . $post_id ) ) { if ( \function_exists( 'learndash_get_course_users_access_from_meta' ) ) { self::$before_course_users[ $post_id ] = \learndash_get_course_users_access_from_meta( $post_id ); } } } } if ( 'groups' === $post_type ) { if ( isset( $_POST['post_ID'] ) ) { $post_id = (int) $_POST['post_ID']; $nonce_field = 'learndash_group_users-' . $post_id . '-nonce'; $nonce_action = 'learndash_group_users-' . $post_id; if ( isset( $_POST[ $nonce_field ] ) ) { if ( \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST[ $nonce_field ] ) ), $nonce_action ) ) { if ( \function_exists( 'learndash_get_groups_user_ids' ) ) { self::$before_group_users[ $post_id ] = \learndash_get_groups_user_ids( $post_id, true ); } } } } } } /** * Captures original LearnDash dates from DB before any meta updates. * This method handles both courses and groups. * * @return void * * @since 5.6.0 */ public static function capture_before_ld_start_end_dates() { // Verify nonce before accessing any POST data. $nonce = isset( $_POST['_wpnonce'] ) ? \sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ) ) : ''; $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0; if ( empty( $nonce ) || ! \wp_verify_nonce( $nonce, 'update-post_' . $post_id ) ) { return; } $post_type = \sanitize_text_field( \wp_unslash( $_POST['post_type'] ?? '' ) ); // Only process courses and groups. if ( 'sfwd-courses' !== $post_type && 'groups' !== $post_type ) { return; } $post_dates = LearnDash_Helper::get_ld_start_end_dates_meta( $post_id, $post_type ); if ( isset( $post_dates['start_date'] ) && isset( $post_dates['end_date'] ) ) { // Store original dates. self::$before_ld_start_end_dates[ $post_id ] = array( 'post_type' => $post_type, 'start_date' => $post_dates['start_date'], 'end_date' => $post_dates['end_date'], ); } } /** * Checks and triggers pending date change events after post save completes. * This fires once per request after all LearnDash meta updates are complete, * preventing duplicate events from multiple meta_update hook calls that would happen with LearnDash. * * @param int $post_id - Post ID. * * @return void * * @since 5.6.0 */ public static function maybe_trigger_start_end_date_events( int $post_id ) { // Check if this post has pending date events to check. if ( ! isset( self::$pending_date_events[ $post_id ] ) ) { return; } // Verify we have original dates captured. if ( ! isset( self::$before_ld_start_end_dates[ $post_id ] ) ) { return; } // Skip if already checked. if ( self::$pending_date_events[ $post_id ]['checked'] ) { return; } // Mark as checked to prevent duplicate processing. self::$pending_date_events[ $post_id ]['checked'] = true; // Get the meta key to check. $meta_key = self::$pending_date_events[ $post_id ]['meta_key']; // Get values after updates from database after all meta updates. $final_meta = \get_post_meta( $post_id, $meta_key, true ); $final_metadata = \maybe_unserialize( $final_meta ); if ( ! \is_array( $final_metadata ) ) { // Clean up for this post. unset( self::$pending_date_events[ $post_id ] ); unset( self::$before_ld_start_end_dates[ $post_id ] ); return; } // Get original dates from Phase 1 capture. $original_dates = self::$before_ld_start_end_dates[ $post_id ]; // Determine event ID and date keys based on meta_key. if ( '_sfwd-courses' === $meta_key ) { $start_date_key = 'sfwd-courses_course_start_date'; $end_date_key = 'sfwd-courses_course_end_date'; $event_id = 11056; } elseif ( '_groups' === $meta_key ) { $start_date_key = 'groups_group_start_date'; $end_date_key = 'groups_group_end_date'; $event_id = 11507; } else { return; } /** * Compare dates */ $original_start = LearnDash_Helper::cast_date_to_float( $original_dates['start_date'] ); $original_end = LearnDash_Helper::cast_date_to_float( $original_dates['end_date'] ); $final_start = LearnDash_Helper::cast_date_to_float( $final_metadata[ $start_date_key ] ?? '' ); $final_end = LearnDash_Helper::cast_date_to_float( $final_metadata[ $end_date_key ] ?? '' ); // Only trigger if something actually changed. if ( $original_start === $final_start && $original_end === $final_end ) { // Clean up for this post. unset( self::$pending_date_events[ $post_id ] ); unset( self::$before_ld_start_end_dates[ $post_id ] ); return; } /** * Format dates for display. * * LD format in case of need: * $formatted_old_start = empty( $before_start_date ) ? $not_set_string : \date_i18n( 'd F Y @ H:i', (int) $before_start_date ); * $formatted_new_start = empty( $after_start_date ) ? $not_set_string : \date_i18n( 'd F Y @ H:i', (int) $after_start_date ); * $formatted_old_end = empty( $before_end_date ) ? $not_set_string : \date_i18n( 'd F Y @ H:i', (int) $before_end_date ); * $formatted_new_end = empty( $after_end_date ) ? $not_set_string : \date_i18n( 'd F Y @ H:i', (int) $after_end_date ); */ $not_set_string = \esc_html__( 'Not set', 'wp-security-audit-log' ); $formatted_old_start = empty( $original_start ) ? $not_set_string : DateTime_Formatter_Helper::get_formatted_date_time( $original_start, 'datetime' ); $formatted_new_start = empty( $final_start ) ? $not_set_string : DateTime_Formatter_Helper::get_formatted_date_time( $final_start, 'datetime' ); $formatted_old_end = empty( $original_end ) ? $not_set_string : DateTime_Formatter_Helper::get_formatted_date_time( $original_end, 'datetime' ); $formatted_new_end = empty( $final_end ) ? $not_set_string : DateTime_Formatter_Helper::get_formatted_date_time( $final_end, 'datetime' ); $event_variables = array( 'PostTitle' => \get_the_title( $post_id ), 'PostID' => $post_id, 'OldStartDate' => $formatted_old_start, 'NewStartDate' => $formatted_new_start, 'OldEndDate' => $formatted_old_end, 'NewEndDate' => $formatted_new_end, 'EditorLinkPost' => \esc_url( \get_edit_post_link( $post_id ) ), ); Alert_Manager::trigger_event( $event_id, $event_variables ); // Clean up for this post. unset( self::$pending_date_events[ $post_id ] ); unset( self::$before_ld_start_end_dates[ $post_id ] ); } /** * Detects if a duplication action is being performed and stores the source post ID. * * @return void * * @since 5.6.0 */ public static function maybe_save_duplicate_action_post_id() { // Check for common duplication actions in $_REQUEST. $action = isset( $_REQUEST['action'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['action'] ) ) : ''; $ld_clone_actions = array( 'learndash_cloning_action_course', 'learndash_cloning_action_lesson', 'learndash_cloning_action_topic', ); if ( empty( $action ) || ! in_array( $action, $ld_clone_actions, true ) ) { return; } // Extract and sanitize object_id and nonce from REQUEST. $object_id = isset( $_REQUEST['object_id'] ) ? (int) $_REQUEST['object_id'] : 0; $nonce = isset( $_REQUEST['nonce'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['nonce'] ) ) : ''; // Verify nonce with LearnDash's action format. if ( empty( $nonce ) || ! \wp_verify_nonce( $nonce, $action . $object_id ) ) { return; } // Store the clone source post ID for later use. self::$duplication_source_id = $object_id; } /** * Ignore LearnDash user meta events. * * @param bool $ignore_event - True if ignore meta event, false if not. * @param string $meta_key - Meta key. * @param mixed $meta_value - Meta value. * @param int $user_id - User ID. * * @return bool * * @since 5.6.0 */ public static function ignore_learndash_user_meta_events( $ignore_event, $meta_key, $meta_value, $user_id ) { if ( $ignore_event ) { return $ignore_event; } /** * Ignore LearnDash course enrollment meta keys. */ if ( \preg_match( '/^learndash_course_\d+_enrolled_at$/', $meta_key ) || \preg_match( '/^course_\d+_access_from$/', $meta_key ) ) { return true; } /** * Ignore LearnDash group enrollment meta keys. */ if ( \preg_match( '/^learndash_group_\d+_enrolled_at$/', $meta_key ) || \preg_match( '/^group_\d+_access_from$/', $meta_key ) || \preg_match( '/^learndash_group_users_\d+$/', $meta_key ) ) { return true; } /** * Ignore LearnDash quiz-related meta keys. */ if ( \preg_match( '/^quiz_time_\d+$/', $meta_key ) ) { return true; } return $ignore_event; } /** * Trigger the create event for LearnDash posts * * @param string $post_type - The post type, used to trigger the correct event. * @param array $event_variables - Event variables. * * @since 5.6.0 */ public static function trigger_ld_created_post_event( $post_type, $event_variables ) { if ( 'sfwd-courses' === $post_type ) { Alert_Manager::trigger_event( 11000, $event_variables ); } elseif ( 'sfwd-lessons' === $post_type ) { Alert_Manager::trigger_event( 11200, $event_variables ); } elseif ( 'sfwd-topic' === $post_type ) { Alert_Manager::trigger_event( 11400, $event_variables ); } elseif ( 'groups' === $post_type ) { Alert_Manager::trigger_event( 11500, $event_variables ); } elseif ( 'sfwd-certificates' === $post_type ) { } } /** * Trigger the publish event for LearnDash posts * * @param string $post_type - The post type, used to trigger the correct event. * @param array $event_variables - Event variables. * * @since 5.6.0 */ public static function trigger_ld_published_post_event( $post_type, $event_variables ) { if ( 'sfwd-courses' === $post_type ) { Alert_Manager::trigger_event( 11001, $event_variables ); } elseif ( 'sfwd-lessons' === $post_type ) { Alert_Manager::trigger_event( 11201, $event_variables ); } elseif ( 'sfwd-topic' === $post_type ) { Alert_Manager::trigger_event( 11401, $event_variables ); } elseif ( 'groups' === $post_type ) { Alert_Manager::trigger_event( 11501, $event_variables ); } elseif ( 'sfwd-certificates' === $post_type ) { } } /** * Triggers duplication event if a source post ID was detected. * * @param \WP_Post $post - The duplicated post object. * @param array $post_event_variables - Event variables for the post. * * @return bool - True if duplication event was triggered, false otherwise. * * @since 5.6.0 */ public static function maybe_trigger_duplication_event( $post, $post_event_variables ) { if ( self::$duplication_source_id && LearnDash_Helper::is_post_duplicate( $post, self::$duplication_source_id ) ) { // Override PostTitle with source post. $post_event_variables['PostTitle'] = \esc_html( \get_the_title( self::$duplication_source_id ) ); // Get categories from the SOURCE post. $post_event_variables['Categories'] = LearnDash_Helper::get_post_categories( self::$duplication_source_id ); $post_event_variables['LdPostCategory'] = LearnDash_Helper::get_ld_post_categories( self::$duplication_source_id, $post->post_type ); if ( 'sfwd-courses' === $post->post_type ) { Alert_Manager::trigger_event( 11006, $post_event_variables ); return true; } elseif ( 'sfwd-lessons' === $post->post_type ) { Alert_Manager::trigger_event( 11209, $post_event_variables ); return true; } elseif ( 'sfwd-topic' === $post->post_type ) { Alert_Manager::trigger_event( 11406, $post_event_variables ); return true; } } return false; } /** * Learndash event triggers whenever a user saves a post: create, publish, update. * * @param int $post_id - Post ID. * @param \WP_Post $post - Post object that was just updated. * @param bool $update - Whether this is an existing post being updated. * @param null|\WP_Post $post_before - Null for new posts, the WP_Post object prior to the update for updated posts. * * @return void * * @since 5.6.0 */ public static function ld_saved_post_event_triggers( $post_id, $post, $update, $post_before ) { if ( 'auto-draft' === $post->post_status || 'trash' === $post->post_status ) { return; } $ld_post_types = LearnDash_Helper::get_custom_post_types(); if ( ! \in_array( $post->post_type, $ld_post_types, true ) ) { return; } $post_event_variables = LearnDash_Helper::build_ld_post_event_variables( $post ); $post_event_variables['PostStatus'] = $post->post_status; $post_event_variables['EditorLinkPost'] = \esc_url( \get_edit_post_link( $post->ID ) ); // Check for course UI post creation. if ( $post_before && Learndash_Helper::ld_post_created_via_ld_course_builder( $post_before, $post, $update ) ) { self::trigger_ld_created_post_event( $post->post_type, $post_event_variables ); // Return to avoid further processing. return; } // Check for duplication. if ( self::maybe_trigger_duplication_event( $post, $post_event_variables ) ) { // Return to avoid further processing. return; } /** * A post is considered truly created if it's displayed in its post list. */ if ( ( 'draft' === $post->post_status || 'publish' === $post->post_status ) && $update && 'auto-draft' === $post_before->post_status ) { self::trigger_ld_created_post_event( $post->post_type, $post_event_variables ); } if ( 'publish' === $post->post_status && $update && ( 'draft' === $post_before->post_status || 'auto-draft' === $post_before->post_status ) ) { // Unset PostStatus from variables, it's not needed in published event. unset( $post_event_variables['PostStatus'] ); self::trigger_ld_published_post_event( $post->post_type, $post_event_variables ); // Return to avoid further processing. return; } elseif ( $post_before && 'auto-draft' !== $post_before->post_status ) { } } /** * Detect when a Learndash post is moved to trash. * * @param int $post_id - Post ID. * * @return void * * @since 5.6.0 */ public static function ld_post_trashed( $post_id ) { $post = \get_post( $post_id ); $ld_post_types = LearnDash_Helper::get_custom_post_types(); if ( ! in_array( $post->post_type, $ld_post_types, true ) ) { return; } $event_variables = LearnDash_Helper::build_ld_post_event_variables( $post ); $event_variables['PostStatus'] = $post->post_status; if ( 'sfwd-courses' === $post->post_type ) { Alert_Manager::trigger_event( 11003, $event_variables ); } elseif ( 'sfwd-lessons' === $post->post_type ) { Alert_Manager::trigger_event( 11206, $event_variables ); } elseif ( 'sfwd-topic' === $post->post_type ) { Alert_Manager::trigger_event( 11404, $event_variables ); } elseif ( 'groups' === $post->post_type ) { Alert_Manager::trigger_event( 11503, $event_variables ); } elseif ( 'sfwd-certificates' === $post->post_type ) { } } /** * Detect when a Learndash post is permanently deleted. * * @param int $post_id - Post ID. * @param \WP_Post $post - Post object. * * @return void * * @since 5.6.0 */ public static function ld_post_perma_deleted( $post_id, $post ) { $ld_post_types = LearnDash_Helper::get_custom_post_types(); if ( ! in_array( $post->post_type, $ld_post_types, true ) ) { return; } $event_variables = LearnDash_Helper::build_ld_post_event_variables( $post ); if ( 'sfwd-courses' === $post->post_type ) { Alert_Manager::trigger_event( 11004, $event_variables ); } elseif ( 'sfwd-lessons' === $post->post_type ) { Alert_Manager::trigger_event( 11207, $event_variables ); } elseif ( 'sfwd-topic' === $post->post_type ) { Alert_Manager::trigger_event( 11405, $event_variables ); } elseif ( 'groups' === $post->post_type ) { Alert_Manager::trigger_event( 11504, $event_variables ); } elseif ( 'sfwd-certificates' === $post->post_type ) { } } /** * Detect when a Learndash post is restored from trash. * * @param int $post_id - Post ID. * * @return void * * @since 5.6.0 */ public static function ld_post_restored( $post_id ) { $post = \get_post( $post_id ); $ld_post_types = LearnDash_Helper::get_custom_post_types(); if ( ! in_array( $post->post_type, $ld_post_types, true ) ) { return; } $event_variables = LearnDash_Helper::build_ld_post_event_variables( $post ); $event_variables['EditorLinkPost'] = \esc_url( \get_edit_post_link( $post->ID ) ); if ( 'sfwd-courses' === $post->post_type ) { Alert_Manager::trigger_event( 11005, $event_variables ); } elseif ( 'sfwd-lessons' === $post->post_type ) { Alert_Manager::trigger_event( 11208, $event_variables ); } elseif ( 'groups' === $post->post_type ) { Alert_Manager::trigger_event( 11505, $event_variables ); } elseif ( 'sfwd-certificates' === $post->post_type ) { } } /** * New course category created trigger. * * @param int $term_id - Term ID. * @param int $tt_id - Term taxonomy ID. * @param string $taxonomy - Taxonomy slug. * @param array $args - Arguments passed to wp_insert_term(). * * @return void * * @since 5.6.0 */ public static function ld_taxonomy_creation_triggers( $term_id, $tt_id, $taxonomy, $args ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed $ld_taxonomies = LearnDash_Helper::get_custom_taxonomies(); if ( ! \in_array( $taxonomy, $ld_taxonomies, true ) ) { return; } $term = \get_term( $term_id, $taxonomy ); $event_variables = array( 'TaxonomyTitle' => $term->name, 'Slug' => $term->slug, ); if ( 'ld_course_category' === $taxonomy ) { $event_variables['CategoryLink'] = LearnDash_Helper::get_ld_taxonomy_edit_link( $term_id, 'ld_course_category', 'sfwd-courses' ); Alert_Manager::trigger_event( 11080, $event_variables ); } elseif ( 'ld_lesson_category' === $taxonomy ) { $event_variables['CategoryLink'] = LearnDash_Helper::get_ld_taxonomy_edit_link( $term_id, 'ld_lesson_category', 'sfwd-lessons' ); Alert_Manager::trigger_event( 11300, $event_variables ); } elseif ( 'ld_course_tag' === $taxonomy ) { $event_variables['TagLink'] = LearnDash_Helper::get_ld_taxonomy_edit_link( $term_id, 'ld_course_tag', 'sfwd-courses' ); Alert_Manager::trigger_event( 11090, $event_variables ); } elseif ( 'ld_lesson_tag' === $taxonomy ) { $event_variables['TagLink'] = LearnDash_Helper::get_ld_taxonomy_edit_link( $term_id, 'ld_lesson_tag', 'sfwd-lessons' ); Alert_Manager::trigger_event( 11350, $event_variables ); } elseif ( 'ld_topic_tag' === $taxonomy ) { $event_variables['TagLink'] = LearnDash_Helper::get_ld_taxonomy_edit_link( $term_id, 'ld_topic_tag', 'sfwd-topic' ); Alert_Manager::trigger_event( 11450, $event_variables ); } } /** * Course category deleted trigger. * * @param int $term_id - Term ID. * @param int $tt_id - Term taxonomy ID. * @param string $taxonomy - Taxonomy slug. * @param \WP_Term $deleted_term - Copy of the already-deleted term. * @param array $object_ids - List of term object IDs. * * @return void * * @since 5.6.0 */ public static function ld_taxonomy_deletion_triggers( $term_id, $tt_id, $taxonomy, $deleted_term, $object_ids ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed $ld_taxonomies = LearnDash_Helper::get_custom_taxonomies(); if ( ! \in_array( $taxonomy, $ld_taxonomies, true ) ) { return; } $event_variables = array( 'TaxonomyTitle' => $deleted_term->name, 'Slug' => $deleted_term->slug, ); if ( 'ld_course_category' === $taxonomy ) { Alert_Manager::trigger_event( 11081, $event_variables ); } elseif ( 'ld_lesson_category' === $taxonomy ) { Alert_Manager::trigger_event( 11301, $event_variables ); } elseif ( 'ld_course_tag' === $taxonomy ) { Alert_Manager::trigger_event( 11091, $event_variables ); } elseif ( 'ld_lesson_tag' === $taxonomy ) { Alert_Manager::trigger_event( 11351, $event_variables ); } elseif ( 'ld_topic_tag' === $taxonomy ) { Alert_Manager::trigger_event( 11451, $event_variables ); } } /** * Detect when a logged-in user views a LearnDash course or lesson on the front-end. * * @return void * * @since 5.6.2 */ public static function ld_post_viewed() { if ( ! \is_singular( array( 'sfwd-courses', 'sfwd-lessons' ) ) ) { return; } if ( ! \is_user_logged_in() || \is_admin() ) { return; } $post = \get_queried_object(); if ( empty( $post ) || ! $post instanceof \WP_Post ) { return; } $current_path = isset( $_SERVER['REQUEST_URI'] ) ? \sanitize_text_field( \wp_unslash( $_SERVER['REQUEST_URI'] ) ) : false; if ( ! empty( $_SERVER['HTTP_REFERER'] ) && ! empty( $current_path ) && false !== \strpos( \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_REFERER'] ) ), $current_path ) ) { // Ignore same-page refresh to avoid double entries. return; } if ( empty( $post->post_title ) ) { return; } if ( 'sfwd-courses' === $post->post_type ) { $event_id = 11020; } elseif ( 'sfwd-lessons' === $post->post_type ) { $event_id = 11215; } else { return; } $event_variables = LearnDash_Helper::build_ld_view_event_variables( $post ); $event_variables['PostUrl'] = \esc_url( \get_permalink( $post->ID ) ); $event_variables['EditorLinkPost'] = \esc_url( \get_edit_post_link( $post->ID ) ); Alert_Manager::trigger_event( $event_id, $event_variables ); } /** * Detect when a LearnDash course or lesson is opened in the Gutenberg editor. * * @return void * * @since 5.6.2 */ public static function ld_post_opened_in_gutenberg_editor() { $post_id = isset( $_GET['post'] ) ? (int) $_GET['post'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( 0 === $post_id ) { return; } $post = \get_post( $post_id ); if ( empty( $post ) || ! $post instanceof \WP_Post ) { return; } if ( ! \in_array( $post->post_type, array( 'sfwd-courses', 'sfwd-lessons' ), true ) ) { return; } self::ld_post_opened_in_editor( $post ); } /** * Detect when a LearnDash course or lesson is opened in the Classic editor. * * @param \WP_Post $post - Post object. * * @return \WP_Post * * @since 5.6.2 */ public static function ld_post_opened_in_classic_editor( $post ) { if ( empty( $post ) || ! $post instanceof \WP_Post ) { return $post; } if ( ! \in_array( $post->post_type, array( 'sfwd-courses', 'sfwd-lessons' ), true ) ) { return $post; } self::ld_post_opened_in_editor( $post ); return $post; } /** * Shared logic for triggering the "opened in editor" event for LearnDash courses and lessons. * * @param \WP_Post $post - Post object. * * @return void * * @since 5.6.2 */ private static function ld_post_opened_in_editor( $post ) { $current_path = isset( $_SERVER['SCRIPT_NAME'] ) ? \esc_url_raw( \wp_unslash( $_SERVER['SCRIPT_NAME'] ) ) . '?post=' . $post->ID : false; $referrer = isset( $_SERVER['HTTP_REFERER'] ) ? \esc_url_raw( \wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : false; // If the referrer is post-new then we can ignore this one. if ( ! empty( $referrer ) ) { $parsed_url = \wp_parse_url( $referrer ); if ( isset( $parsed_url['path'] ) && 'post-new' === basename( $parsed_url['path'], '.php' ) ) { return; } } if ( ! empty( $referrer ) && false !== strpos( $referrer, $current_path ) ) { // Ignore same-page to avoid double entries. return; } if ( empty( $post->post_title ) ) { return; } if ( 'sfwd-courses' === $post->post_type ) { $event_id = 11021; } elseif ( 'sfwd-lessons' === $post->post_type ) { $event_id = 11216; } else { return; } if ( ! Alert_Manager::was_triggered( $event_id ) ) { $event_variables = LearnDash_Helper::build_ld_view_event_variables( $post ); $event_variables['EditorLinkPost'] = \esc_url( \get_edit_post_link( $post->ID ) ); Alert_Manager::trigger_event( $event_id, $event_variables ); } } /** * Applies default disabled alerts when LearnDash is first detected by an existing WSAL install. * * This ensures high-volume events are disabled even when WSAL was installed before LearnDash. * * @return void * * @since 5.6.0 */ private static function maybe_apply_first_detection_disabled_alerts() { $processed_plugins = \get_option( 'wsal_detected_plugins_processed', array() ); if ( in_array( 'sfwd-lms', $processed_plugins, true ) ) { return; } $disabled_alerts = Settings_Helper::get_disabled_alerts(); $learndash_defaults = LearnDash_Custom_Alerts::get_default_disabled_alerts(); $disabled_alerts = array_unique( array_merge( $disabled_alerts, $learndash_defaults ) ); Settings_Helper::set_disabled_alerts( $disabled_alerts ); $processed_plugins[] = 'sfwd-lms'; \update_option( 'wsal_detected_plugins_processed', $processed_plugins ); } } }