WordPress поиск. Безопасный запрос к базе данных

В соответствии с рекомендациями данными в предыдущем вопросе: Wordpress. Насколько безопасен данный запрос к базе данных Я внес изменения в код, и хотел бы узнать насколько это правильно и безопасно сделано. К тому же, я не очень понял с проверкой wp_nonce для поиска. Я не встретил ни одного примера использования проверки nonce для поиска, в том числе и в темах которые идут с wordpress. Может быть я что то не так понял??? Дело в том, что я хотел сделать редирект типа wp_redirect( home_url() . "/search/" . urlencode( get_query_var( 's' ) ) ); но проверку wp_nonce не проходит и данные не выводятся. И последний вопрос. В данном случае я ищу посты в том числе и по метаполю. Поэтому пришлось сделать несколько запросов к базе данных, что бы правильно работала пагинация и я не знаю насколько это правильно.

searchform.php

<form class="search_form" role="search" method="post" id="searchform" action="<?php echo esc_url( home_url('/') );?>" > 
    <input class="search_input" placeholder="Search" type="text" value="<?php echo get_search_query() ?>" name="s" id="s">
    <div class="search_select_div">
        <select class="search_select" name="category" >
            <option value="graphics,templates,products">All</option>
            <option value="graphics,templates">Digital Only</option>
            <option value="graphics">Graphics</option>
            <option value="templates">Templates</option>
            <option value="products">Products</option>
        </select>
        <span class="icon-icons_ar_b"></span>
    </div>
    <input class="sort_input" type="hidden" name="sort" value="">
    <?php wp_nonce_field('search_action','search_nonce'); ?>
    <button class="search_button" type="submit" value="Search"><span class="icon-icons_search"></span></button>
</form>

search.php

if ( empty($_POST) || ! wp_verify_nonce( $_POST['search_nonce'], 'search_action') ){
   exit;
}else{
global $wpdb, $paged, $max_num_pages;
print_r($_POST['s']);
if( isset( $_POST['s'] )){
    $search_all = '.*';
    $regexp = "%[^A-Za-z0-9#\s\']%";
    $get_s_san = preg_replace( $regexp, "", $_POST['s'] );
    $get_s_sanitize = sanitize_text_field( $get_s_san );
    $get_s = explode( " ", trim($get_s_sanitize) );
    if( $get_s ) {
        foreach ( $get_s as $s ) {
            $search_all .= $s . '.*';
        }
    }
    if  (strpos( $search_all, "'" ) !== false ) {
        $search_all = str_replace( "'", "[a'-z]*", $search_all );
    } 
}

$category_name = 'category';
$meta_key_1 = 'key';
$meta_key_2 = 'popular';

$get_cat_all = sanitize_text_field( $_POST['category'] );
$get_cat_count = explode(",", trim( $get_cat_all ));

$placeholders = array_fill( 0, count( $get_cat_count ), '%s' );
$format = implode( ', ', $placeholders );

$args = $meta_key_1 . ',' . $meta_key_2 . ',' . $category_name . ',' . $get_cat_all . ',' . $search_all . ',' . $meta_key_1 . ',' . $category_name . ',' . $get_cat_all . ',' . $search_all . ',' . $offset . ',' . $post_per_page;
$args_all = explode( ",",  $args );

$sql = "SELECT DISTINCT * FROM $wpdb->posts AS ps
LEFT JOIN $wpdb->postmeta AS psm1 ON ( ps.ID = psm1.post_id AND psm1.meta_key = %s )
LEFT JOIN $wpdb->postmeta AS psm2 ON ( ps.ID = psm2.post_id AND psm2.meta_key = %s )
LEFT JOIN $wpdb->term_relationships AS tr ON ( ps.ID = tr.object_id)
LEFT JOIN $wpdb->term_taxonomy AS tt ON ( tr.term_taxonomy_id = tt.term_taxonomy_id )
LEFT JOIN $wpdb->terms AS t ON ( tt.term_id = t.term_id )
WHERE ps.post_type = 'post'
AND ps.post_status = 'publish'
AND tt.taxonomy = %s
AND t.slug IN ( $format )
AND ps.post_title regexp %s
OR psm1.meta_key = %s
AND tt.taxonomy = %s
AND t.slug IN ( $format )
AND psm1.meta_value regexp %s
AND ps.post_status = 'publish'";

if( $_POST['sort'] === 'popular'){
    $sql .= " ORDER BY psm2.meta_value +0 DESC";
}else if( $_POST['sort'] === 'oldest' ){
    $sql .= " ORDER BY ps.post_date ASC";
}else{
    $sql .= " ORDER BY ps.post_date DESC";
}
if ( $sql ){
    $result = $wpdb->get_results( $wpdb->prepare ( $sql,  $args_all ));
}



$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$post_per_page = intval(get_query_var('posts_per_page'));
$offset = ($paged - 1) * $post_per_page;

$total_count = $wpdb->get_var( $wpdb->prepare( "SELECT FOUND_ROWS();" ));

$max_num_pages  = ceil($total_count/ $post_per_page);
$wp_query->found_posts   = $total_count;
$wp_query->max_num_pages = $max_num_pages;

$limit_query = " LIMIT %d, %d";

if ( $sql ){
    $result = $wpdb->get_results( $wpdb->prepare ( $sql.$limit_query,  $args_all));
}}

