470 lines
12 KiB
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;
|
|
}
|
|
}
|
|
}
|