Files
SIGE-WEB-snapshot/wp-content/plugins/wp-security-audit-log/classes/WPSensors/class-wpforms-sensor.php

1343 lines
51 KiB
PHP

<?php
/**
* Custom Sensors for WPForms
*
* Class file for alert manager.
*
* @since 1.0.0
* @package Wsal
*/
declare(strict_types=1);
namespace WSAL\WP_Sensors;
use WPForms\Pro\Access\Capabilities;
use WSAL\Controllers\Alert_Manager;
use WSAL\WP_Sensors\Helpers\WPForms_Helper;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( '\WSAL\WP_Sensors\WPForms_Sensor' ) ) {
/**
* Custom sensor class to process WPForms events.
*
* @since 4.6.0
*/
class WPForms_Sensor {
/**
* Holds a cached value if the checked alert has recently fired.
*
* @var \WP_Post
*
* @since 4.6.0
*/
private static $old_post = null;
/**
* Hook events related to sensor.
*
* @since 4.6.0
*/
public static function init() {
if ( WPForms_Helper::is_wpforms_active() ) {
\add_action( 'pre_post_update', array( __CLASS__, 'get_before_post_edit_data' ), 10, 2 );
\add_action( 'save_post', array( __CLASS__, 'event_form_saved' ), 10, 3 );
\add_action( 'delete_post', array( __CLASS__, 'event_form_deleted' ), 10, 1 );
\add_action( 'wpforms_pre_delete', array( __CLASS__, 'event_entry_deleted' ), 10, 1 );
\add_action( 'wpforms_pro_admin_entries_edit_submit_completed', array( __CLASS__, 'event_entry_modified' ), 5, 4 );
\add_action( 'updated_option', array( __CLASS__, 'event_settings_updated' ), 10, 3 );
\add_action( 'added_option', array( __CLASS__, 'event_added_option' ), 10, 2 );
\add_action( 'wpforms_plugin_activated', array( __CLASS__, 'addon_plugin_activated' ), 10, 1 );
\add_action( 'wpforms_plugin_deactivated', array( __CLASS__, 'addon_plugin_deactivated' ), 10, 1 );
\add_action( 'wpforms_plugin_installed', array( __CLASS__, 'addon_plugin_installed' ), 10, 1 );
\add_action( 'wpforms_process_complete', array( __CLASS__, 'event_entry_added' ), 10, 4 );
\add_action( 'wpforms_post_delete_entry', array( __CLASS__, 'event_entry_deleted' ), 10, 1 );
}
}
/**
* Loads all the plugin dependencies early.
*
* @return void
*
* @since 4.6.0
*/
public static function early_init() {
/**
* Add our filters.
*/
\add_filter(
'wsal_event_objects',
array( WPForms_Helper::class, 'wsal_wpforms_add_custom_event_objects' )
);
\add_filter(
'wsal_ignored_custom_post_types',
array( WPForms_Helper::class, 'wsal_wpforms_add_custom_ignored_cpt' )
);
}
/**
* Get Post Data.
*
* Collect old post data before post update event.
*
* @since 4.6.0
*
* @param int $post_id - Post ID.
*/
public static function get_before_post_edit_data( $post_id ) {
$post_id = absint( $post_id ); // Making sure that the post id is integer.
$post = get_post( $post_id ); // Get post.
// If post exists.
if ( ! empty( $post ) && $post instanceof \WP_Post ) {
self::$old_post = $post;
}
}
/**
* Trigger event when an entry is added,
*
* @param array $fields - Submitted form fields.
* @param array $entry - Entry content.
* @param array $form_data - For. data.
* @param int $entry_id - entry id.
* @return void
*
* @since 4.6.0
*/
public static function event_entry_added( $fields, $entry, $form_data, $entry_id ) {
$alert_code = 5523;
$editor_link = esc_url(
add_query_arg(
array(
'view' => 'edit',
'entry_id' => $entry_id,
),
admin_url( 'admin.php?page=wpforms-entries' )
)
);
// Grab form content.
$form_content = '';
$field_values = array_values( $fields );
foreach ( $field_values as $value ) {
$form_content .= implode( ',', $value );
}
// Search it for any email address.
$email_address = self::extract_emails( $form_content );
// Now let's see if we have more than one email present, if so, just grab the 1st one.
if ( $email_address && is_array( $email_address ) ) {
$email_address = $email_address[0];
} elseif ( $email_address && ! is_array( $email_address ) ) {
$email_address = $email_address;
} else {
$email_address = esc_html__( 'No email provided', 'wp-security-audit-log' );
}
$variables = array(
'form_name' => sanitize_text_field( $form_data['settings']['form_title'] ),
'form_id' => sanitize_text_field( $form_data['id'] ),
'entry_email' => $email_address,
'EditorLinkEntry' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'check_if_duplicate' ) );
}
/**
* Form renamed event.
*
* Detect when forms title has been changed.
*
* @since 4.6.0
*
* @param int $post_id - Post ID.
* @param object $post - Post data.
* @param bool $update - Whether this is an existing post being updated or not.
*/
public static function event_form_saved( $post_id, $post, $update ) {
$post_id = absint( $post_id ); // Making sure that the post id is integer.
$form = get_post( $post_id );
$has_alert_triggered = false; // Create a variable so we can determine if an alert has already fired.
// Handling form creation. First lets check an old post was set and its not flagged as an update, then finally check its not a duplicate.
if ( ! isset( self::$old_post->post_title ) && ! $update && ! preg_match( '/\s\(ID #[0-9].*?\)/', $form->post_title ) && 'wpforms' === $post->post_type ) {
$alert_code = 5500;
$editor_link = self::create_form_post_editor_link( $post_id );
$variables = array(
'EventType' => 'created',
'PostTitle' => sanitize_text_field( $post->post_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'check_if_duplicate' ) );
$has_alert_triggered = true;
// Handling form rename. Check if this is a form and if an old title is set.
} elseif ( isset( self::$old_post->post_title ) && self::$old_post->post_title !== $post->post_title && 'wpforms' === $post->post_type && $update ) {
// Checking to ensure this is not a draft or fresh form.
if ( isset( $post->post_status ) && 'auto-draft' !== $post->post_status ) {
$alert_code = 5506;
$post = get_post( $post_id );
$editor_link = self::create_form_post_editor_link( $post_id );
$variables = array(
'EventType' => 'renamed',
'old_form_name' => sanitize_text_field( self::$old_post->post_title ),
'new_form_name' => sanitize_text_field( $post->post_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
}
}
// Handling duplicated forms by checking to see if the post has ID # in the title.
if ( preg_match( '/\s\(ID #[0-9].*?\)/', $form->post_title ) && 'wpforms' === $form->post_type ) {
$post_created = new \DateTime( $form->post_date_gmt );
$post_modified = new \DateTime( $form->post_modified_gmt );
$alert_code = 5502;
// Check if this is indeed a new form.
if ( $form->post_date_gmt === $form->post_modified_gmt ) {
// Grab old form ID from its post content.
$old_form_content = self::json_decode_encode( self::$old_post->post_content );
$editor_link = self::create_form_post_editor_link( $post_id );
if ( isset( $old_form_content->id ) ) {
$variables = array(
'OldPostTitle' => sanitize_text_field( self::$old_post->post_title ),
'PostTitle' => sanitize_text_field( $form->post_title ),
'SourceID' => sanitize_text_field( $old_form_content->id ),
'PostID' => $post_id,
'EditorLinkFormDuplicated' => $editor_link,
);
Alert_Manager::trigger_event( $alert_code, $variables );
$has_alert_triggered = true;
remove_action( 'save_post', array( __CLASS__, 'event_form_saved' ), 10, 3 );
}
}
}
if ( 'wpforms' === $form->post_type && isset( self::$old_post ) ) {
if ( isset( $post->post_status ) && 'auto-draft' !== $post->post_status ) {
$form_content = self::json_decode_encode( $form->post_content );
$old_form_content = self::json_decode_encode( self::$old_post->post_content );
$post_created = new \DateTime( $post->post_date_gmt );
$post_modified = new \DateTime( $post->post_modified_gmt );
$editor_link = self::create_form_post_editor_link( $post_id );
if ( ( isset( $form_content->settings->antispam_v3 ) && ! isset( $old_form_content->settings->antispam_v3 ) ) || ( isset( $old_form_content->settings->antispam_v3 ) && ! isset( $form_content->settings->antispam_v3 ) ) ) {
$alert_code = 5513;
$variables = array(
'EventType' => ( isset( $form_content->settings->antispam_v3 ) ) ? 'enabled' : 'disabled',
'form_name' => sanitize_text_field( $form->post_title ),
'form_id' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
if ( ( 1 === (int) $form_content->settings->store_spam_entries && 0 === (int) $old_form_content->settings->store_spam_entries ) || ( 1 === (int) $old_form_content->settings->store_spam_entries && 0 === (int) $form_content->settings->store_spam_entries ) ) {
$alert_code = 5525;
$variables = array(
'EventType' => ( isset( $form_content->settings->store_spam_entries ) ) ? 'enabled' : 'disabled',
'form_name' => sanitize_text_field( $form->post_title ),
'form_id' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
if ( ( isset( $form_content->settings->anti_spam->time_limit->enable ) && ! isset( $old_form_content->settings->anti_spam->time_limit->enable ) ) || ( isset( $old_form_content->settings->anti_spam->time_limit->enable ) && ! isset( $form_content->settings->anti_spam->time_limit->enable ) ) ) {
$alert_code = 5526;
$variables = array(
'EventType' => ( isset( $form_content->settings->anti_spam->time_limit->enable ) ) ? 'enabled' : 'disabled',
'form_name' => sanitize_text_field( $form->post_title ),
'form_id' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
if ( isset( $form_content->settings->anti_spam->time_limit->duration ) ) {
$new_value = (int) $form_content->settings->anti_spam->time_limit->duration;
$old_Value = isset( $old_form_content->settings->anti_spam->time_limit->enable ) ? (int) $old_form_content->settings->anti_spam->time_limit->duration : 0;
if ( $new_value !== $old_Value ) {
$alert_code = 5527;
$variables = array(
'new_value' => (int) $form_content->settings->anti_spam->time_limit->duration,
'old_value' => $old_Value,
'form_name' => sanitize_text_field( $form->post_title ),
'form_id' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
}
if ( isset( $form_content->settings->antispam ) && ! isset( $old_form_content->settings->antispam ) || isset( $old_form_content->settings->antispam ) && ! isset( $form_content->settings->antispam ) ) {
$alert_code = 5513;
$variables = array(
'EventType' => ( isset( $form_content->settings->antispam ) ) ? 'enabled' : 'disabled',
'form_name' => sanitize_text_field( $form->post_title ),
'form_id' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
if ( isset( $form_content->settings->dynamic_population ) && ! isset( $old_form_content->settings->dynamic_population ) || ! isset( $form_content->settings->dynamic_population ) && isset( $old_form_content->settings->dynamic_population ) ) {
$alert_code = 5514;
$variables = array(
'EventType' => ( isset( $form_content->settings->dynamic_population ) ) ? 'enabled' : 'disabled',
'form_name' => sanitize_text_field( $form->post_title ),
'form_id' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
if ( isset( $form_content->settings->ajax_submit ) && ! isset( $old_form_content->settings->ajax_submit ) || ! isset( $form_content->settings->ajax_submit ) && isset( $old_form_content->settings->ajax_submit ) ) {
$alert_code = 5515;
$variables = array(
'EventType' => ( isset( $form_content->settings->ajax_submit ) ) ? 'enabled' : 'disabled',
'form_name' => sanitize_text_field( $form->post_title ),
'form_id' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
if ( isset( $form_content->settings->confirmations ) && isset( $old_form_content->settings->confirmations ) ) {
$form_content_array = self::json_decode_encode( $form_content->settings->confirmations, true, true );
$old_form_content_array = self::json_decode_encode( $old_form_content->settings->confirmations, true, true );
$changes = self::determine_added_removed_and_changed_items( $form_content_array, $old_form_content_array );
$added_items = $changes['added'];
$removed_items = $changes['deleted'];
$changed_items = $changes['modified'];
// Check new content size determine if something has been added.
if ( count( $form_content_array ) > count( $old_form_content_array ) ) {
$alert_code = 5518;
foreach ( $added_items as $confirmation ) {
if ( isset( $confirmation['name'] ) ) {
$confirmation_name = $confirmation['name'];
} else {
$confirmation_name = esc_html__( 'Default confirmation', 'wp-security-audit-log' );
}
$variables = array(
'EventType' => 'added',
'confirmation_name' => sanitize_text_field( $confirmation_name ),
'form_name' => sanitize_text_field( $form->post_title ),
'form_id' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
}
// Check new content size determine if something has been removed.
} elseif ( count( $form_content_array ) < count( $old_form_content_array ) ) {
$alert_code = 5518;
foreach ( $removed_items as $confirmation ) {
if ( isset( $confirmation['name'] ) ) {
$confirmation_name = $confirmation['name'];
} else {
$confirmation_name = esc_html__( 'Default confirmation', 'wp-security-audit-log' );
}
$variables = array(
'EventType' => 'deleted',
'confirmation_name' => sanitize_text_field( $confirmation_name ),
'form_name' => sanitize_text_field( $form->post_title ),
'form_id' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
}
} elseif ( ! empty( $changed_items ) ) {
foreach ( $changed_items as $key => $confirmation ) {
$new_array = (array) $form_content->settings->confirmations;
if ( empty( $new_array ) || ! isset( $new_array[ $key ] ) ) {
continue;
}
$new_changed_item = (array) $new_array[ $key ];
$confirmation_changes = array(
'type',
'page',
'redirect',
'message',
);
foreach ( $confirmation_changes as $change_type ) {
if ( ! isset( $confirmation[ $change_type ] ) ) {
continue;
}
if ( strip_tags( $new_changed_item[ $change_type ] ) !== strip_tags( $confirmation[ $change_type ] ) ) {
if ( 'type' === $change_type ) {
$alert_code = 5519;
} elseif ( 'page' === $change_type ) {
$alert_code = 5520;
$new_changed_item[ $change_type ] = get_the_title( $new_changed_item[ $change_type ] );
$confirmation[ $change_type ] = get_the_title( $confirmation[ $change_type ] );
} elseif ( 'redirect' === $change_type ) {
$alert_code = 5521;
} elseif ( 'message' === $change_type ) {
$alert_code = 5522;
$confirmation[ $change_type ] = \sanitize_text_field( \wp_strip_all_tags( $confirmation[ $change_type ] ) );
$new_changed_item[ $change_type ] = \sanitize_text_field( \wp_strip_all_tags( $new_changed_item[ $change_type ] ) );
}
if ( ! isset( $alert_code ) ) {
continue;
}
if ( isset( $new_changed_item['name'] ) ) {
$confirmation_name = $new_changed_item['name'];
} else {
$confirmation_name = esc_html__( 'Default confirmation', 'wp-security-audit-log' );
}
$variables = array(
'confirmation_name' => $confirmation_name,
'old_value' => $confirmation[ $change_type ],
'new_value' => $new_changed_item[ $change_type ],
'form_name' => sanitize_text_field( $form_content->settings->form_title ),
'form_id' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
}
}
}
}
}
}
}
// Handling form notifications.
if ( 'wpforms' === $form->post_type && isset( self::$old_post ) && $update && ! Alert_Manager::was_triggered_recently( 5500 ) ) {
// Checking to ensure this is not a draft or fresh form.
if ( isset( $post->post_status ) && 'auto-draft' !== $post->post_status ) {
$form_content = self::json_decode_encode( $form->post_content );
$old_form_content = self::json_decode_encode( self::$old_post->post_content );
$post_created = new \DateTime( $post->post_date_gmt );
$post_modified = new \DateTime( $post->post_modified_gmt );
$editor_link = self::create_form_post_editor_link( $post_id );
// Create 2 arrays from the notification object for comparison later.
if ( isset( $form_content->settings->notifications ) && isset( $old_form_content->settings->notifications ) ) {
$form_content_array = self::json_decode_encode( $form_content->settings->notifications, true, true );
$old_form_content_array = self::json_decode_encode( $old_form_content->settings->notifications, true, true );
$changes = self::determine_added_removed_and_changed_items( $form_content_array, $old_form_content_array );
$added_items = $changes['added'];
$removed_items = $changes['deleted'];
$changed_items = $changes['modified'];
// Check new content size determine if something has been added.
if ( count( $form_content_array ) > count( $old_form_content_array ) ) {
$alert_code = 5503;
foreach ( $added_items as $notification ) {
if ( isset( $notification['notification_name'] ) ) {
$notification_name = $notification['notification_name'];
} else {
$notification_name = esc_html__( 'Default Notification', 'wp-security-audit-log' );
}
$variables = array(
'EventType' => 'created',
'notifiation_name' => sanitize_text_field( $notification_name ),
'form_name' => sanitize_text_field( $form->post_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
}
// Check new content size determine if something has been removed.
} elseif ( count( $form_content_array ) < count( $old_form_content_array ) ) {
$alert_code = 5503;
foreach ( $removed_items as $notification ) {
if ( isset( $notification['notification_name'] ) ) {
$notification_name = $notification['notification_name'];
} else {
$notification_name = esc_html__( 'Default Notification', 'wp-security-audit-log' );
}
$variables = array(
'EventType' => 'deleted',
'notifiation_name' => sanitize_text_field( $notification_name ),
'form_name' => sanitize_text_field( $form->post_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
}
// Compare old post and new post to see if the notifications have been disabled.
} elseif ( isset( $old_form_content->settings->notification_enable ) && ! isset( $form_content->settings->notification_enable ) ) {
$alert_code = 5505;
$variables = array(
'EventType' => 'disabled',
'form_name' => sanitize_text_field( $form_content->settings->form_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
} elseif ( ! isset( $old_form_content->settings->notification_enable ) && isset( $form_content->settings->notification_enable ) ) {
$alert_code = 5505;
$variables = array(
'EventType' => 'enabled',
'form_name' => sanitize_text_field( $form_content->settings->form_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
// Finally, as none of the above triggered anything, lets see if the notifications themselves have been modified.
} elseif ( ! empty( $changed_items ) ) {
// Check time and also if there is an actual change in the post content.
if ( abs( $post_created->diff( $post_modified )->s ) <= 1 ) {
// post hasn't changed return without event trigger.
return;
}
$alert_code = 5503;
foreach ( $changed_items as $key => $notification ) {
if ( isset( $notification['notification_name'] ) ) {
$notification_name = $notification['notification_name'];
} else {
$notification_name = esc_html__( 'Default Notification', 'wp-security-audit-log' );
}
$new_array = (array) $form_content->settings->notifications;
$new_changed_item = (array) $new_array[ $key ];
$new_name = isset( $new_changed_item['notification_name'] ) ? $new_changed_item['notification_name'] : false;
if ( ! $new_name ) {
continue;
}
$notification_metas = array(
'email',
'subject',
'sender_name',
'sender_address',
'replyto',
'message',
);
foreach ( $notification_metas as $metas ) {
if ( isset( $changed_items[ $key ][ $metas ] ) && $new_changed_item[ $metas ] !== $changed_items[ $key ][ $metas ] ) {
$alert_code = 5517;
$variables = array(
'EventType' => 'modified',
'metadata_name' => $metas,
'old_value' => $changed_items[ $key ][ $metas ],
'new_value' => $new_changed_item[ $metas ],
'form_name' => sanitize_text_field( $form->post_title ),
'notification_name' => $notification_name,
'form_id' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$alert_code = null;
}
}
if ( $notification_name !== $new_name ) {
$alert_code = 5516;
$variables = array(
'EventType' => 'modified',
'old_name' => sanitize_text_field( $notification_name ),
'new_name' => sanitize_text_field( $new_name ),
'form_name' => sanitize_text_field( $form->post_title ),
'form_id' => $post_id,
'EditorLinkForm' => $editor_link,
);
} else {
$variables = array(
'EventType' => 'modified',
'notifiation_name' => sanitize_text_field( $notification_name ),
'form_name' => sanitize_text_field( $form->post_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
}
if ( $alert_code ) {
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
}
$has_alert_triggered = true;
}
}
}
}
}
// Handling fields.
if ( 'wpforms' === $form->post_type && isset( self::$old_post ) ) {
// Checking to ensure this is not a draft or fresh form.
if ( isset( $post->post_status ) && 'auto-draft' !== $post->post_status ) {
$form_content = self::json_decode_encode( $form->post_content );
$old_form_content = self::json_decode_encode( self::$old_post->post_content );
$post_created = new \DateTime( $post->post_date_gmt );
$post_modified = new \DateTime( $post->post_modified_gmt );
$editor_link = self::create_form_post_editor_link( $post_id );
// First lets see if we have BOTH old and new content to compare.
if ( isset( $form_content->fields ) && isset( $old_form_content->fields ) && serialize( $form_content->fields ) !== serialize( $old_form_content->fields ) ) {
// Create 2 arrays from the fields object for comparison later.
$form_content_array = self::json_decode_encode( $form_content->fields, true, true );
$old_form_content_array = self::json_decode_encode( $old_form_content->fields, true, true );
// Compare the 2 arrays and create array of added items.
if ( $form_content_array !== $old_form_content_array ) {
$compare_added_items = array_diff(
array_map( 'serialize', $form_content_array ),
array_map( 'serialize', $old_form_content_array )
);
$added_items = array_map( 'unserialize', $compare_added_items );
} else {
$added_items = $form_content_array;
}
$changes = self::determine_added_removed_and_changed_items( $form_content_array, $old_form_content_array );
$added_items = $changes['added'];
$removed_items = $changes['deleted'];
$changed_items = $changes['modified'];
$changed_items = array_intersect_key( $added_items, $changed_items );
if ( ! empty( $added_items ) ) {
$added_items = array_diff(
array_map( 'serialize', $added_items ),
array_map( 'serialize', $changed_items )
);
$added_items = array_map( 'unserialize', $added_items );
}
// Check new content size determine if something has been added.
if ( $added_items && $added_items !== $changed_items ) {
$alert_code = 5501;
foreach ( $added_items as $fields ) {
$field_name = self::get_type_if_field_has_no_name( $fields );
$variables = array(
'EventType' => 'created',
'field_name' => $field_name,
'form_name' => sanitize_text_field( $form->post_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
}
}
// Check new content size determine if something has been removed.
if ( $removed_items ) {
$alert_code = 5501;
foreach ( $removed_items as $fields => $value ) {
if ( ! empty( $changed_items ) ) {
if ( ! $changed_items[ $fields ] ) {
$field_name = self::get_type_if_field_has_no_name( $value );
$variables = array(
'EventType' => 'deleted',
'field_name' => $field_name,
'form_name' => sanitize_text_field( $form->post_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
}
} else {
$field_name = self::get_type_if_field_has_no_name( $value );
$variables = array(
'EventType' => 'deleted',
'field_name' => $field_name,
'form_name' => sanitize_text_field( $form->post_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
}
}
}
// Check content to see if anything has been modified.
if ( ! empty( $changed_items ) && ! Alert_Manager::was_triggered_recently( 5500 ) ) {
$alert_code = 5501;
foreach ( $changed_items as $fields ) {
$field_name = self::get_type_if_field_has_no_name( $fields );
$variables = array(
'EventType' => 'modified',
'field_name' => $field_name,
'form_name' => sanitize_text_field( $form->post_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
}
}
// Now we shall check if we have just a single new field thats been added.
} elseif ( isset( $form_content->fields ) && ! isset( $old_form_content->fields ) ) {
// Create 2 arrays from the fields object for comparison later.
$form_content_array = self::json_decode_encode( $form_content->fields, true, true );
$alert_code = 5501;
foreach ( $form_content_array as $fields ) {
$variables = array(
'EventType' => 'created',
'field_name' => sanitize_text_field( $fields['label'] ),
'form_name' => sanitize_text_field( $form->post_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
}
// Finally we shall check if we have just a single new field thats been removed.
} elseif ( ! isset( $form_content->fields ) && isset( $old_form_content->fields ) ) {
// Create 2 arrays from the fields object for comparison later.
$form_content_array = self::json_decode_encode( $old_form_content->fields, true, true );
$alert_code = 5501;
foreach ( $form_content_array as $fields ) {
$variables = array(
'EventType' => 'deleted',
'field_name' => sanitize_text_field( $fields['label'] ),
'form_name' => sanitize_text_field( $form->post_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'must_not_be_new_form' ) );
$has_alert_triggered = true;
}
}
}
}
// Finally, if all of the above didn't catch anything, but the form as still been modified in some way, lets handle that.
if ( ! $has_alert_triggered && 'wpforms' === $form->post_type && isset( self::$old_post ) && ! $update && ! Alert_Manager::was_triggered_recently( 5500 ) ) {
if ( isset( $post->post_status ) && 'auto-draft' !== $post->post_status ) {
$alert_code = 5500;
$form_content = self::json_decode_encode( $form->post_content );
$old_form_content = self::json_decode_encode( self::$old_post->post_content );
// First, lets check the content is available in the current and old post.
if ( isset( $form_content ) && isset( $old_form_content ) ) {
// Content is found, so lets create some arrays to compare for changes.
$form_content_array = self::json_decode_encode( $form_content, true, true );
$old_form_content_array = self::json_decode_encode( $old_form_content, true, true );
$compare_changed_items = array_diff_assoc(
array_map( 'serialize', $old_form_content_array ),
array_map( 'serialize', $form_content_array )
);
// Round up any changes into a neat array, could expand in this later also.
$changed_items = array_map( 'unserialize', $compare_changed_items );
// Now lets check if anything has been added to our array, if it has, somethings changed so lets alert.
if ( $changed_items ) {
$editor_link = self::create_form_post_editor_link( $post_id );
$variables = array(
'EventType' => 'modified',
'PostTitle' => sanitize_text_field( $post->post_title ),
'PostID' => $post_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event_if( $alert_code, $variables, array( __CLASS__, 'check_if_duplicate' ) );
remove_action( 'save_post', array( __CLASS__, 'event_form_saved' ), 10, 3 );
}
}
}
}
}
/**
* Form deleted event.
*
* Detect when a form has been fully deleted.
*
* @since 4.6.0
*
* @param int $post_id - Post ID.
*/
public static function event_form_deleted( $post_id ) {
$alert_code = 5500;
$post_id = absint( $post_id );
$post = get_post( $post_id );
if ( 'wpforms' === $post->post_type ) {
$variables = array(
'EventType' => 'deleted',
'PostTitle' => $post->post_title,
'PostID' => $post_id,
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
}
/**
* Delete entry event.
*
* Detect when an entry has been deleted.
*
* @param int $row_id - Row ID.
*
* @since 4.6.0
*/
public static function event_entry_deleted( $row_id ) {
$alert_code = 5504;
if ( is_null( $row_id ) || ! isset( $row_id ) ) {
return;
}
if ( \is_null( wpforms()->entry ) ) {
return;
}
$entry = wpforms()->entry->get( $row_id );
if ( is_null( $entry ) ) {
return;
}
$form = get_post( $entry->form_id );
// Grab from content.
$form_content = (string) $entry->fields;
// Search it for any email address.
$email_address = self::extract_emails( $form_content );
// Now lets see if we have more than one email present, if so, just grab the 1st one.
if ( $email_address && is_array( $email_address ) ) {
$email_address = $email_address[0];
} elseif ( $email_address && ! is_array( $email_address ) ) {
$email_address = $email_address;
} else {
$email_address = esc_html__( 'No email provided', 'wp-security-audit-log' );
}
$editor_link = self::create_form_post_editor_link( $entry->form_id );
$variables = array(
'entry_email' => sanitize_text_field( $email_address ),
'entry_id' => sanitize_text_field( $row_id ),
'form_name' => sanitize_text_field( $form->post_title ),
'form_id' => $entry->form_id,
'EditorLinkForm' => $editor_link,
);
Alert_Manager::trigger_event( $alert_code, $variables );
remove_action( 'wpforms_pre_delete', array( __CLASS__, 'event_entry_deleted' ), 10, 1 );
}
/**
* Trigger event when entry is modified.
*
* @param array $form_data - Form data.
* @param int $response - Response.
* @param array $updated_fields - New fields.
* @param object $entry - Entry data.
* @return void
*
* @since 4.6.0
*/
public static function event_entry_modified( $form_data, $response, $updated_fields, $entry ) {
$alert_code = 5507;
$fields = self::json_decode_encode( $entry->fields, false, true );
foreach ( $updated_fields as $updated_field ) {
$modified_value = array( array_search( $updated_field['name'], array_column( $fields, 'name', 'value' ), true ) );
$editor_link = esc_url(
add_query_arg(
array(
'view' => 'edit',
'entry_id' => $entry->entry_id,
),
admin_url( 'admin.php?page=wpforms-entries' )
)
);
if ( isset( $updated_field['name'] ) ) {
$variables = array(
'entry_id' => $entry->entry_id,
'form_name' => $form_data['settings']['form_title'],
'field_name' => $updated_field['name'],
'old_value' => implode( $modified_value ),
'new_value' => $updated_field['value'],
'EditorLinkEntry' => $editor_link,
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
}
}
/**
* Trigger event when settings are update.
*
* @param string $option_name - Option being updated.
* @param array $old_value - Previous value.
* @param array $value - Updated value.
* @return void
*
* @since 4.6.0
*/
public static function event_settings_updated( $option_name, $old_value, $value ) {
if ( $value !== $old_value ) {
if ( ! is_array( $old_value ) || ! is_array( $value ) ) {
return;
}
global $wpdb;
// For access settings, we need to check its the correct thing updateing.
if ( $wpdb->prefix . 'user_roles' === $option_name ) {
// Gather role names as we need them later.
$roles = wp_roles()->get_names();
// Array of possible capabilities which wpforms can add/remove from a role.
$wpforms_caps = array( 'wpforms_create_forms', 'wpforms_view_own_forms', 'wpforms_view_others_forms', 'wpforms_edit_own_forms', 'wpforms_edit_others_forms', 'wpforms_delete_own_forms', 'wpforms_delete_others_forms', 'wpforms_view_entries_own_forms', 'wpforms_view_entries_others_forms', 'wpforms_edit_entries_own_forms', 'wpforms_edit_entries_others_forms', 'wpforms_delete_entries_own_forms', 'wpforms_delete_entries_others_forms' );
// Create empty arrays to be filled later.
$updated_new = array();
$updated_old = array();
$caps = array();
if ( \class_exists( 'WPForms\Pro\Access\Capabilities' ) ) {
$w_cap = new Capabilities();
$caps = $w_cap->get_caps();
}
// Loop through each availble role and build a simple array of the available
// wpforms capabilities, assiging applicable roles to each as we find them.
foreach ( $roles as $role_index_name => $role_label ) {
// Create array of current values.
if ( isset( $value[ $role_index_name ] ) ) {
foreach ( $wpforms_caps as $capability ) {
if ( self::array_key_exists_recursive( $capability, $value[ $role_index_name ] ) ) {
$role = $value[ $role_index_name ]['name'];
if ( ! isset( $updated_new[ $capability ]['roles'] ) ) {
$updated_new[ $capability ]['roles'] = array();
}
$updated_new[ $capability ]['roles'] +=
array( $role => $role );
}
// Fill up array with capability anyway, even if its blank.
if ( ! isset( $updated_new[ $capability ] ) ) {
$updated_new[ $capability ] = array(
'roles' => array(),
);
}
}
}
// Create array of old values for comparison.
if ( isset( $old_value[ $role_index_name ] ) ) {
foreach ( $wpforms_caps as $capability ) {
if ( self::array_key_exists_recursive( $capability, $old_value[ $role_index_name ] ) ) {
$role = $value[ $role_index_name ]['name'];
if ( ! isset( $updated_old[ $capability ]['roles'] ) ) {
$updated_old[ $capability ]['roles'] = array();
}
$updated_old[ $capability ]['roles'] +=
array( $role => $role );
}
// Fill up array with capability anyway, even if its blank.
if ( ! isset( $updated_old[ $capability ] ) ) {
$updated_old[ $capability ] = array(
'roles' => array(),
);
}
}
}
}
// Detect changes for each wpforms capability and fire off 5508 if a change is found.
foreach ( $wpforms_caps as $wpforms_capability ) {
// Compare old and new to see if something has been tinkered with.
if ( isset( $updated_new[ $wpforms_capability ] ) && $updated_new[ $wpforms_capability ] !== $updated_old[ $wpforms_capability ] ) {
$alert_code = 5508;
// Tidy up name for event.
$setting_name = ucwords( str_replace( '_', ' ', str_replace( 'wpforms', '', $wpforms_capability ) ) );
// Determine the type of setting thats been changed.
if ( strpos( $wpforms_capability, 'own' ) !== false ) {
$setting_type = esc_html__( 'Own', 'wp-security-audit-log' );
} elseif ( strpos( $wpforms_capability, 'other' ) !== false ) {
$setting_type = esc_html__( 'Other', 'wp-security-audit-log' );
} else {
$setting_type = esc_html__( 'N/A', 'wp-security-audit-log' );
}
if ( ! empty( $caps ) && isset( $caps[ $wpforms_capability ] ) ) {
$setting_name = $caps[ $wpforms_capability ];
}
// Setup event variables using above.
$variables = array(
'setting_name' => $setting_name,
'setting_type' => $setting_type,
'old_value' => implode( ', ', $updated_old[ $wpforms_capability ]['roles'] ),
'new_value' => implode( ', ', $updated_new[ $wpforms_capability ]['roles'] ),
);
// Fire off 5508.
Alert_Manager::trigger_event( $alert_code, $variables );
}
}
}
// Event 5509 (Change of currency).
if ( 'wpforms_settings' === $option_name && isset( $value['currency'] ) && function_exists( 'wpforms_get_currencies' ) ) {
$wp_forms_currencies = wpforms_get_currencies();
if ( isset( $old_value['currency'] ) ) {
$old_value = $wp_forms_currencies[ $old_value['currency'] ]['name'] . ' (' . $old_value['currency'] . ')';
} else {
$old_value = null;
}
$alert_code = 5509;
$variables = array(
'old_value' => $old_value,
'new_value' => $wp_forms_currencies[ $value['currency'] ]['name'] . ' (' . $value['currency'] . ')',
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
// Event 5510 (Integration enabled/disabled).
if ( 'wpforms_providers' === $option_name ) {
$providers = array();
if ( function_exists( 'wpforms_get_providers_available' ) ) {
$providers = \array_keys( wpforms_get_providers_available() );
}
foreach ( $providers as $provider ) {
if ( isset( $value[ $provider ] ) ) {
if ( ! empty( $value[ $provider ] ) && empty( $old_value[ $provider ] ) ) {
$event_type = 'added';
$connection_label = array_column( $value[ $provider ], 'label' );
} else {
$event_type = 'deleted';
$connection_label = array_column( $old_value[ $provider ], 'label' );
}
// Tidy labels up.
if ( 'mailchimpv3' === $provider ) {
$provider = esc_html__( 'Mailchimp', 'wp-security-audit-log' );
} elseif ( 'getresponse' === $provider ) {
$provider = esc_html__( 'GetResponse', 'wp-security-audit-log' );
}
$alert_code = 5510;
$connection_name = ! empty( $connection_label ) ? $connection_label[0] : null;
$variables = array(
'EventType' => $event_type,
'service_name' => ucwords( str_replace( '-', ' ', $provider ) ),
'connection_name' => $connection_name,
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
}
}
}
}
/**
* Detect initial changes to WPforms option. These typically use the "added_option"
* hook as no previous option is present to update.
*
* @param string $option_name Name of option being changed.
* @param array $value New values.
*
* @since 4.6.0
*/
public static function event_added_option( $option_name, $value ) {
// Event 5509 (Initial change of currency).
if ( 'wpforms_settings' === $option_name && isset( $value['currency'] ) && function_exists( 'wpforms_get_currencies' ) ) {
$wp_forms_currencies = wpforms_get_currencies();
$alert_code = 5509;
$variables = array(
'old_value' => $wp_forms_currencies['USD']['name'] . ' (USD)',
'new_value' => $wp_forms_currencies[ $value['currency'] ]['name'] . ' (' . $value['currency'] . ')',
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
}
/**
* Trigger event when addon is activated.
*
* @param string $plugin - Activated addon name.
* @return void
*
* @since 4.6.0
*/
public static function addon_plugin_activated( $plugin ) {
$event_type = 'activated';
self::generate_addon_event( $plugin, $event_type );
}
/**
* Trigger event when addon is deactivated.
*
* @param string $plugin - Deactivated addon name.
* @return void
*
* @since 4.6.0
*/
public static function addon_plugin_deactivated( $plugin ) {
$event_type = 'deactivated';
self::generate_addon_event( $plugin, $event_type );
}
/**
* Trigger event when addon is installed.
*
* @param string $plugin - Installed addon name.
* @return void
*
* @since 4.6.0
*/
public static function addon_plugin_installed( $plugin ) {
$event_type = 'installed';
self::generate_addon_event( $plugin, $event_type );
}
/**
* Trigger addon alert.
*
* @param string $plugin - Plugin being installed etc.
* @param string $event_type - Event type.
* @return void
*
* @since 4.6.0
*/
public static function generate_addon_event( $plugin, $event_type ) {
$alert_code = 5511;
$tidy_plugin_name = preg_replace( '/\.[^.]+$/', '', basename( $plugin ) );
$variables = array(
'EventType' => $event_type,
'addon_name' => str_replace( 'Wpforms', 'WPForms', ucwords( str_replace( '-', ' ', $tidy_plugin_name ) ) ),
);
Alert_Manager::trigger_event( $alert_code, $variables );
}
/**
* Method: This function make sures that alert 5501
* has not been triggered before triggering.
*
* @return bool
*
* @since 4.6.0
*/
public static function check_other_changes() {
if ( Alert_Manager::will_or_has_triggered( 5501 ) ) {
return false;
}
return true;
}
/**
* Method: This function make sures that alert 5500
* has not been triggered before triggering.
*
* @return bool
*
* @since 4.6.0
*/
public static function must_not_be_new_form() {
if ( Alert_Manager::will_or_has_triggered( 5500 ) ) {
return false;
}
return true;
}
/**
* Method: This function make sures that alert 5502
* has not been triggered before triggering
*
* @return bool
*
* @since 4.6.0
*/
public static function check_if_duplicate() {
if ( Alert_Manager::will_or_has_triggered( 5502 ) ) {
return false;
}
return true;
}
/**
* Extract email address from a string.
*
* @param string $string - String to search.
*
* @return string
*
* @since 4.6.0
*/
private static function extract_emails( $string ) {
// This regular expression extracts all emails from a string.
$regexp = '/([a-z0-9_\.\-])+\@(([a-z0-9\-])+\.)+([a-z0-9]{2,4})+/i';
preg_match( $regexp, $string, $m );
return isset( $m[0] ) ? $m[0] : array();
}
/**
* Checks multi-dimensional arrays for a key.
*
* @param string $key - Needle..
* @param array $array - Haystack.
* @return array|false;
*
* @since 4.6.0
*/
private static function array_key_exists_recursive( $key, $array ) {
if ( is_array( $array ) && array_key_exists( $key, $array ) ) {
return true;
}
if ( is_array( $array ) ) {
foreach ( $array as $k => $value ) {
if ( is_array( $value ) && self::array_key_exists_recursive( $key, $value ) ) {
return true;
}
}
}
return false;
}
/**
* Return the fields type if it has no provided label to display.
*
* @param array $fields - Fields to check.
* @return string
*
* @since 4.6.0
*/
private static function get_type_if_field_has_no_name( $fields ) {
return ( empty( $fields['label'] ) ) ? sanitize_text_field( $fields['type'] ) : sanitize_text_field( $fields['label'] );
}
/**
* Checks for added, removed or modified items given an old/new array.
*
* @param array $new_array - Newer array to compare.
* @param array $old_array - Older array to compare.
* @return array $result - Array containing changes.
*
* @since 4.6.0
*/
private static function determine_added_removed_and_changed_items( $new_array, $old_array ) {
$result = array(
'added' => array(),
'deleted' => array(),
'modified' => array(),
);
// Compare the 2 arrays and create array of added items.
$compare_added_items = array_diff(
array_map( 'serialize', $new_array ),
array_map( 'serialize', $old_array )
);
$added_items = array_map( 'unserialize', $compare_added_items );
$result['added'] = $added_items;
// Compare the 2 arrays and create array of removed items.
$compare_removed_items = array_diff(
array_map( 'serialize', $old_array ),
array_map( 'serialize', $new_array )
);
$removed_items = array_map( 'unserialize', $compare_removed_items );
$result['deleted'] = $removed_items;
// Compare the 2 arrays and create array of changed.
$compare_changed_items = array_diff_assoc(
array_map( 'serialize', $old_array ),
array_map( 'serialize', $new_array )
);
$changed_items = array_map( 'unserialize', $compare_removed_items );
$result['modified'] = $changed_items;
return $result;
}
/**
* Creates an editor link for a given form_ID.
*
* @param int $post_id - Forms ID.
* @return string $editor_link - URL to edit screen.
*
* @since 4.6.0
*/
private static function create_form_post_editor_link( $post_id ) {
$editor_link = esc_url(
add_query_arg(
array(
'view' => 'fields',
'form_id' => $post_id,
),
admin_url( 'admin.php?page=wpforms-builder' )
)
);
return $editor_link;
}
/**
* Decodes given item, using WP functions where possible.
*
* @param string $item_to_decode - Item to be decoded.
* @param boolean $needs_encoding_first - Do we need to encode first.
* @param boolean $decode_associative - Return associative array from decode.
*
* @return mixed item_to_decode - Decoded string.
*
* @since 4.6.0
*/
private static function json_decode_encode( $item_to_decode, $needs_encoding_first = false, $decode_associative = null ) {
if ( $needs_encoding_first ) {
$item_to_decode = ( function_exists( 'wp_json_encode' ) ) ? wp_json_encode( $item_to_decode ) : json_encode( $item_to_decode );
}
return json_decode( $item_to_decode, $decode_associative );
}
}
}