Ответы (1 шт):

Автор решения: KAGG Design

Вот код, вычищенный с помощью анализатора кода по стандартам кодирования WordPress.

searchform.php

<?php
/**
 * The searchform.php template.
 *
 * Used any time that get_search_form() is called.
 *
 * @package my-search
 */

?>
<form
        class="search_form" role="search" method="post" id="searchform"
        action="<?php echo esc_url( home_url( '/' ) ); ?>">
    <label for="s"></label>
    <input
            class="search_input" placeholder="Search" type="text" value="<?php echo get_search_query(); ?>" name="s"
            id="s">
    <div class="search_select_div">
        <label>
            <select class="search_select" name="cat">
                <option value="graphics,templates,products">All</option>
                <option value="graphics,templates">Digital Only</option>
                <option value="graphics">Graphics</option>
                <option value="templates">Templates</option>
                <option value="products">Products</option>
            </select>
        </label>
        <span class="icon-icons_ar_b"></span>
    </div>
    <input class="sort_input" type="hidden" name="sort" value="">
    <?php wp_nonce_field( 'search_action', 'search_nonce' ); ?>
    <button class="search_button" type="submit" value="Search"><span class="icon-icons_search"></span></button>
</form>

search.php

<?php
/**
 * The template for displaying search results pages
 *
 * @package my-search
 */

$nonce = isset( $_POST['search_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['search_nonce'] ) ) : '';
if ( ! wp_verify_nonce( $nonce, 'search_action' ) ) {
    exit;
}

global $wpdb;

$search_string = isset( $_POST['s'] ) ? sanitize_text_field( wp_unslash( $_POST['s'] ) ) : '';
$search_all    = '';

if ( $search_string ) {
    $search_all    = '.*';
    $regexp        = "%[^A-Za-z0-9#\s']%";
    $search_string = preg_replace( $regexp, '', $search_string );
    $search_array  = explode( ' ', $search_string );

    if ( $search_array ) {
        // Не используйте $s - это глобальная переменная.
        foreach ( $search_array as $search_string ) {
            $search_all .= $search_string . '.*';
        }
    }

    // Тут не может быть ', удалён в preg_replace.
    if ( strpos( $search_all, "'" ) !== false ) {
        $search_all = str_replace( "'", "[a'-z]*", $search_all );
    }
}

$category_name = 'category';
$meta_key_1    = 'key';
$meta_key_2    = 'popular';

// $_POST['category'] - зарезервированная суперглобальная переменная.
$get_cat_all   = isset( $_POST['cat'] ) ? sanitize_text_field( wp_unslash( $_POST['cat'] ) ) : '';
$get_cat_count = explode( ',', trim( $get_cat_all ) );

$placeholders = array_fill( 0, count( $get_cat_count ), '%s' );
$format       = implode( ', ', $placeholders );

// $paged - глобальная преременная.
$my_paged       = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;
$posts_per_page = (int) get_query_var( 'posts_per_page' );
$offset         = ( $my_paged - 1 ) * $posts_per_page;

// Не были определены переменные $offset и $post_per_page.
$args     = $meta_key_1 . ',' . $meta_key_2 . ',' . $category_name . ',' . $get_cat_all . ',' . $search_all . ',' . $meta_key_1 . ',' . $category_name . ',' . $get_cat_all . ',' . $search_all . ',' . $offset . ',' . $posts_per_page;
$args_all = explode( ',', $args );

$sql = "SELECT DISTINCT * FROM $wpdb->posts AS ps
LEFT JOIN $wpdb->postmeta AS psm1 ON ( ps.ID = psm1.post_id AND psm1.meta_key = %s )
LEFT JOIN $wpdb->postmeta AS psm2 ON ( ps.ID = psm2.post_id AND psm2.meta_key = %s )
LEFT JOIN $wpdb->term_relationships AS tr ON ( ps.ID = tr.object_id)
LEFT JOIN $wpdb->term_taxonomy AS tt ON ( tr.term_taxonomy_id = tt.term_taxonomy_id )
LEFT JOIN $wpdb->terms AS t ON ( tt.term_id = t.term_id )
WHERE ps.post_type = 'post'
AND ps.post_status = 'publish'
AND tt.taxonomy = %s
AND t.slug IN ( $format )
AND ps.post_title regexp %s
OR psm1.meta_key = %s
AND tt.taxonomy = %s
AND t.slug IN ( $format )
AND psm1.meta_value regexp %s
AND ps.post_status = 'publish'
LIMIT %d, %d";

$sort = isset( $_POST['sort'] ) ? sanitize_text_field( wp_unslash( $_POST['sort'] ) ) : '';

switch ( $sort ) {
    case 'popular':
        $sql .= ' ORDER BY psm2.meta_value +0 DESC';
        break;
    case 'oldest':
        $sql .= ' ORDER BY ps.post_date ASC';
        break;
    default:
        $sql .= ' ORDER BY ps.post_date DESC';
}

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
$result = $wpdb->get_results(
    // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
    $wpdb->prepare( $sql, $args_all )
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery

$total_count = $wpdb->num_rows;

Я не вдавался глубоко в суть кода, мне кажется, там есть логические изъяны. Но в плане очистки входных данных и подготовки запроса в базу теперь ок.

→ Ссылка