Files
SIGE-WEB-snapshot/wp-content/plugins/wp-security-audit-log/classes/Select2/class-select2-wpws.php

470 lines
12 KiB
PHP

<?php
/**
* Library core class file.
*
* @package WordPress
* @subpackage s24wp
*/
namespace Tools;
use MLS\Reset_Passwords;
use WSAL\Helpers\Settings_Helper;
use WSAL\Helpers\WP_Helper;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( '\Tools\Select2_WPWS' ) ) {
/**
* Core class of the library. The name stands for "Select 2 for WordPress".
*
* @since 4.5.1
*/
class Select2_WPWS {
/**
* Action parameter for the AJAX call.
*
* @var string
*
* @since 4.5.1
*/
private static $ajax_action = 'wpws_s24wp';
/**
* Prefix for the autogenerated HTML field IDs.
*
* @var string
*
* @since 4.5.1
*/
private static $id_prefix = 'wpw-select2-';
/**
* Number of UI controls generated. Used to ensure unique HTML element IDs.
*
* @var int
*
* @since 4.5.1
*/
private static $id_counter = 0;
/**
* True once the scripts were queued. We only want to include them once.
*
* @var bool
*
* @since 4.5.1
*/
private static $scripts_queued = false;
/**
* Full URL to the root folder of the library. This depends on a plugin or theme where the library is used.
*
* @var string
*
* @since 4.5.1
*/
private static $base_url;
/**
* Keeps class status
*
* @var bool
*
* @since 5.2.2
*/
private static $initiated = \false;
/**
* Initializes the library with given base URL.
*
* @param string $lib_base_url URL pointing to the root of the library.
*
* @since 4.5.1
*/
public static function init( $lib_base_url ) {
if ( ! self::$initiated ) {
self::$base_url = $lib_base_url;
if ( ! has_action( 'wp_ajax_' . self::$ajax_action, array( __CLASS__, 'handle_ajax_call' ) ) ) {
add_action( 'wp_ajax_' . self::$ajax_action, array( __CLASS__, 'handle_ajax_call' ) );
}
self::$initiated = true;
}
}
/**
* Handles AJAX requests from the autocomplete controls.
*
* @since 4.5.1
*/
public static function handle_ajax_call() {
// Verify nonce.
if ( ! isset( $_REQUEST['nonce'] ) || false === wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_REQUEST['nonce'] ) ), 'wsal-select2-check' ) ) {
wp_send_json_error( esc_html__( 'Insecure request.', 'wp-security-audit-log' ) );
}
if ( ! Settings_Helper::current_user_can( 'edit' ) ) {
die( 'Access Denied.' );
}
// Check the 'entity' parameter.
if ( ! array_key_exists( 'entity', $_REQUEST ) ) {
wp_send_json_error( 'Data type not defined.' );
}
if ( ! array_key_exists( 'term', $_REQUEST ) ) {
wp_send_json_error( 'Search term is missing.' );
}
$result = array();
$entity = trim( sanitize_text_field( wp_unslash( $_REQUEST['entity'] ) ) );
$search_term = trim( sanitize_text_field( wp_unslash( $_REQUEST['term'] ) ) );
switch ( $entity ) {
case 'user':
$result = self::get_users( $search_term );
break;
case 'post':
$result = self::get_posts( $search_term );
break;
default:
wp_send_json_error( 'Unsupported data type.' );
}
echo wp_json_encode(
array(
'results' => $result,
)
);
die();
}
/**
* Get the user id through ajax, used in 'select2'.
*
* @param string $search_term Search term.
*
* @return array
*
* @since 4.5.1
*/
public static function get_users( $search_term = null ) {
$result = array();
$query_params = array();
if ( ! is_null( $search_term ) ) {
$query_params['search'] = '*' . $search_term . '*';
$query_params['search_columns'] = array( 'user_login', 'user_email' );
}
if ( WP_Helper::is_multisite() ) {
$query_params['blog_id'] = 0;
}
$users = get_users( $query_params );
if ( empty( $users ) ) {
return $result;
}
return array_map( array( __CLASS__, 'convert_object_to_select2_data' ), $users );
}
/**
* Converts given object to data that can be consumed by select2 library.
*
* @param object $object User, post or other supported object.
*
* @return array|void
*
* @since 4.5.1
*/
private static function convert_object_to_select2_data( $object ) {
if ( $object instanceof \WP_User ) {
return array(
'id' => $object->ID,
'text' => $object->user_login . ' (' . $object->user_email . ')',
);
}
if ( $object instanceof \WP_Post ) {
$post_title = $object->post_title;
if ( strlen( $post_title ) > 50 ) {
$post_title = substr( $post_title, 0, 50 ) . '...';
}
$post_id = $object->ID;
return array(
'id' => $post_id,
'text' => $post_title . ' (' . $post_id . ')',
);
}
}
/**
* Handles AJAX calls to retrieve post data to be used in 'select2'.
*
* @param string $search_term Search term.
*
* @return array
*
* @since 4.5.1
*/
public static function get_posts( $search_term ) {
$result = array();
$args = array(
'search_post_title' => $search_term, // Search post title only.
'suppress_filters' => false,
'post_status' => 'publish',
'post_type' => 'any',
);
add_filter( 'posts_where', array( __CLASS__, 'search_post_title' ), 10, 2 );
$posts = get_posts( $args );
remove_filter( 'posts_where', array( __CLASS__, 'search_post_title' ), 10 );
if ( empty( $posts ) ) {
return $result;
}
return array_map( array( __CLASS__, 'convert_object_to_select2_data' ), $posts );
}
/**
* Renders all the HTML, CSS and JS code necessary to display select2 form control configure using given parameters.
*
* @param array $args Select form control parameters.
*
* @since 4.5.1
*/
public static function insert( $args ) {
// name - string
// data-type - user, role, post
// placeholder - string
// width - int (pixels)
// id - string
// multiple - bool
// min_chars - int (minimum number of characters to type before searching)
// selected - selected value or an array of values
// extra_js_callback - function to call after the select2 init function is called, it should print additional JS if necessary, using "s2" as reference to the current select2 instance.
if ( ! isset( self::$base_url ) ) {
// Library not initialized correctly.
return;
}
if ( ! array_key_exists( 'name', $args ) ) {
// Field name is missing.
return;
}
if ( ! array_key_exists( 'data-type', $args ) && ! array_key_exists( 'data', $args ) ) {
// No data source defined.
return;
}
// Enqueue scripts.
if ( ! self::$scripts_queued ) {
self::enqueue_scripts();
}
$attributes = array(
'name' => $args['name'],
);
if ( ! array_key_exists( 'id', $args ) ) {
$args['id'] = self::$id_prefix . self::$id_counter++;
}
$attributes['id'] = $args['id'];
if ( array_key_exists( 'width', $args ) ) {
$attributes['style'] = 'width: ' . $args['width'] . 'px;';
}
array_walk(
$attributes,
function ( &$value, $key ) {
$value = $key . '="' . \esc_attr( $value ) . '"';
}
);
echo '<select ' . implode( ' ', $attributes ) . '></select>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<script type="application/javascript">';
echo 'jQuery( document ).ready( function() {';
echo 'const s2 = jQuery( "#' . \esc_attr( $args['id'] ) . '" ).select2( {';
if ( array_key_exists( 'placeholder', $args ) ) {
echo 'placeholder: "' . \esc_attr( $args['placeholder'] ) . '",';
}
echo 'containerCssClass: "s24wp-wrapper",';
if ( array_key_exists( 'multiple', $args ) && true === $args['multiple'] ) {
echo 'multiple: true,';
}
if ( array_key_exists( 'width', $args ) ) {
echo 'width: "' . \esc_attr( $args['width'] ) . 'px",';
}
if ( array_key_exists( 'data-type', $args ) && 'role' === $args['data-type'] ) {
// Populate the roles' data. Local data source will be used.
$role_names = wp_roles()->role_names;
asort( $role_names );
$roles_data = array();
foreach ( $role_names as $slug => $label ) {
$roles_data[] = array(
'id' => $slug,
'text' => $label,
);
}
$args['data'] = $roles_data;
unset( $args['data-type'] );
}
/**
* Users are by default loaded from a remote AJAX data source.
*
* However, there is an option to use pre-loaded local data source if there is less than certain number of
* objects of given type in system.
*
* TODO consider support for posts
*/
$data_types_with_remote_source_option = array( 'user' );
if ( array_key_exists( 'remote_source_threshold', $args ) && in_array( $args['data-type'], $data_types_with_remote_source_option, true ) ) {
$threshold = intval( $args['remote_source_threshold'] );
if ( $threshold > 0 ) {
if ( 'user' === $args['data-type'] ) {
$users_count = Reset_Passwords::count_users();
if ( $users_count <= $threshold ) {
$args['data'] = self::get_users();
unset( $args['data-type'] );
}
}
}
}
if ( array_key_exists( 'data', $args ) ) {
echo 'data: ' . json_encode( $args['data'] );
}
$has_remote_source = array_key_exists( 'data-type', $args );
if ( $has_remote_source ) {
$url = admin_url( 'admin-ajax.php' ) . '?action=' . self::$ajax_action . '&entity=' . $args['data-type'] . '&nonce=' . esc_attr( wp_create_nonce( 'wsal-select2-check' ) );
$min_chars = array_key_exists( 'min_chars', $args ) ? intval( $args['min_chars'] ) : 3;
echo 'minimumInputLength: "' . esc_attr( $min_chars ) . '",';
echo 'ajax: {';
echo 'url : "' . \esc_url( $url ) . '",';
echo 'dataType: "json"';
echo '}';
}
echo '} );';
if ( array_key_exists( 'extra_js_callback', $args ) && is_callable( $args['extra_js_callback'] ) ) {
call_user_func( $args['extra_js_callback'], $attributes['id'] );
}
if ( array_key_exists( 'selected', $args ) && is_array( $args['selected'] ) ) {
if ( $has_remote_source ) {
foreach ( $args['selected'] as $selected_value ) {
$data_type = $args['data-type'];
if ( 'user' === $data_type ) {
$object = get_user_by( 'ID', intval( $selected_value ) );
} elseif ( 'post' === $data_type ) {
$object = get_post( intval( $selected_value ) );
}
if ( isset( $object ) ) {
$data_item = self::convert_object_to_select2_data( $object );
if ( ! is_null( $data_item ) ) {
echo 's2.append(new Option("' . \esc_attr( $data_item['text'] ) . '", ' . \esc_attr( $data_item['id'] ) . ', true, true)).trigger("change");';
}
}
}
}
echo 's2.val(' . json_encode( $args['selected'] ) . ');';
echo 's2.trigger("change");';
}
echo '} );';
echo '</script>';
}
/**
* Enqueues necessary JS scripts and stylesheets.
*
* @since 4.5.1
* @since 5.0.0 - this method is no longer private.
*/
public static function enqueue_scripts() {
if ( self::$scripts_queued ) {
return;
}
if ( ! self::$initiated ) {
return;
}
wp_enqueue_style(
'wpw-select2',
self::$base_url . '/assets/css/select2.min.css',
array(),
'4.0.13'
);
wp_enqueue_style(
'wpw-select2-overrides',
self::$base_url . '/assets/css/wp-overrides.css',
array( 'wpw-select2' ),
'4.0.13'
);
wp_enqueue_script(
'wpw-select2',
self::$base_url . '/assets/js/select2.full.min.js',
array( 'jquery' ),
'4.0.13',
true
);
self::$scripts_queued = true;
}
/**
* Alters WordPress query to search only by post title.
*
* @param string $where SQL WHERE statement.
* @param WP_Query $wp_query WordPress query object.
*
* @return string
*
* @since 4.5.1
*/
public static function search_post_title( $where, $wp_query ) {
$search_term = $wp_query->get( 'search_post_title' );
if ( $search_term ) {
global $wpdb;
$where .= ' AND ' . $wpdb->posts . '.post_title LIKE \'%' . $wpdb->esc_like( $search_term ) . '%\'';
}
return $where;
}
}
}