Created
October 5, 2017 06:45
-
-
Save DarkAllMan/faa7995a152d96132cd5570052a9876c to your computer and use it in GitHub Desktop.
Smart Woocommerce Search with Groups Compatibility
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Class Ysm_Search | |
* Retrieves posts from the database depending on settings | |
*/ | |
class Ysm_Search | |
{ | |
/** | |
* Current widget id | |
* @var int | |
*/ | |
protected static $w_id = 0; | |
/** | |
* Registered post types list | |
* @var array | |
*/ | |
protected static $registered_pt = array(); | |
/** | |
* List of post types to search through | |
* @var array | |
*/ | |
protected static $pt = array(); | |
/** | |
* List of post fields to search through (title, content, excerpt) | |
* @var array | |
*/ | |
protected static $fields = array(); | |
/** | |
* List of terms to search through | |
* @var array | |
*/ | |
protected static $terms = array(); | |
/** | |
* List of post meta fields to search through | |
* @var array | |
*/ | |
protected static $postmeta = array(); | |
/** | |
* List of suggestions | |
* @var array | |
*/ | |
protected static $suggestions = array(); | |
/** | |
* List of elements that should be displayed in the widget | |
* @var array | |
*/ | |
protected static $display_opts = array(); | |
/** | |
* Limitation of search results | |
* @var int | |
*/ | |
protected static $max_posts = 0; | |
/** | |
* List of found post id's that satisfy search query | |
* @var array | |
*/ | |
protected static $result_post_ids = array(); | |
/** | |
* Search query | |
* @var string | |
*/ | |
protected static $s = ''; | |
/** | |
* Debug | |
* @var bool | |
*/ | |
public static $debug = false; | |
/** | |
* Initial hooks | |
*/ | |
public static function init() | |
{ | |
add_action('wp_ajax_nopriv_ysm_default_search', array(__CLASS__, 'default_search')); | |
add_action('wp_ajax_ysm_default_search', array(__CLASS__, 'default_search')); | |
add_action('wp_ajax_nopriv_ysm_product_search', array(__CLASS__, 'product_search')); | |
add_action('wp_ajax_ysm_product_search', array(__CLASS__, 'product_search')); | |
add_action('wp_ajax_nopriv_ysm_custom_search', array(__CLASS__, 'custom_search')); | |
add_action('wp_ajax_ysm_custom_search', array(__CLASS__, 'custom_search')); | |
add_action('pre_get_posts', array(__CLASS__, 'search_filter')); | |
add_action('wp', array(__CLASS__, 'remove_search_filter')); | |
add_filter('the_title', array(__CLASS__, 'accent_search_words'), 9999, 1); | |
add_filter('get_the_excerpt', array(__CLASS__, 'accent_search_words'), 9999, 1); | |
add_filter('the_content', array(__CLASS__, 'accent_search_words'), 9999, 1); | |
$registered_pt = get_post_types(); | |
unset($registered_pt['attachment']); | |
unset($registered_pt['revision']); | |
unset($registered_pt['nav_menu_item']); | |
unset($registered_pt['custom_css']); | |
unset($registered_pt['customize_changeset']); | |
if (ysm_is_woocommerce_active()) { | |
$registered_pt['product'] = 'Product'; | |
} | |
self::$registered_pt = array_keys($registered_pt); | |
} | |
/** | |
* Default search widget case | |
*/ | |
public static function default_search() | |
{ | |
self::$w_id = 'default'; | |
self::parse_settings(); | |
$s = $_REQUEST['query']; | |
if (!$s) { | |
self::output(); | |
} | |
if (count(self::$pt) === 0){ | |
self::output(); | |
} | |
$posts = self::search_posts($s); | |
self::get_suggestions($posts); | |
self::output(); | |
} | |
/** | |
* Default woocommerce product search widget case | |
*/ | |
public static function product_search() | |
{ | |
self::$w_id = 'product'; | |
self::parse_settings(); | |
$s = $_REQUEST['query']; | |
if (!$s) { | |
self::output(); | |
} | |
if (count(self::$pt) === 0){ | |
self::output(); | |
} | |
$posts = self::search_posts($s); | |
self::get_suggestions($posts); | |
self::output(); | |
} | |
/** | |
* Custom search widget case | |
*/ | |
public static function custom_search() | |
{ | |
if (isset($_REQUEST['id'])) { | |
self::$w_id = (int) $_REQUEST['id']; | |
} | |
self::parse_settings(); | |
$s = $_REQUEST['query']; | |
if (!$s) { | |
self::output(); | |
} | |
if (count(self::$pt) === 0){ | |
self::output(); | |
} | |
$posts = self::search_posts($s); | |
self::get_suggestions($posts); | |
self::output(); | |
} | |
/** | |
* Parse widget settings to define search behavior | |
*/ | |
public static function parse_settings() | |
{ | |
if (self::$w_id == 'product' || self::$w_id == 'default') { | |
$widgets = ysm_get_default_widgets(); | |
} else { | |
$widgets = ysm_get_custom_widgets(); | |
} | |
$settings = $widgets[ self::$w_id ]['settings']; | |
if (self::$w_id == 'product') { | |
self::$pt[ 'product' ] = 'product'; | |
} else { | |
foreach (self::$registered_pt as $type){ | |
if ( isset($settings['post_type_'.$type]) ) { | |
self::$pt[ $type ] = $type; | |
} | |
} | |
} | |
if ( ! empty( $settings['post_type_product_variation'] ) ) { | |
self::$pt[ 'product_variation' ] = 'product_variation'; | |
} | |
self::$max_posts = !empty( $settings['max_post_count'] ) ? $settings['max_post_count'] : 99; | |
/* fields to search through */ | |
if ( !empty( $settings['field_title'] ) ) { | |
self::$fields['post_title'] = 1; | |
} | |
if ( !empty( $settings['field_content'] ) ) { | |
self::$fields['post_content'] = 1; | |
} | |
if ( !empty( $settings['field_excerpt'] ) ) { | |
self::$fields['post_excerpt'] = 1; | |
} | |
if ( !empty( $settings['allowed_product_cat'] ) ) { | |
self::$fields['allowed_product_cat'] = $settings['allowed_product_cat']; | |
} | |
if ( !empty( $settings['field_tag'] ) ) { | |
self::$terms['post_tag'] = 'post_tag'; | |
} | |
if ( !empty( $settings['field_category'] ) ) { | |
self::$terms['category'] = 'category'; | |
} | |
if ( !empty( $settings['field_product_tag'] ) ) { | |
self::$terms['product_tag'] = 'product_tag'; | |
} | |
if ( !empty( $settings['field_product_cat'] ) ) { | |
self::$terms['product_cat'] = 'product_cat'; | |
} | |
if ( !empty( $settings['field_product_sku'] ) ) { | |
self::$postmeta['_sku'] = '_sku'; | |
} | |
/* output items to display */ | |
if ( !empty( $settings['display_icon'] ) ) { | |
self::$display_opts['display_icon'] = 'display_icon'; | |
} | |
if ( !empty( $settings['display_excerpt'] ) ) { | |
self::$display_opts['display_excerpt'] = 'display_excerpt'; | |
} | |
if ( !empty( $settings['excerpt_symbols_count'] ) ) { | |
self::$display_opts['excerpt_symbols_count'] = $settings['excerpt_symbols_count']; | |
} | |
if ( !empty( $settings['display_view_all_link'] ) ) { | |
self::$display_opts['display_view_all_link'] = 'display_view_all_link'; | |
} | |
if ( !empty( $settings['view_all_link_text'] ) ) { | |
self::$display_opts['view_all_link_text'] = $settings['view_all_link_text']; | |
} | |
if ( !empty( $settings['display_price'] ) ) { | |
self::$display_opts['display_price'] = 'display_price'; | |
} | |
if ( !empty( $settings['display_sku'] ) ) { | |
self::$display_opts['display_sku'] = 'display_sku'; | |
} | |
if ( !empty( $settings['search_page_default_output'] ) ) { | |
self::$display_opts['search_page_default_output'] = 'search_page_default_output'; | |
} | |
if ( !empty( $settings['accent_words_on_search_page'] ) ) { | |
self::$display_opts['accent_words_on_search_page'] = 'accent_words_on_search_page'; | |
} | |
} | |
/** | |
* Hook for changing posts set on search results page | |
* @param $query | |
* @return mixed | |
*/ | |
public static function search_filter($query) | |
{ | |
if ( $query->is_main_query() ) { | |
if ( $query->is_search && isset($_GET['search_id']) ) { | |
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { | |
return $query; | |
} | |
$wp_posts = array(); | |
$w_id = $_GET['search_id']; | |
$s = $_GET['s']; | |
if (empty($w_id)) { | |
return $query; | |
} | |
if (empty($s)) { | |
return $query; | |
} | |
if ($w_id == 'product') { | |
self::$w_id = 'product'; | |
} else if ($w_id == 'default') { | |
self::$w_id = 'default'; | |
} else { | |
self::$w_id = (int) $w_id; | |
} | |
self::parse_settings(); | |
if (!empty(self::$display_opts['search_page_default_output'])) { | |
return $query; | |
} | |
if (count(self::$pt) === 0){ | |
return $query; | |
} | |
self::$max_posts = '-1'; | |
$posts = self::search_posts($s); | |
self::$result_post_ids = $wp_posts; | |
$query->set('s', esc_attr( strip_tags( self::$s ) ) ); | |
$query->set('post__in', $wp_posts ); | |
$query-> set('orderby' ,'post__in'); | |
add_filter( 'posts_where', array( __CLASS__, 'posts_where' ), 9999 ); | |
} | |
} | |
} | |
/** | |
* Remove hook that change posts set on search results page | |
*/ | |
public static function remove_search_filter() | |
{ | |
global $wp_the_query; | |
if ( ! ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) { | |
if (!empty( $wp_the_query->query_vars['s'] ) && isset($_GET['search_id'])) { | |
remove_filter( 'posts_where', array( __CLASS__, 'posts_where' ), 9999 ); | |
remove_action('pre_get_posts', array( __CLASS__, 'search_filter' )); | |
} | |
} | |
} | |
/** | |
* Filter that set posts id's on search results page | |
* @param $where | |
* @return string | |
*/ | |
public static function posts_where($where ) | |
{ | |
global $wpdb; | |
$ids = !empty(self::$result_post_ids) ? implode(' , ', self::$result_post_ids) : '0'; | |
$where = " AND {$wpdb->posts}.ID IN (" . $ids . ") "; | |
return $where; | |
} | |
/** | |
* Generate a main query to retrieve posts from database | |
* @param string $s | |
* @return array|null|object | |
*/ | |
protected static function search_posts($s = '') | |
{ | |
global $wpdb; | |
self::$s = esc_attr( strip_tags( $s ) ); | |
$s = mb_strtolower( $s ); | |
/* SELECT part */ | |
$select = array(); | |
$select[] = "DISTINCT p.ID"; | |
$select[] = "p.post_title"; | |
$select[] = "p.post_content"; | |
$select[] = "p.post_excerpt"; | |
$select[] = "p.post_type"; | |
/* JOIN part */ | |
$join = array(); | |
/* WHERE part */ | |
$where = array( | |
'and' => array(), | |
'or' => array(), | |
); | |
$where['and'][] = "p.post_status = 'publish'"; | |
$s_post_types = array(); | |
foreach (self::$pt as $type){ | |
$s_post_types[] = "'" . esc_sql($type) . "'"; | |
} | |
$s_post_types = implode(',', $s_post_types); | |
$where['and'][] = "p.post_type IN ({$s_post_types})"; | |
if ( isset(self::$pt['product']) ) { | |
if ( version_compare( WC()->version, '3.0.0', '<' ) ) { | |
$join['pmpv'] = "LEFT JOIN {$wpdb->postmeta} pmpv ON pmpv.post_id = p.ID"; | |
$where['and'][] = "( p.post_type NOT IN ('product') OR (p.post_type = 'product' AND pmpv.meta_key = '_visibility' AND CAST(pmpv.meta_value AS CHAR) IN ('search','visible')) )"; | |
} else { | |
$wc_product_visibility_term_ids = wc_get_product_visibility_term_ids(); | |
$where['and'][] = sprintf( "p.ID NOT IN ( | |
SELECT object_id | |
FROM {$wpdb->term_relationships} | |
WHERE term_taxonomy_id IN (%d) | |
)", $wc_product_visibility_term_ids['exclude-from-search'] ); | |
} | |
} | |
/* relevance part */ | |
$relevance = array(); | |
/* GROUP BY part */ | |
$groupby = "p.ID"; | |
/* ORDER BY part */ | |
$orderby = array(); | |
/* LIMIT */ | |
$limit = self::$max_posts === '' ? 100 : self::$max_posts; | |
/* filters */ | |
if ( !empty(self::$fields['post_title']) ) { | |
$where['or'][] = "lower(p.post_title) LIKE %s"; | |
$relevance['p.post_title'] = 30; | |
} | |
if ( !empty(self::$fields['post_content']) ) { | |
$where['or'][] = "lower(p.post_content) LIKE %s"; | |
$relevance['p.post_content'] = 10; | |
} | |
if ( !empty(self::$fields['post_excerpt']) ) { | |
$where['or'][] = "lower(p.post_excerpt) LIKE %s"; | |
$relevance['p.post_excerpt'] = 10; | |
} | |
/* tags and categories */ | |
if ( !empty( self::$terms ) ) { | |
$s_terms = array(); | |
foreach (self::$terms as $term){ | |
$s_terms[] = "'" . $term . "'"; | |
} | |
$s_terms = implode(',', $s_terms); | |
$where['or'][] = "( t_tax.taxonomy IN ({$s_terms}) AND lower(t.name) LIKE %s )"; | |
} | |
// restrict searching only in defined categories | |
if ( !empty( self::$fields['allowed_product_cat'] ) ) { | |
$allowed_product_cats = explode( ',', trim( self::$fields['allowed_product_cat'], ',' ) ); | |
$allowed_product_cats_filtered = array(); | |
foreach ( $allowed_product_cats as $allowed_product_cat ) { | |
$allowed_product_cat = trim( $allowed_product_cat ); | |
if ( ! empty( $allowed_product_cat ) ) { | |
$allowed_product_cats_filtered[] = "'" . intval( $allowed_product_cat ) . "'"; | |
} | |
} | |
if ( ! empty( $allowed_product_cats_filtered ) ) { | |
$allowed_product_cats_filtered = implode( ",", $allowed_product_cats_filtered ); | |
$where['and'][] = sprintf( "( p.post_type NOT IN ('product') OR ( p.post_type = 'product' AND t_tax.taxonomy = 'product_cat' AND t.term_id IN (%s) ) )", $allowed_product_cats_filtered ); | |
// product variations | |
//$where['and'][] = sprintf( "( p.post_type NOT IN ('product_variation') OR ( p.post_type = 'product_variation' AND t_tax.taxonomy = 'product_cat' AND t.term_id IN (%s) ) )", $allowed_product_cats_filtered ); | |
} | |
} | |
if ( !empty( self::$terms ) || !empty( self::$fields['allowed_product_cat'] ) ) { | |
$join['t_rel'] = "LEFT JOIN {$wpdb->term_relationships} t_rel ON p.ID = t_rel.object_id"; | |
$join['t_tax'] = "LEFT JOIN {$wpdb->term_taxonomy} t_tax ON t_tax.term_taxonomy_id = t_rel.term_taxonomy_id"; | |
$join['t'] = "LEFT JOIN {$wpdb->terms} t ON t_tax.term_id = t.term_id"; | |
} | |
if ( !empty( self::$postmeta ) ) { | |
foreach (self::$postmeta as $postmeta) { | |
$where['or'][] = "( pm.meta_key = '{$postmeta}' AND lower( pm.meta_value ) LIKE %s )"; | |
} | |
$join['pm'] = "LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID"; | |
} | |
if ( !empty($where['or']) ) { | |
$placeholder = array(); | |
$like_query = "(" . implode(' OR ', $where['or']) . ")"; | |
foreach ($where['or'] as $val) { | |
$placeholder[] = "%".$s."%"; | |
} | |
$where['and'][] = $wpdb->prepare( $like_query, $placeholder ); | |
} | |
if ( !empty($relevance) ) { | |
$placeholder = array(); | |
$relevance_query = array(); | |
foreach ($relevance as $k => $v) { | |
$relevance_query[] = "( CASE | |
WHEN ( lower($k) LIKE '%s' ) THEN " . (int) $v ." | |
ELSE 0 | |
END )"; | |
$placeholder[] = "%".$s."%"; | |
} | |
$relevance_query = "( " . implode(' + ', $relevance_query) . " )"; | |
$relevance_query = $wpdb->prepare( $relevance_query, $placeholder ); | |
$select[] = "$relevance_query as relevance"; | |
$orderby[] = "relevance DESC"; | |
} | |
if ( defined('ICL_LANGUAGE_CODE') && ICL_LANGUAGE_CODE != '' ) { | |
$join['icl'] = $wpdb->prepare( "RIGHT JOIN {$wpdb->prefix}icl_translations icl ON (p.ID = icl.element_id AND icl.language_code = '%s')", ICL_LANGUAGE_CODE ); | |
} | |
$join = apply_filters('smart_search_query_join', $join); | |
$where = apply_filters('smart_search_query_where', $where); | |
$orderby[] = "p.post_title ASC"; | |
$query = "SELECT " . implode(' , ', $select) . | |
" FROM {$wpdb->posts} p | |
" . implode(' ', $join) . | |
" WHERE " . implode(' AND ', $where['and']) . | |
" GROUP BY " . $groupby . | |
" ORDER BY " . implode(' , ', $orderby); | |
if ($limit !== '-1') { | |
$query .= " LIMIT " . (int) $limit; | |
} | |
$posts = $wpdb->get_results($query, OBJECT_K); | |
if(class_exists( 'Groups_Cache')){ | |
foreach ($posts as $post) { | |
if( has_term('product', 'taxonomy', $post) ) { | |
$product = new WC_Product( $post ); | |
$visible = intval($product->is_visible()); | |
if($visible){ | |
$visible = intval(Groups_Post_Access::user_can_read_post($post->ID)); | |
} | |
if($visible){ | |
$filtered_posts[] = $post; | |
} | |
} else { | |
$filtered_posts[] = $post; | |
} | |
} | |
return $filtered_posts; | |
} | |
return $posts; | |
} | |
/** | |
* Prepare suggestions list | |
* @param $posts | |
*/ | |
protected static function get_suggestions($posts) | |
{ | |
foreach ($posts as $post) { | |
$output = '<a href="' . esc_url( get_permalink($post->ID) ) . '" class="smart-search-post post-' . (int) $post->ID . '">'; | |
/* featured image */ | |
if ( !empty(self::$display_opts['display_icon']) && has_post_thumbnail( $post->ID )) { | |
$image = get_the_post_thumbnail($post->ID); | |
if (empty($image)) { | |
$post_format = get_post_format($post->ID); | |
$image = '<span class="smart-search-post-format-' . $post_format . '"></span>'; | |
} | |
$output .= '<div class="smart-search-post-icon">' . $image . '</div>'; | |
} | |
/* holder open */ | |
$output .= '<div class="smart-search-post-holder">'; | |
/* title */ | |
$post_title = esc_html( $post->post_title ); | |
$post_title = preg_replace( '/'.self::$s.'/i', "<strong>$0</strong>", $post_title ); | |
$output .= '<div class="smart-search-post-title">' . $post_title . '</div>'; | |
if ( ( 'product' === $post->post_type || 'product_variation' === $post->post_type ) && ysm_is_woocommerce_active() ) { | |
$product = wc_get_product( $post->ID ); | |
/* product price */ | |
if ( !empty( self::$display_opts['display_price'] ) ) { | |
$output .= '<div class="smart-search-post-price">' . $product->get_price_html() . '</div>'; | |
} | |
/* product sku */ | |
if ( !empty( self::$display_opts['display_sku'] ) ) { | |
$output .= '<div class="smart-search-post-sku">' . esc_html( $product->get_sku() ) . '</div>'; | |
} | |
} | |
$output .= '<div class="smart-search-clear"></div>'; | |
$output .= '</div><!--.smart-search-post-holder-->'; | |
$output .= '<div class="smart-search-clear"></div>'; | |
/* excerpt */ | |
if (!empty(self::$display_opts['display_excerpt'])) { | |
if ( $post->post_excerpt != '' ) { | |
$post_excerpt = $post->post_excerpt; | |
} else { | |
$post_excerpt = $post->post_content; | |
} | |
$post_excerpt = strip_tags( strip_shortcodes( $post_excerpt) ); | |
$excerpt_symbols_count_max = !empty( self::$display_opts['excerpt_symbols_count'] ) ? (int) self::$display_opts['excerpt_symbols_count'] : 50; | |
$excerpt_symbols_count = strlen($post_excerpt); | |
$post_excerpt = mb_substr( $post_excerpt, 0, $excerpt_symbols_count_max); | |
if ($excerpt_symbols_count > $excerpt_symbols_count_max) { | |
$post_excerpt .= ' ...'; | |
} | |
$post_excerpt = preg_replace( '/'.self::$s.'/i', "<strong>$0</strong>", $post_excerpt ); | |
$output .= '<div class="smart-search-post-excerpt">' . $post_excerpt . '</div>'; | |
} | |
$output .= '</a>'; | |
self::$suggestions[] = array( | |
'value' => esc_js($post->post_title), | |
'data' => $output, | |
); | |
} | |
} | |
/** | |
* Retrieve the url of View All link or redirect url of form | |
* @return string | |
*/ | |
protected static function get_viewall_link_url () { | |
$url = home_url('/') . '?s=' . self::$s; | |
//if ( empty(self::$display_opts['search_page_default_output']) ) { | |
$url .= '&search_id=' . self::$w_id; | |
//} | |
if ( self::$w_id === 'product' || ( isset(self::$pt[ 'product' ]) && count(self::$pt) === 1 ) ) { | |
$url .= '&post_type=product'; | |
} | |
return $url; | |
} | |
/** | |
* Output suggestions | |
*/ | |
protected static function output() | |
{ | |
$view_all_link = ''; | |
if (!empty(self::$display_opts['display_view_all_link']) || !empty(self::$display_opts['view_all_link_text'])) { | |
$view_all_link = self::get_viewall_link_url(); | |
$view_all_link = '<a class="smart-search-view-all" href="' . $view_all_link . '">' . __( self::$display_opts['view_all_link_text'] , 'smart_search') . '</a>'; | |
} | |
$res = array( | |
'suggestions' => self::$suggestions, | |
'view_all_link' => $view_all_link | |
); | |
//debug output | |
if ( self::$debug ) { | |
global $wpdb; | |
echo "<pre>"; | |
print_r( $wpdb->queries ); | |
echo "</pre>"; | |
} else { | |
ob_clean(); | |
} | |
echo json_encode($res); | |
exit(); | |
} | |
public static function accent_search_words( $text ) { | |
if ( is_search() && isset($_GET['search_id']) ) { | |
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { | |
return $text; | |
} | |
$w_id = $_GET['search_id']; | |
$s = $_GET['s']; | |
if ( empty( $w_id ) || empty( $s ) ) { | |
return $text; | |
} | |
if ($w_id == 'product') { | |
self::$w_id = 'product'; | |
} else if ($w_id == 'default') { | |
self::$w_id = 'default'; | |
} else { | |
self::$w_id = (int) $w_id; | |
} | |
self::parse_settings(); | |
if ( empty( self::$display_opts['search_page_default_output'] ) && ! empty( self::$display_opts['accent_words_on_search_page'] ) ) { | |
$text = preg_replace( '/' . self::$s . '/i', "<strong>$0</strong>", $text ); | |
} | |
} | |
return $text; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment