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 шт):
Вот код, вычищенный с помощью анализатора кода по стандартам кодирования 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;
Я не вдавался глубоко в суть кода, мне кажется, там есть логические изъяны. Но в плане очистки входных данных и подготовки запроса в базу теперь ок.