574 lines
18 KiB
PHP
574 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* Sensor: ACF meta
|
|
*
|
|
* ACF Meta sensor class file.
|
|
*
|
|
* @since 5.0.0
|
|
* @package wsal
|
|
* @subpackage sensors
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace WSAL\WP_Sensors;
|
|
|
|
use WSAL\Controllers\Alert_Manager;
|
|
use WSAL\WP_Sensors\Helpers\ACF_Helper;
|
|
|
|
// Exit if accessed directly.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
if ( ! class_exists( '\WSAL\WP_Sensors\ACF_Sensor' ) ) {
|
|
|
|
/**
|
|
* Sensor for events specific to Advanced Custom Fields plugin.
|
|
*
|
|
* @package wsal
|
|
* @subpackage sensors
|
|
* @since 5.0.0
|
|
*/
|
|
class ACF_Sensor {
|
|
|
|
/**
|
|
* Old post object.
|
|
*
|
|
* @var object
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
private static $old_post;
|
|
|
|
/**
|
|
* Array with the old post terms.
|
|
*
|
|
* @var array
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
private static $old_post_terms;
|
|
|
|
/**
|
|
* Inits the main hooks
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
public static function init() {
|
|
if ( ACF_Helper::is_acf_active() ) {
|
|
\add_action( 'pre_post_update', array( __CLASS__, 'get_before_post_edit_data' ), 10, 2 );
|
|
\add_action( 'save_post', array( __CLASS__, 'acf_save_post' ), 10, 3 );
|
|
\add_action( 'delete_post', array( __CLASS__, 'event_post_deleted' ), 10, 1 );
|
|
\add_action( 'create_term', array( __CLASS__, 'event_category_creation' ), 10, 4 );
|
|
\add_filter( 'wp_update_term_data', array( __CLASS__, 'event_update_term_data' ), 10, 4 );
|
|
\add_action( 'pre_delete_term', array( __CLASS__, 'check_taxonomy_term_deletion' ), 10, 2 );
|
|
\add_action( 'set_object_terms', array( __CLASS__, 'post_terms_changed' ), 10, 4 );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads all the plugin dependencies early.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
public static function early_init() {
|
|
/**
|
|
* Add our filters.
|
|
*/
|
|
\add_filter(
|
|
'wsal_event_objects',
|
|
array( ACF_Helper::class, 'add_custom_event_objects' )
|
|
);
|
|
|
|
\add_filter(
|
|
'wsal_ignored_custom_post_types',
|
|
array( ACF_Helper::class, 'add_custom_ignored_cpt' )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Record events for ACF changes.
|
|
*
|
|
* @param int $post_id - ID of post being saved.
|
|
* @param WP_Post $post - Post object.
|
|
* @param bool $update - Is a post update or not.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
public static function acf_save_post( $post_id, $post, $update ) {
|
|
if ( 'acf-post-type' === $post->post_type || 'acf-taxonomy' === $post->post_type ) {
|
|
$editor_link = array(
|
|
'name' => 'EditorLinkPost',
|
|
'value' => get_edit_post_link( $post_id ),
|
|
);
|
|
|
|
if ( ! $update ) { // phpcs:ignore
|
|
// If we wanted to try an event when an item is duplicated, now would be a good time.
|
|
|
|
} else {
|
|
$old_content_arr = (array) maybe_unserialize( self::$old_post->post_content );
|
|
$new_content_arr = (array) maybe_unserialize( $post->post_content );
|
|
|
|
if ( isset( self::$old_post->post_status ) && 'draft' !== self::$old_post->post_status && 'auto-draft' !== self::$old_post->post_status ) {
|
|
if ( self::$old_post->post_status !== $post->post_status ) {
|
|
if ( 'trash' === $post->post_status ) {
|
|
$event = ( 'acf-post-type' === $post->post_type ) ? 10007 : 10017;
|
|
$event_data = array(
|
|
'PostTypeTitle' => $post->post_title,
|
|
'PostID' => $post->ID,
|
|
'PostStatus' => $post->post_status,
|
|
'EditorLink' => $editor_link['value'],
|
|
);
|
|
Alert_Manager::trigger_event( $event, $event_data );
|
|
} elseif ( 'trash' === self::$old_post->post_status && 'publish' === $post->post_status ) {
|
|
$event = ( 'acf-post-type' === $post->post_type ) ? 10008 : 10018;
|
|
$event_data = array(
|
|
'PostTypeTitle' => $post->post_title,
|
|
'PostID' => $post->ID,
|
|
'PostStatus' => $post->post_status,
|
|
'EditorLink' => $editor_link['value'],
|
|
'PostUrl' => get_permalink( $post->ID ),
|
|
);
|
|
Alert_Manager::trigger_event( $event, $event_data );
|
|
} else {
|
|
$event = ( 'acf-post-type' === $post->post_type ) ? 10001 : 10011;
|
|
$event_data = array(
|
|
'PostTypeTitle' => $post->post_title,
|
|
'PostID' => $post->ID,
|
|
'PostStatus' => $post->post_status,
|
|
'EventType' => ( 'acf-disabled' === $post->post_status ) ? 'deactivated' : 'activated',
|
|
'EditorLink' => $editor_link['value'],
|
|
);
|
|
Alert_Manager::trigger_event( $event, $event_data );
|
|
}
|
|
}
|
|
if ( isset( self::$old_post->post_title ) ) {
|
|
if ( self::$old_post->post_title !== $post->post_title ) {
|
|
$event = ( 'acf-post-type' === $post->post_type ) ? 10002 : 10012;
|
|
$event_data = array(
|
|
'OldPostTypeTitle' => self::$old_post->post_title,
|
|
'PostTypeTitle' => $post->post_title,
|
|
'PostID' => $post->ID,
|
|
'PostStatus' => $post->post_status,
|
|
'EditorLink' => $editor_link['value'],
|
|
);
|
|
Alert_Manager::trigger_event( $event, $event_data );
|
|
}
|
|
}
|
|
} elseif ( ! Alert_Manager::was_triggered_recently( 10000 ) && ! Alert_Manager::was_triggered_recently( 10006 ) && ! Alert_Manager::was_triggered_recently( 10010 ) ) {
|
|
$event = ( 'acf-post-type' === $post->post_type ) ? 10000 : 10010;
|
|
$event_data = array(
|
|
'PostTypeTitle' => $post->post_title,
|
|
'PostID' => $post->ID,
|
|
'PostStatus' => 'publish',
|
|
'EditorLink' => $editor_link['value'],
|
|
);
|
|
Alert_Manager::trigger_event( $event, $event_data );
|
|
}
|
|
|
|
if ( isset( $old_content_arr['labels'] ) ) {
|
|
if ( $old_content_arr['labels']['singular_name'] !== $new_content_arr['labels']['singular_name'] ) {
|
|
$event = ( 'acf-post-type' === $post->post_type ) ? 10003 : 10013;
|
|
$event_data = array(
|
|
'PostTypeTitle' => $post->post_title,
|
|
'PostID' => $post->ID,
|
|
'PostStatus' => $post->post_status,
|
|
'old_label' => $old_content_arr['labels']['singular_name'],
|
|
'new_label' => $new_content_arr['labels']['singular_name'],
|
|
'EditorLink' => $editor_link['value'],
|
|
);
|
|
Alert_Manager::trigger_event( $event, $event_data );
|
|
}
|
|
}
|
|
|
|
if ( isset( $old_content_arr['post_type'] ) ) {
|
|
if ( $old_content_arr['post_type'] !== $new_content_arr['post_type'] ) {
|
|
$event = 10004;
|
|
$event_data = array(
|
|
'PostTypeTitle' => $post->post_title,
|
|
'PostID' => $post->ID,
|
|
'PostStatus' => $post->post_status,
|
|
'old_key' => $old_content_arr['post_type'],
|
|
'new_key' => $new_content_arr['post_type'],
|
|
'EditorLink' => $editor_link['value'],
|
|
);
|
|
Alert_Manager::trigger_event( $event, $event_data );
|
|
}
|
|
}
|
|
|
|
if ( isset( $old_content_arr['taxonomy'] ) ) {
|
|
if ( $old_content_arr['taxonomy'] !== $new_content_arr['taxonomy'] ) {
|
|
$event = 10014;
|
|
$event_data = array(
|
|
'PostTypeTitle' => $post->post_title,
|
|
'PostID' => $post->ID,
|
|
'PostStatus' => $post->post_status,
|
|
'old_key' => $old_content_arr['taxonomy'],
|
|
'new_key' => $new_content_arr['taxonomy'],
|
|
'EditorLink' => $editor_link['value'],
|
|
);
|
|
Alert_Manager::trigger_event( $event, $event_data );
|
|
}
|
|
}
|
|
if ( isset( $old_content_arr['taxonomies'] ) ) {
|
|
if ( $old_content_arr['taxonomies'] !== $new_content_arr['taxonomies'] ) {
|
|
$event = 10015;
|
|
$event_data = array(
|
|
'PostTypeTitle' => $post->post_title,
|
|
'PostID' => $post->ID,
|
|
'PostStatus' => $post->post_status,
|
|
'old_tax' => ( ! empty( $old_content_arr['taxonomies'] ) ) ? implode( ', ', $old_content_arr['taxonomies'] ) : esc_html__( 'Not provided', 'wp-security-audit-log' ),
|
|
'new_tax' => ( ! empty( $new_content_arr['taxonomies'] ) ) ? implode( ', ', $new_content_arr['taxonomies'] ) : esc_html__( 'Not provided', 'wp-security-audit-log' ),
|
|
'EditorLink' => $editor_link['value'],
|
|
);
|
|
Alert_Manager::trigger_event( $event, $event_data );
|
|
}
|
|
}
|
|
|
|
if ( isset( $old_content_arr['object_type'] ) ) {
|
|
if ( $old_content_arr['object_type'] !== $new_content_arr['object_type'] ) {
|
|
$event = 10015;
|
|
$event_data = array(
|
|
'PostTypeTitle' => $post->post_title,
|
|
'PostID' => $post->ID,
|
|
'PostStatus' => $post->post_status,
|
|
'old_tax' => ( ! empty( $old_content_arr['object_type'] ) ) ? implode( ', ', $old_content_arr['object_type'] ) : esc_html__( 'Not provided', 'wp-security-audit-log' ),
|
|
'new_tax' => ( ! empty( $new_content_arr['object_type'] ) ) ? implode( ', ', $new_content_arr['object_type'] ) : esc_html__( 'Not provided', 'wp-security-audit-log' ),
|
|
'EditorLink' => $editor_link['value'],
|
|
);
|
|
Alert_Manager::trigger_event( $event, $event_data );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Post permanently deleted.
|
|
*
|
|
* @param integer $post_id - Post ID.
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
public static function event_post_deleted( $post_id ) {
|
|
// Exclude CPTs from external plugins.
|
|
$post = get_post( $post_id );
|
|
|
|
if ( 'acf-taxonomy' === $post->post_type || 'acf-post-type' === $post->post_type ) {
|
|
$event = ( 'acf-post-type' === $post->post_type ) ? 10009 : 10019;
|
|
$event_data = array(
|
|
'PostTypeTitle' => $post->post_title,
|
|
'PostID' => $post->ID,
|
|
'PostStatus' => $post->post_status,
|
|
);
|
|
Alert_Manager::trigger_event( $event, $event_data );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create link to terms.
|
|
*
|
|
* @param int $tag_id - ID if edited item.
|
|
* @param string $taxonomy - Taxonomy slug.
|
|
*
|
|
* @return string|null Returns link if it can, otherwise null.
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
private static function get_taxonomy_edit_link( $tag_id, $taxonomy = 'post_tag' ) {
|
|
$tag_args = array(
|
|
'taxonomy' => $taxonomy,
|
|
'tag_ID' => $tag_id,
|
|
);
|
|
return ! empty( $tag_id ) ? add_query_arg( $tag_args, \network_admin_url( 'term.php' ) ) : null;
|
|
}
|
|
|
|
/**
|
|
* A new term was created.
|
|
*
|
|
* @param int $term_id - New term ID.
|
|
* @param int $tt_id - Parent taxonomy ID.
|
|
* @param string $taxonomy - Taxonomy slug.
|
|
* @param array $args - Addition args.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
public static function event_category_creation( $term_id, $tt_id, $taxonomy, $args ) {
|
|
$is_acf_term = self::check_if_acf_term( $taxonomy );
|
|
|
|
if ( ! empty( $is_acf_term ) ) {
|
|
$link = self::get_taxonomy_edit_link( $term_id, $taxonomy );
|
|
Alert_Manager::trigger_event(
|
|
10020,
|
|
array(
|
|
'TaxonomyTerm' => $args['name'],
|
|
'slug' => $args['slug'],
|
|
'EditorLink' => $link,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggered when term data is updated.
|
|
*
|
|
* @param array $data Term data to be updated.
|
|
* @param int $term_id Term ID.
|
|
* @param string $taxonomy Taxonomy slug.
|
|
* @param array $args Arguments passed to wp_update_term().
|
|
*
|
|
* @return array $data We are only observing, return original data.
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
public static function event_update_term_data( $data, $term_id, $taxonomy, $args ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
|
$new_name = isset( $data['name'] ) ? $data['name'] : false;
|
|
$new_slug = isset( $data['slug'] ) ? $data['slug'] : false;
|
|
|
|
$is_acf_term = self::check_if_acf_term( $taxonomy );
|
|
|
|
// Get old data.
|
|
$term = get_term( $term_id, $taxonomy );
|
|
$old_name = $term->name;
|
|
$old_slug = $term->slug;
|
|
|
|
if ( ! empty( $is_acf_term ) ) {
|
|
$link = self::get_taxonomy_edit_link( $term_id, $taxonomy );
|
|
if ( $old_name !== $new_name ) {
|
|
Alert_Manager::trigger_event(
|
|
10021,
|
|
array(
|
|
'old_name' => $old_name,
|
|
'new_name' => $new_name,
|
|
'EditorLink' => $link,
|
|
'slug' => $new_slug,
|
|
)
|
|
);
|
|
}
|
|
if ( $old_slug !== $new_slug ) {
|
|
Alert_Manager::trigger_event(
|
|
10023,
|
|
array(
|
|
'TaxonomyTerm' => $term->name,
|
|
'old_slug' => $old_slug,
|
|
'new_slug' => $new_slug,
|
|
'EditorLink' => $link,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
return $data; // Return data for the filter.
|
|
}
|
|
|
|
/**
|
|
* Taxonomy Terms Deleted Events.
|
|
*
|
|
* @param integer $term_id - Term ID.
|
|
* @param string $taxonomy - Taxonomy Name.
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
public static function check_taxonomy_term_deletion( $term_id, $taxonomy ) {
|
|
$is_acf_term = self::check_if_acf_term( $taxonomy );
|
|
if ( ! empty( $is_acf_term ) ) {
|
|
$term = get_term( $term_id, $taxonomy );
|
|
Alert_Manager::trigger_event(
|
|
10022,
|
|
array(
|
|
'TermName' => $term->name,
|
|
'slug' => $term->slug,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if post terms changed via Gutenberg.
|
|
*
|
|
* @param int $post_id - Post ID.
|
|
* @param array $terms - Array of terms.
|
|
* @param array $tt_ids - Array of taxonomy term ids.
|
|
* @param string $taxonomy - Taxonomy slug.
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
public static function post_terms_changed( $post_id, $terms, $tt_ids, $taxonomy ) {
|
|
$post = get_post( $post_id );
|
|
|
|
if ( is_wp_error( $post ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( null === $post ) {
|
|
return;
|
|
}
|
|
|
|
if ( 'auto-draft' === $post->post_status ) {
|
|
return;
|
|
}
|
|
|
|
// Support for Admin Columns Pro plugin and its add-on.
|
|
if ( isset( $_POST['_ajax_nonce'] ) && ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_ajax_nonce'] ) ), 'ac-ajax' ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( isset( $_POST['action'] ) && 'acp_editing_single_request' === sanitize_text_field( wp_unslash( $_POST['action'] ) ) ) {
|
|
return;
|
|
}
|
|
$is_acf_term = self::check_if_acf_term( $taxonomy );
|
|
|
|
if ( ! empty( $is_acf_term ) ) {
|
|
self::check_terms_change( self::$old_post_terms, self::get_post_terms( $post_id ), $post, $taxonomy );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check for changes to a posts terms.
|
|
*
|
|
* @param array $old_tags - Previous temrs list.
|
|
* @param array $new_tags - Incoming terms.
|
|
* @param \WP_Post $post - Post.
|
|
* @param string $taxonomy - Taxonomy slug.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
private static function check_terms_change( $old_tags, $new_tags, $post, $taxonomy ) {
|
|
$extracted_old_tags = array();
|
|
$extracted_new_tags = array();
|
|
if ( isset( $old_tags ) && \is_array( $old_tags ) && isset( $old_tags[ $taxonomy ] ) ) {
|
|
$extracted_old_tags = $old_tags[ $taxonomy ];
|
|
}
|
|
if ( isset( $new_tags ) && \is_array( $new_tags ) && isset( $new_tags[ $taxonomy ] ) ) {
|
|
$extracted_new_tags = $new_tags[ $taxonomy ];
|
|
}
|
|
|
|
$intersection = array_intersect_assoc( array_map( 'serialize', $extracted_old_tags ), array_map( 'serialize', $extracted_new_tags ) );
|
|
if ( count( $intersection ) === count( $extracted_old_tags ) && count( $extracted_old_tags ) === count( $extracted_new_tags ) ) {
|
|
// No change, let's leave.
|
|
return;
|
|
}
|
|
|
|
self::report_terms_change_event( $extracted_old_tags, $extracted_new_tags, $post, $taxonomy );
|
|
}
|
|
|
|
/**
|
|
* Report changes to ACF terms.
|
|
*
|
|
* @param array $old_tags - Previous temrs list.
|
|
* @param array $new_tags - Incoming terms.
|
|
* @param \WP_Post $post - Post.
|
|
* @param string $taxonomy - Taxonomy slug.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
private static function report_terms_change_event( $old_tags, $new_tags, $post, $taxonomy ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
|
if ( $old_tags === $new_tags ) {
|
|
return;
|
|
}
|
|
|
|
$editor_link = array(
|
|
'name' => 'EditorLink',
|
|
'value' => get_edit_post_link( $post->ID ),
|
|
);
|
|
|
|
Alert_Manager::trigger_event(
|
|
10024,
|
|
array(
|
|
'PostID' => $post->ID,
|
|
'PostTitle' => $post->post_title,
|
|
'old_terms' => ! empty( $old_tags ) ? implode( ', ', self::create_term_list( $old_tags ) ) : esc_html__( 'none', 'wp-security-audit-log' ),
|
|
'new_terms' => ! empty( $new_tags ) ? implode( ', ', self::create_term_list( $new_tags ) ) : esc_html__( 'none', 'wp-security-audit-log' ),
|
|
'EditorLink' => $editor_link['value'],
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create neat list from term array.
|
|
*
|
|
* @param array $terms - Incoming terms to tidy.
|
|
* @return array $return_arr - Neat list.
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
private static function create_term_list( $terms ) {
|
|
$return_arr = array();
|
|
foreach ( $terms as $item ) {
|
|
$return_arr[] = $item['name'];
|
|
}
|
|
return $return_arr;
|
|
}
|
|
|
|
/**
|
|
* Get a copy of a posts meta data prior to update for later comparison.
|
|
*
|
|
* @param int $post_id - Post ID.
|
|
* @return void
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
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 ) {
|
|
$access_array = array();
|
|
self::$old_post = $post;
|
|
self::$old_post_terms = self::get_post_terms( $post_id );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a given taxomony is one that was created via ACF.
|
|
*
|
|
* @param string $taxonomy - Taxonomy slug to look for.
|
|
* @return bool|array $acf_tax_search - If found, return item, otherwise false.
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
public static function check_if_acf_term( $taxonomy ) {
|
|
global $wpdb;
|
|
$acf_tax_search = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s AND post_name LIKE 'taxonomy%'", '%' . $taxonomy . '%' ), ARRAY_A ); // phpcs:ignore
|
|
return empty( $acf_tax_search ) ? false : $acf_tax_search;
|
|
}
|
|
|
|
/**
|
|
* Get ACF taxomonies for a post, if applicable
|
|
*
|
|
* @param int $post_id - Post ID.
|
|
* @return array $found_terms - Applicable terms.
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
private static function get_post_terms( $post_id ) {
|
|
$post_taxonomies = get_post_taxonomies( $post_id );
|
|
$found_terms = array();
|
|
foreach ( $post_taxonomies as $taxonomy ) {
|
|
$check = self::check_if_acf_term( $taxonomy );
|
|
if ( ! empty( $check ) ) {
|
|
$possible_terms = wp_get_post_terms( $post_id, $taxonomy );
|
|
if ( ! empty( $possible_terms ) ) {
|
|
$found_terms[ $taxonomy ] = json_decode( wp_json_encode( $possible_terms ), true );
|
|
}
|
|
}
|
|
}
|
|
return $found_terms;
|
|
}
|
|
}
|
|
}
|