Files
SIGE-WEB-snapshot/wp-content/plugins/wp-security-audit-log/classes/Entities/Fields/class-base-fields.php

481 lines
15 KiB
PHP

<?php
/**
* Extension: Base fields.
*
* @since 5.1.0
*
* @package wsal
* @subpackage entities
*/
declare(strict_types=1);
namespace WSAL\Entities;
use WSAL\MainWP\MainWP_Addon;
use WSAL\MainWP\MainWP_Helper;
use WSAL\Entities\Metadata_Entity;
use WSAL\Entities\Occurrences_Entity;
use WSAL\Helpers\DateTime_Formatter_Helper;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( '\WSAL\Entities\Base_Fields' ) ) {
/**
* Provides fields searching functionality.
*
* @since 5.0.0
*/
class Base_Fields {
/**
* Filed must have:
* - unique name of field (no spaces)
* - DB relation (which DB field represents the field)
* - type
* - validation
*/
/**
* Mapper method - maps all the fields for searching to the corresponding fields (and how to extract value if they are not directly callable from the occurrence table)
*
* @return array
*
* @since 5.0.0
*/
public static function prepare_all_fields(): array {
// Collects the default table fields.
$fields = Occurrences_Entity::get_fields();
$table_meta = Metadata_Entity::get_table_name();
/**
* Logic for searching in the meta table for some values.
*
* @param sql_prefix - The SQL prefix to search for given values
* @param sub_sql_string - The SQL part of the query used to search for specific values (how)
* @param sql_suffix - The SQL suffix for the search query
* @param column_name - Which column from the occurrences table to map the results to
*/
$fields_subs = array(
'post_title' => array(
'sql_prefix' => 'SELECT occurrence_id FROM ' . $table_meta . ' as meta WHERE meta.name=\'PostTitle\' AND ( ',
'sub_sql_string' => "( (meta.value LIKE '%s') > 0 )",
'sql_suffix' => ' )',
'column_name' => 'occurrence_id',
),
);
/**
* All the user fields logic for collecting
* That differs because the users can be searched using mails, names, roles etc...
*
* With this we are mapping the logic for extraction the user IDs using different user searc criteria
*
* @param call - Which method (and class) to call with the parameters.
* @param field_name - The name of the field to extract data from.
* @param in_table - Marks what to use for searching in the occurrences table (they are currently the same)
*/
$user_fields = array(
'user_first_name' => array(
'call' => array( self::class, 'users_search' ),
'field_name' => 'first_name',
'in_table' => array(
'user_id' => 'ID',
'username' => 'user_login',
),
),
'user_last_name' => array(
'call' => array( self::class, 'users_search' ),
'field_name' => 'last_name',
'in_table' => array(
'user_id' => 'ID',
'username' => 'user_login',
),
),
'user_email' => array(
'call' => array( self::class, 'users_search' ),
'field_name' => 'user_email',
'in_table' => array(
'user_id' => 'ID',
'username' => 'user_login',
),
),
'user_role' => array(
'call' => array( self::class, 'users_search' ),
'extract' => 'role',
'in_table' => array(
'user_id' => 'ID',
'username' => 'user_login',
),
),
'user_id' => array(
'call' => array( self::class, 'users_search' ),
'field_name' => 'ID',
'in_table' => array(
'user_id' => 'ID',
'username' => 'user_login',
),
),
);
$fields_aliases = array(
'post_title' => array( 'post_name' ),
);
foreach ( array_keys( $fields_subs ) as $key ) {
if ( isset( $fields_aliases[ $key ] ) ) {
foreach ( $fields_aliases[ $key ] as $alias ) {
$fields_subs[ $alias ] = $fields_subs[ $key ];
}
}
}
// Dates.
$dates = array(
'start_date' => 'date',
'end_date' => 'date',
);
return \array_merge( $fields_subs, $fields, $dates, $user_fields );
}
/**
* Parses the given search string (human readable) and returns the WHERE SQL statement based on the given string logic. To construct this use "field_name" followed by ":" and then the value (no spaces are supported) if there is a space in the string use '"'. For exclusions use "-" before the field name.
*
* @param string $search_string - Example: alert_id:1000 post_name:"something" user_id:1 .
*
* @return string
*
* @since 5.0.0
*/
public static function string_to_search( string $search_string ): string {
/*
* alert_id:1000 post_name:"something" user_id:1
*/
// There is nothing to parse - bounce.
if ( ! \strpos( $search_string, ':' ) ) {
return $search_string;
}
$fields_values = array();
$where_sql = '';
$occurrences_ids_to_search = array();
$occurrences_ids_to_exclude = array();
// Remove trailing and leading spaces.
$string = trim( $search_string );
$raw_fields = \explode( ' ', $string );
foreach ( $raw_fields as $field ) {
if ( empty( $field ) || ! \strpos( $field, ':' ) ) {
continue;
}
list($field_key, $field_value) = \explode( ':', trim( $field ) );
$exc_flag = false;
if ( false !== \strpos( $field_key, '-' ) ) {
$exc_flag = true;
$field_key = ltrim( $field_key, $field_key[0] );
}
if ( '' !== trim( $field_value ) ) {
if ( isset( $fields_values[ $field_key ] ) ) {
if ( $exc_flag ) {
$fields_values[ $field_key ]['exc'][] = $field_value;
} else {
$fields_values[ $field_key ][] = $field_value;
}
} else {
$fields_values[ $field_key ] = array();
if ( $exc_flag ) {
$fields_values[ $field_key ]['exc'][] = $field_value;
} else {
$fields_values[ $field_key ][] = $field_value;
}
}
} else {
continue;
}
}
$get_sub_fields = self::prepare_all_fields();
foreach ( $fields_values as $filed_name => $filed_values ) {
if ( isset( $get_sub_fields[ $filed_name ] ) && isset( $get_sub_fields[ $filed_name ]['sql_prefix'] ) ) {
self::meta_search( $get_sub_fields[ $filed_name ], $filed_values, $occurrences_ids_to_search, $occurrences_ids_to_exclude );
}
if ( isset( $get_sub_fields[ $filed_name ] ) && isset( $get_sub_fields[ $filed_name ]['call'] ) ) {
$where_sql .= \call_user_func( $get_sub_fields[ $filed_name ]['call'], $get_sub_fields[ $filed_name ], $filed_values ) . ' AND ';
}
if ( isset( $get_sub_fields[ $filed_name ] ) && ( ! isset( $get_sub_fields[ $filed_name ]['call'] ) && ! isset( $get_sub_fields[ $filed_name ]['sql_prefix'] ) ) ) {
$where_sql .= self::direct_field_call( (string) $filed_name, $filed_values ) . ' AND ';
}
}
$where_sql = \rtrim( $where_sql, ' AND ' );
return $where_sql;
}
/**
* Special class method for searching for users based on different criteria - user first name, user roles, etc...
*
* @param array $search_keys - Keys (from field mapping logic @see prepare_all_fields method) giving the logic of how to search for user based on specific field.
* @param array $search_values - Values collected from the search string to search for.
*
* @return string
*
* @since 5.0.0
*/
public static function users_search( array $search_keys, array $search_values ) {
$exclude = false;
if ( isset( $search_values['exc'] ) ) {
$exclude = true;
$search_user_values = $search_values['exc'];
} else {
$search_user_values = $search_values;
}
if ( isset( $search_keys['field_name'] ) ) {
$users = array();
$exclude = false;
if ( isset( $search_values['exc'] ) ) {
$exclude = true;
$search_user_values = $search_values['exc'];
} else {
$search_user_values = $search_values;
}
$args = array(
'blog_id' => 0,
'meta_query' => array(
array(
'key' => $search_keys['field_name'],
'values' => $search_user_values,
'compare' => 'REGEXP',
),
),
);
if ( 'ID' === $search_keys['field_name'] ) {
unset( $args['meta_query'] );
$args['include'] = $search_user_values;
}
if ( 'user_email' === $search_keys['field_name'] ) {
global $wpdb;
$search_str = '';
$search_vals = array();
foreach ( $search_user_values as $value ) {
$search_str .= ' ' . $search_keys['field_name'] . ' LIKE %s OR ';
$search_vals[] = $value;
}
$search_str = \rtrim( $search_str, ' OR ' );
$users_array = $wpdb->get_results(
$wpdb->prepare( 'SELECT * FROM ' . $wpdb->users . ' WHERE ' . $search_str, $search_vals ) // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
); // This will return list of users with matching email domain.
} else {
$users_array = \get_users( $args );
}
if ( MainWP_Addon::check_mainwp_plugin_active() ) {
$mainwp_users = MainWP_Helper::find_users_by( array( $search_keys['field_name'] ), $search_user_values, false );
$users_array = array_merge( $users_array, $mainwp_users );
}
foreach ( $users_array as $user ) {
if ( isset( $search_keys['in_table'] ) ) {
$arr = array();
foreach ( $search_keys['in_table'] as $field_name => $object_name ) {
$arr[ $field_name ] = "'" . $user->$object_name . "'";
}
$users[] = $arr;
} else {
$users[] = array( 'user_id' => $user->ID );
}
}
$from_string_array = array();
if ( ! empty( $users ) ) {
foreach ( $users as $user ) {
foreach ( $user as $field_name => $field_value ) {
if ( ! empty( $field_value ) ) {
$from_string_array[ $field_name ][] = $field_value;
}
}
}
}
$users_sql = '';
foreach ( $from_string_array as $field_name => $field_values ) {
$users_sql .= ( ( true === $exclude ) ? ' ( ' . $field_name . ' IS NULL OR ' : '' ) . $field_name . ( ( true === $exclude ) ? ' NOT' : '' ) . ' IN ( ' . \implode( ',', $field_values ) . ( ( true === $exclude ) ? ' ) ) AND ' : ' ) OR ' );
}
if ( true === $exclude ) {
$users_sql = ' ( ' . \rtrim( $users_sql, ' AND ' ) . ' ) ';
} else {
$users_sql = ' ( ' . \rtrim( $users_sql, ' OR ' ) . ' ) ';
}
if ( true === $exclude ) {
unset( $search_values['exc'] );
if ( ! empty( $search_values ) ) {
$users_sql .= ' AND ' . self::users_search( $search_keys, $search_values );
}
}
return $users_sql;
} elseif ( isset( $search_keys['extract'] ) ) {
if ( 'role' === $search_keys['extract'] ) {
$users_sql = '';
foreach ( $search_user_values as $field_value ) {
$users_sql .= ( ( true === $exclude ) ? '!' : '' ) . " FIND_IN_SET('" . $field_value . "',`user_roles`) " . ( ( true === $exclude ) ? ' AND ' : ' OR ' );
}
if ( true === $exclude ) {
$users_sql = ' ( ' . \rtrim( $users_sql, ' AND ' ) . ' ) ';
} else {
$users_sql = ' ( ' . \rtrim( $users_sql, ' OR ' ) . ' ) ';
}
if ( true === $exclude ) {
unset( $search_values['exc'] );
if ( ! empty( $search_values ) ) {
$users_sql .= ' AND ' . self::users_search( $search_keys, $search_values );
}
}
return $users_sql;
}
}
}
/**
* Special class method used for searching in the meta table for the given filed/value based on the mapping provided in the @see prepare_all_fields
*
* @param array $search_keys - Keys (from field mapping logic @see prepare_all_fields method) giving the logic of how to search for user based on specific field.
* @param array $search_values - Values collected from the search string to search for.
* @param array $occurrences_ids_to_search - Array with the collected IDs to search for so far.
* @param array $occurrences_ids_to_exclude - Array with the collected IDs to exclude from search so far.
*
* @return void
*
* @since 5.0.0
*/
public static function meta_search( array $search_keys, array $search_values, array &$occurrences_ids_to_search, array &$occurrences_ids_to_exclude ) {
$sub_sql = $search_keys['sql_prefix'];
$exclude = false;
if ( isset( $search_values['exc'] ) ) {
$exclude = true;
$search_user_values = $search_values['exc'];
} else {
$search_user_values = $search_values;
}
foreach ( $search_user_values as $value ) {
$sub_sql .= $search_keys['sub_sql_string'] . ' OR ';
}
$sub_sql = \rtrim( $sub_sql, ' OR ' );
$sub_sql .= $search_keys['sql_suffix'];
$_wpdb = Metadata_Entity::get_connection();
$sql = $_wpdb->prepare( $sub_sql, $search_user_values );
$results = $_wpdb->get_results( $sql, ARRAY_A );
if ( ! empty( $results ) ) {
if ( true === $exclude ) {
$occurrences_ids_to_exclude = \array_merge( $occurrences_ids_to_exclude, array_column( $results, $search_keys['column_name'] ) );
} else {
$occurrences_ids_to_search = \array_merge( $occurrences_ids_to_search, array_column( $results, $search_keys['column_name'] ) );
}
}
if ( true === $exclude ) {
unset( $search_values['exc'] );
if ( ! empty( $search_values ) ) {
self::meta_search( $search_keys, $search_values, $occurrences_ids_to_search, $occurrences_ids_to_exclude );
}
}
}
/**
* Falls here if the filed / value pair to search for is directly present in the occurrences table - meaning that there is no need to map, or extract additional data.
*
* @param string $field_name - The name of the field to search for.
* @param array $search_values - The search values provided.
*
* @return string
*
* @since 5.0.0
*/
public static function direct_field_call( string $field_name, array $search_values ) {
$exclude = false;
$field_sql = '';
if ( \in_array( $field_name, array( 'start_date', 'end_date' ), true ) ) {
if ( 'start_date' === $field_name ) {
$start_datetime = \DateTime::createFromFormat( 'Y-m-d H:i:s', $search_values[0] . ' 00:00:00' );
$_start_timestamp = $start_datetime->format( 'U' ) + ( DateTime_Formatter_Helper::get_time_zone_offset() ) * -1;
$field_sql .= " created_on >= {$_start_timestamp} ";
} else {
$end_datetime = \DateTime::createFromFormat( 'Y-m-d H:i:s', $search_values[0] . ' 23:59:59' );
$_end_timestamp = $end_datetime->format( 'U' ) + ( DateTime_Formatter_Helper::get_time_zone_offset() ) * -1;
$field_sql .= " created_on <= {$_end_timestamp} ";
}
} else {
if ( isset( $search_values['exc'] ) ) {
$exclude = true;
$search_field_values = $search_values['exc'];
} else {
$search_field_values = $search_values;
}
foreach ( $search_field_values as $field_values ) {
$field_sql .= ( ( true === $exclude ) ? ' ( ' . $field_name . ' IS NULL OR ' : '' ) . $field_name . ( ( true === $exclude ) ? ' NOT' : '' ) . ' IN ( ' . "'" . \implode( ",'", (array) $field_values ) . "'" . ( ( true === $exclude ) ? ' ) ) AND ' : ' ) OR ' );
}
if ( true === $exclude ) {
$field_sql = ' ( ' . \rtrim( $field_sql, ' AND ' ) . ' ) ';
} else {
$field_sql = ' ( ' . \rtrim( $field_sql, ' OR ' ) . ' ) ';
}
if ( true === $exclude ) {
unset( $search_values['exc'] );
if ( ! empty( $search_values ) ) {
$field_sql .= ' AND ' . self::direct_field_call( $field_name, $search_values );
}
}
}
return $field_sql;
}
}
}