options; $sd = $this->searchData; $types = array(); //$post_types = ""; $term_query = "(1)"; //$post_statuses = ""; //$term_join = ""; $postmeta_join = ""; $kw_logic = strtolower( $sd['keyword_logic'] ); // Prefixes and suffixes $pre_field = $this->pre_field; $suf_field = $this->suf_field; $pre_like = $this->pre_like; $suf_like = $this->suf_like; $s = $this->s; // full keyword $_s = $this->_s; // array of keywords $wcl = '%'; // Wildcard Left $wcr = '%'; // Wildcard right if ( $options['set_exactonly'] == 1 ) { if ( $sd['exact_match_location'] == 'start' ) $wcl = ''; else if ( $sd['exact_match_location'] == 'end' ) $wcr = ''; } if ( isset($options['non_ajax_search']) ) $this->remaining_limit = 500; else $this->remaining_limit = $sd['maxresults']; $q_config['language'] = $options['qtranslate_lang']; /*------------------------- Statuses ----------------------------*/ $post_statuses_arr = explode(',', str_replace(' ', '', w_isset_def($sd['post_status'], 'publish')) ); // Do not allow private posts for non-editors if ( !current_user_can('read_private_posts') ) $post_statuses_arr = array_diff($post_statuses_arr, array('private')); $post_statuses = "'".implode( "','", $post_statuses_arr )."'"; $post_statuses = "( $wpdb->posts.post_status IN ($post_statuses) )"; /*---------------------------------------------------------------*/ /*----------------------- Gather Types --------------------------*/ if ($options['set_inposts'] == 1) $types[] = "post"; if ($options['set_inpages']) $types[] = "page"; if (isset($options['customset']) && count($options['customset']) > 0) $types = array_merge($types, $options['customset']); if (count($types) < 1) { return ''; } else { $words = implode("','", $types); if ( in_array('product_variation', $types) && class_exists('WooCommerce') ) { $_post_types = $types; $_post_types = array_diff($_post_types, array('product_variation')); if (count($_post_types) > 0) $or_ptypes = "OR $wpdb->posts.post_type IN ('".implode("', '", $_post_types)."')"; else $or_ptypes = ''; $post_types = " (( ( $wpdb->posts.post_type = 'product_variation' AND EXISTS(SELECT 1 FROM $wpdb->posts par WHERE par.ID = $wpdb->posts.post_parent AND par.post_status IN('publish') ) ) $or_ptypes ))"; } else { $post_types = "( $wpdb->posts.post_type IN ('$words') )"; } } /*---------------------------------------------------------------*/ /*------------- Custom Fields with Custom selectors -------------*/ $cf_select = ''; if ( $sd['woo_exclude_outofstock'] == 1 ) { $cf_select = $this->build_cff_query($wpdb->posts . ".ID", array( 'post_meta_filter' => array( array( 'key' => '_stock_status', 'value' => 'instock', 'operator' => 'ELIKE' ) ) )); $cf_select = $cf_select != '' ? ' AND ' . $cf_select : ''; } /*---------------------------------------------------------------*/ $words = $options['set_exactonly'] == 1 && $s != '' ? array($s) : $_s; /** * Ex.: When the minimum word count is 2, and the user enters 'a' then $_s is empty. * But $s is not actually empty, thus the wrong query will be executed. */ if ( count($words) == 0 && $s != '' ) { $words = array($s); // Allow only beginnings if ( $options['set_exactonly'] == 0 ) $wcl = ''; } if ( $s != '' ) $words = !in_array($s, $words) ? array_merge(array($s), $words) : $words; $relevance_added = false; foreach ( $words as $k => $word ) { $parts = array(); $relevance_parts = array(); $is_exact = $options['set_exactonly'] == 1 || ( count($words) > 1 && $k == 0 && ($kw_logic == 'or' || $kw_logic == 'and') ); /*----------------------- Title query ---------------------------*/ if ( $options['set_intitle'] ) { if ( $kw_logic == 'or' || $kw_logic == 'and' || $is_exact ) { $parts[] = "( " . $pre_field . $wpdb->posts . ".post_title" . $suf_field . " LIKE $pre_like'$wcl" . $word . "$wcr'$suf_like )"; } else { $parts[] = " ( " . $pre_field . $wpdb->posts . ".post_title" . $suf_field . " LIKE $pre_like'% " . $word . " %'$suf_like OR " . $pre_field . $wpdb->posts . ".post_title" . $suf_field . " LIKE $pre_like'" . $word . " %'$suf_like OR " . $pre_field . $wpdb->posts . ".post_title" . $suf_field . " LIKE $pre_like'% " . $word . "'$suf_like OR " . $pre_field . $wpdb->posts . ".post_title" . $suf_field . " = '" . $word . "')"; } if ( !$relevance_added ) { $relevance_parts[] = "(case when (" . $pre_field . $wpdb->posts . ".post_title" . $suf_field . " LIKE '$s%') then " . (w_isset_def($sd['etitleweight'], 10) * 2) . " else 0 end)"; $relevance_parts[] = "(case when (" . $pre_field . $wpdb->posts . ".post_title" . $suf_field . " LIKE '%$s%') then " . w_isset_def($sd['etitleweight'], 10) . " else 0 end)"; // The first word relevance is higher if ( isset($_s[0]) ) { $relevance_parts[] = "(case when (" . $pre_field . $wpdb->posts . ".post_title" . $suf_field . " LIKE '%" . $_s[0] . "%') then " . w_isset_def($sd['etitleweight'], 10) . " else 0 end)"; } } } /*---------------------------------------------------------------*/ /*---------------------- Content query --------------------------*/ if ( $options['set_incontent'] ) { if ( $kw_logic == 'or' || $kw_logic == 'and' || $is_exact ) { $parts[] = "( " . $pre_field . $wpdb->posts . ".post_content" . $suf_field . " LIKE $pre_like'$wcl" . $word . "$wcr'$suf_like )"; /** * Exact matching multi line + word boundary with REGEXP * * $parts[] = "( " . $pre_field . $wpdb->posts . ".post_content" . $suf_field . " REGEXP '([[:blank:][:punct:]]|^|\r\n)" . $word . "([[:blank:][:punct:]]|$|\r\n)' )"; */ } else { $parts[] = " (" . $pre_field . $wpdb->posts . ".post_content" . $suf_field . " LIKE $pre_like'% " . $word . " %'$suf_like OR " . $pre_field . $wpdb->posts . ".post_content" . $suf_field . " LIKE $pre_like'" . $word . " %'$suf_like OR " . $pre_field . $wpdb->posts . ".post_content" . $suf_field . " LIKE $pre_like'% " . $word . "'$suf_like OR " . $pre_field . $wpdb->posts . ".post_content" . $suf_field . " = '" . $word . "')"; } if ( !$relevance_added ) { if ( isset($_s[0]) ) { $relevance_parts[] = "(case when (" . $pre_field . $wpdb->posts . ".post_content" . $suf_field . " LIKE '%" . $_s[0] . "%') then ".w_isset_def($sd['contentweight'], 10)." else 0 end)"; } $relevance_parts[] = "(case when (" . $pre_field . $wpdb->posts . ".post_content" . $suf_field . " LIKE '%$s%') then " . w_isset_def($sd['econtentweight'], 10) . " else 0 end)"; } } /*---------------------------------------------------------------*/ /*----------------- Permalink, post_name query ------------------*/ if ( $sd['search_in_permalinks'] ) { $parts[] = "( " . $pre_field . $wpdb->posts . ".post_name" . $suf_field . " LIKE $pre_like'$wcl" . $word . "$wcr'$suf_like )"; } /*---------------------------------------------------------------*/ /*---------------------- Excerpt query --------------------------*/ if ($options['set_inexcerpt']) { if ( $kw_logic == 'or' || $kw_logic == 'and' || $is_exact ) { $parts[] = "( " . $pre_field . $wpdb->posts . ".post_excerpt" . $suf_field . " LIKE $pre_like'$wcl" . $word . "$wcr'$suf_like )"; } else { $parts[] = " (" . $pre_field . $wpdb->posts . ".post_excerpt" . $suf_field . " LIKE $pre_like'% " . $word . " %'$suf_like OR " . $pre_field . $wpdb->posts . ".post_excerpt" . $suf_field . " LIKE $pre_like'" . $word . " %'$suf_like OR " . $pre_field . $wpdb->posts . ".post_excerpt" . $suf_field . " LIKE $pre_like'% " . $word . "'$suf_like OR " . $pre_field . $wpdb->posts . ".post_excerpt" . $suf_field . " = '" . $word . "')"; } if ( !$relevance_added ) { if ( isset($_s[0]) ) { $relevance_parts[] = "(case when (" . $pre_field . $wpdb->posts . ".post_excerpt" . $suf_field . " LIKE '%" . $_s[0] . "%') then ".w_isset_def($sd['excerptweight'], 10)." else 0 end)"; } $relevance_parts[] = "(case when (" . $pre_field . $wpdb->posts . ".post_excerpt" . $suf_field . " LIKE '%$s%') then " . w_isset_def($sd['eexcerptweight'], 10) . " else 0 end)"; } } /*---------------------------------------------------------------*/ /*------------------------ Term query ---------------------------*/ if ($options['searchinterms']) { if ( $kw_logic == 'or' || $kw_logic == 'and' || $is_exact ) { $parts[] = "( " . $pre_field . $wpdb->terms . ".name" . $suf_field . " LIKE $pre_like'$wcl" . $word . "$wcr'$suf_like )"; } else { $parts[] = " (" . $pre_field . $wpdb->terms . ".name" . $suf_field . " LIKE $pre_like'% " . $word . " %'$suf_like OR " . $pre_field . $wpdb->terms . ".name" . $suf_field . " LIKE $pre_like'" . $word . " %'$suf_like OR " . $pre_field . $wpdb->terms . ".name" . $suf_field . " LIKE $pre_like'% " . $word . "'$suf_like OR " . $pre_field . $wpdb->terms . ".name" . $suf_field . " = '" . $word . "')"; } if ( !$relevance_added ) { $relevance_parts[] = "(case when (" . $pre_field . $wpdb->terms . ".name" . $suf_field . " = '$s') then " . w_isset_def($sd['etermsweight'], 10) . " else 0 end)"; } } /*---------------------------------------------------------------*/ /*---------------------- Custom Fields --------------------------*/ $selected_customfields = isset( $sd['selected-customfields'] ) ? $sd['selected-customfields'] : array(); if ( $sd['search_all_cf'] == 1 ) $selected_customfields = array("all"); if ( is_array($selected_customfields) && count($selected_customfields) > 0 ) { $postmeta_join = "LEFT JOIN $wpdb->postmeta ON $wpdb->postmeta.post_id = $wpdb->posts.ID"; foreach ( $selected_customfields as $cfield ) { $key_part = $sd['search_all_cf'] == 1 ? "" : "$wpdb->postmeta.meta_key='$cfield' AND "; if ( $kw_logic == 'or' || $kw_logic == 'and' || $is_exact ) { $parts[] = "( $key_part " . $pre_field . $wpdb->postmeta . ".meta_value" . $suf_field . " LIKE $pre_like'$wcl" . $word . "$wcr'$suf_like )"; } else { $parts[] = "( $key_part (" . $pre_field . $wpdb->postmeta . ".meta_value" . $suf_field . " LIKE $pre_like'% " . $word . " %'$suf_like OR " . $pre_field . $wpdb->postmeta . ".meta_value" . $suf_field . " LIKE $pre_like'" . $word . " %'$suf_like OR " . $pre_field . $wpdb->postmeta . ".meta_value" . $suf_field . " LIKE $pre_like'% " . $word . "'$suf_like OR " . $pre_field . $wpdb->postmeta . ".meta_value" . $suf_field . " = '" . $word . "') )"; } if ( !$relevance_added ) { if ($cfield == 'author_field_name') $relevance_parts[] = "(case when (EXISTS (SELECT 1 FROM $wpdb->postmeta as cfre WHERE cfre.post_id = $wpdb->posts.ID AND cfre.meta_key = '$cfield' AND (cfre.meta_value" . $suf_field . " LIKE '%" . $s . "%'))) then 100 else 0 end)"; if ($cfield == 'fulltext_field_name') $relevance_parts[] = "(case when (EXISTS (SELECT 1 FROM $wpdb->postmeta as cfre WHERE cfre.post_id = $wpdb->posts.ID AND cfre.meta_key = '$cfield' AND (cfre.meta_value" . $suf_field . " LIKE '%" . $s . "%'))) then 10 else 0 end)"; } } } /*---------------------------------------------------------------*/ /*---------------------- Post CPT IDs ---------------------------*/ if ( $sd['search_in_ids'] ) $parts[] = "($wpdb->posts.ID LIKE '$word')"; /*---------------------------------------------------------------*/ $this->parts[] = array( $parts, $relevance_parts ); $relevance_added = true; } // ------------------------ Categories/taxonomies ---------------------- if ( ! isset( $options['categoryset'] ) || $options['categoryset'] == "" ) { $options['categoryset'] = array(); } if ( ! isset( $options['termset'] ) || $options['termset'] == "" ) { $options['termset'] = array(); } $term_logic = 'and'; $exclude_categories = array(); $sd['selected-exsearchincategories'] = w_isset_def( $sd['selected-exsearchincategories'], array() ); $sd['selected-excludecategories'] = w_isset_def( $sd['selected-excludecategories'], array() ); // New solution if ( count( $sd['selected-exsearchincategories'] ) > 0 || count( $sd['selected-excludecategories'] ) > 0 || count( $options['categoryset'] ) > 0 || $sd['showsearchincategories'] == 1 ) { // If the category settings are invisible, ignore the excluded frontend categories, reset to empty array if ( $sd['showsearchincategories'] == 0 ) { $sd['selected-exsearchincategories'] = array(); } $_all_cat = get_terms( 'category', array( 'fields' => 'ids' ) ); $_needed_cat = array_diff( $_all_cat, $sd['selected-exsearchincategories'] ); $_needed_cat = ! is_array( $_needed_cat ) ? array() : $_needed_cat; // I am pretty sure this is where the devil is born /* AND -> Posts NOT in an array of term ids OR -> Posts in an array of term ids */ if ( $sd['showsearchincategories'] == 1 ) // If the settings is visible, count for the options { $exclude_categories = array_diff( array_merge( $_needed_cat, $sd['selected-excludecategories'] ), $options['categoryset'] ); } else // ..if the settings is not visible, then only the excluded categories count { $exclude_categories = $sd['selected-excludecategories']; } } $exclude_terms = array(); /*if (w_isset_def($sd['exclude_term_ids'], "") != "") { $exclude_terms = explode( ",", str_replace( array("\r", "\n"), '', $sd['exclude_term_ids'] ) ); }*/ $all_terms = array_unique( array_merge( $exclude_categories, $exclude_terms ) ); /** * New method * * This is way more efficient, despite it looks more complicated. * Multiple sub-select is not an issue, since the query can use PRIMARY keys as indexes */ if ( count( $all_terms ) > 0 ) { $words = implode( ',', $all_terms ); // Quick explanation for the AND // .. MAIN SELECT: selects all object_ids that are not in the array // .. SUBSELECT: excludes all the object_ids that are part of the array // This is used because of multiple object_ids (posts in more than 1 category) if ( $term_logic == 'and' ) { $empty_term_query = " NOT EXISTS ( SELECT * FROM $wpdb->term_relationships as xt INNER JOIN $wpdb->term_taxonomy as tt ON ( xt.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'category') WHERE xt.object_id = $wpdb->posts.ID ) OR "; $term_query = " ( $empty_term_query $wpdb->posts.ID IN ( SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr LEFT JOIN $wpdb->term_taxonomy as tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'category') WHERE tt.term_id NOT IN ($words) AND tr.object_id NOT IN ( SELECT DISTINCT(trs.object_id) FROM $wpdb->term_relationships AS trs LEFT JOIN $wpdb->term_taxonomy as tts ON (trs.term_taxonomy_id = tts.term_taxonomy_id AND tts.taxonomy = 'category') WHERE tts.term_id IN ($words) ) ) )"; } else { $term_query = "( $wpdb->posts.ID IN ( SELECT DISTINCT(tr.object_id) FROM wp_term_relationships AS tr WHERE tr.term_taxonomy_id IN ($words) ) )"; } } /*------------------- WooCommerce Visibility --------------------*/ $woo_visibility_query = ''; if ( class_exists('WooCommerce') && $sd['exclude_woo_hidden'] == 1 ) { // Check if this is version > 3.0 if ( asp_woo_version_check('3.0') ) { $_t = get_terms(array( 'slug' => array('exclude-from-search'), 'hide_empty' => 0, 'fields' => 'ids' )); if ( !is_wp_error($_t) && count($_t) > 0) { $woo_visibility_query = " NOT EXISTS ( SELECT * FROM $wpdb->term_relationships as xt INNER JOIN $wpdb->term_taxonomy as tt ON ( xt.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'product_visibility') WHERE xt.object_id = $wpdb->posts.ID ) OR "; $woo_visibility_query = " AND ( $woo_visibility_query $wpdb->posts.ID IN ( SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr LEFT JOIN $wpdb->term_taxonomy as tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'product_visibility') WHERE tt.term_id NOT IN (".implode(', ', $_t).") AND tr.object_id NOT IN ( SELECT DISTINCT(trs.object_id) FROM $wpdb->term_relationships AS trs LEFT JOIN $wpdb->term_taxonomy as tts ON (trs.term_taxonomy_id = tts.term_taxonomy_id AND tts.taxonomy = 'product_visibility') WHERE tts.term_id IN (".implode(', ', $_t).") ) ) )"; } } else { // Fallback to < 3.0 $qry = "( $wpdb->postmeta.meta_value IN ('visible', 'search') )"; $woo_visibility_query .= " AND (( SELECT IF(meta_key IS NULL, 1, IF($qry, COUNT(post_id), 0)) FROM $wpdb->postmeta WHERE $wpdb->postmeta.post_id = $wpdb->posts.ID AND $wpdb->postmeta.meta_key='_visibility' ) >= 1) "; } } /*---------------------------------------------------------------*/ /*------------------------ Exclude id's -------------------------*/ if ( isset( $sd['excludeposts'] ) && $sd['excludeposts'] != "" ) { $exclude_post_ids = array_unique( explode(',', str_replace(' ', '', $sd['excludeposts'])) ); foreach ( $exclude_post_ids as $k=>$v ) { if ( $v == '' ) { unset($exclude_post_ids[$k]); } } if ( count($exclude_post_ids) > 0 ) $exclude_posts = " AND ($wpdb->posts.ID NOT IN (" . implode(',', $exclude_post_ids) . "))"; } else { $exclude_posts = ""; } /*---------------------------------------------------------------*/ /*------------------------ Term JOIN -------------------------*/ // If the search in terms is not active, we don't need this unnecessary big join $term_join = ""; if ( $options['searchinterms'] ) { $term_join = " LEFT JOIN $wpdb->term_relationships ON $wpdb->posts.ID = $wpdb->term_relationships.object_id LEFT JOIN $wpdb->term_taxonomy ON $wpdb->term_taxonomy.term_taxonomy_id = $wpdb->term_relationships.term_taxonomy_id LEFT JOIN $wpdb->terms ON $wpdb->term_taxonomy.term_id = $wpdb->terms.term_id"; } /*---------------------------------------------------------------*/ /*------------------------- Build like --------------------------*/ /*$like_query = implode( ' OR ', $parts ); if ( $like_query == "" ) { $like_query = "(1)"; } else { $like_query = "($like_query)"; }*/ /*---------------------------------------------------------------*/ /*---------------------- Build relevance ------------------------*/ /*$relevance = implode( ' + ', $relevance_parts ); if ( $relevance == "" ) { $relevance = "(1)"; } else { $relevance = "($relevance)"; }*/ /*---------------------------------------------------------------*/ /*------------------------- WPML filter -------------------------*/ $wpml_query = "(1)"; if ( isset( $options['wpml_lang'] ) && w_isset_def( $sd['wpml_compatibility'], 1 ) == 1 ) { global $sitepress; $site_lang_selected = false; $wpml_post_types_arr = array(); foreach ($types as $tt) { $wpml_post_types_arr[] = "post_" . $tt; } $wpml_post_types = implode( "','", $wpml_post_types_arr ); // Let us get the default site language if possible if ( is_object($sitepress) && method_exists($sitepress, 'get_default_language') ) { $site_lang_selected = $sitepress->get_default_language() == $options['wpml_lang'] ? true : false; } $_wpml_query_id_field = "$wpdb->posts.ID"; // Product variations are not translated, so we need to use the parent ID (product) field to compare if ( in_array('product_variation', $types) ) { $_wpml_query_id_field = "(IF($wpdb->posts.post_type='product_variation', $wpdb->posts.post_parent, $wpdb->posts.ID))"; } $wpml_query = " EXISTS ( SELECT DISTINCT(wpml.element_id) FROM " . $wpdb->base_prefix . "icl_translations as wpml WHERE $_wpml_query_id_field = wpml.element_id AND wpml.language_code = '" . ASL_Helpers::escape( $options['wpml_lang'] ) . "' AND wpml.element_type IN ('$wpml_post_types') )"; /** * For missing translations.. * If the site language is used, the translation can be non-existent */ if ($site_lang_selected) { $wpml_query = " NOT EXISTS ( SELECT DISTINCT(wpml.element_id) FROM " . $wpdb->base_prefix . "icl_translations as wpml WHERE $_wpml_query_id_field = wpml.element_id AND wpml.element_type IN ('$wpml_post_types') ) OR " . $wpml_query; } } /*---------------------------------------------------------------*/ /*----------------------- POLYLANG filter -----------------------*/ $polylang_query = ""; if (isset( $options['polylang_lang'] ) && $options['polylang_lang'] != "" && $sd['polylang_compatibility'] == 1 ) { $languages = get_terms('language', array( 'hide_empty' => false, 'fields' => 'ids', 'orderby' => 'term_group', 'slug' => $options['polylang_lang']) ); if ( !empty($languages) && !is_wp_error($languages) && isset($languages[0]) ) { $polylang_query = " AND ( $wpdb->posts.ID IN ( SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr LEFT JOIN $wpdb->term_taxonomy as tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'language') WHERE tt.term_id = $languages[0] ) )"; } } /*---------------------------------------------------------------*/ /*--------------------- Other Query stuff -----------------------*/ // If the content is hidden, why select it.. if ($sd['showdescription'] == 0) $select_content = "''"; else $select_content = $wpdb->posts. ".post_content"; // Dont select excerpt if its not used at all $select_excerpt = ( w_isset_def($sd['titlefield'], 0) == 1 || w_isset_def($sd['descriptionfield'], 0) == 1 || wd_in_array_r('excerpt', $this->imageSettings) ) ? $wpdb->posts. ".post_excerpt" : "''"; /*---------------------------------------------------------------*/ $querystr = " SELECT {args_fields} $wpdb->posts.post_title as title, $wpdb->posts.ID as id, $wpdb->posts.post_date as date, $select_content as content, $select_excerpt as excerpt, 'pagepost' as content_type, (SELECT $wpdb->users.display_name as author FROM $wpdb->users WHERE $wpdb->users.ID = $wpdb->posts.post_author ) as author, '' as ttid, $wpdb->posts.post_type as post_type, {relevance_query} as relevance FROM $wpdb->posts $postmeta_join $term_join {args_join} WHERE $post_types $woo_visibility_query $cf_select AND $post_statuses AND $term_query AND {like_query} $exclude_posts AND ( $wpml_query ) $polylang_query {args_where} GROUP BY {args_groupby} $wpdb->posts.ID ORDER BY {args_orderby} ".$sd['orderby_primary'].", ".$sd['orderby_secondary'].", id DESC LIMIT " . $this->remaining_limit; $_qargs = array( 'fields' => '', 'join' => '', 'where' => '', 'orderby' => '', 'groupby' => '' ); $_qargs = apply_filters('asl_query_add_args', $_qargs, $sd, $options); // Place the argument query fields if ( is_array($_qargs) ) { $querystr = str_replace( array('{args_fields}', '{args_join}', '{args_where}', '{args_orderby}', '{args_groupby}'), array($_qargs['fields'], $_qargs['join'], $_qargs['where'], $_qargs['orderby'], $_qargs['groupby']), $querystr ); } else { $querystr = str_replace( array('{args_fields}', '{args_join}', '{args_where}', '{args_orderby}', '{args_groupby}'), '', $querystr ); } $querystr = $this->build_query( $this->parts, $querystr, array( 'keyword_logic' => $kw_logic, '_post_use_relevance' => 1 )); $pageposts = $wpdb->get_results( $querystr, OBJECT ); wd_asl()->debug->pushData( array( "phrase" => $s, "options" => $options, "query" => $querystr, "results" => count($pageposts) ), "queries", false, true, false, 5 ); $this->results = $pageposts; return $pageposts; } /** * Builds the query from the parts * * @param $parts array LIKE and Relevance parts * @param $query string the search query * @param $argp array arguments passed * * @return string query */ protected function build_query( $parts, $query, $argp = array() ) { $args = array_merge(array( 'keyword_logic' => 'OR', '_post_use_relevance' => 1 ), $argp); $kw_logic = str_replace('EX', '', strtoupper( $args['keyword_logic'] ) ); $kw_logic = $kw_logic != 'AND' && $kw_logic != 'OR' ? 'AND' : $kw_logic; $r_parts = array(); // relevance parts /*------------------------- Build like --------------------------*/ $exact_query = ''; $like_query_arr = array(); foreach ( $parts as $k=>$part ) { if ( $k == 0 ) $exact_query = '(' . implode(' OR ', $part[0]) . ')'; else $like_query_arr[] = '(' . implode(' OR ', $part[0]) . ')'; } $like_query = implode(' ' . $kw_logic . ' ', $like_query_arr); // When $exact query is empty, then surely $like_query must be empty too, see above if ( $exact_query == '' ) { $like_query = "(1)"; } else { // Both $like_query and $exact_query set if ( $like_query != '' ) { $like_query = "( $exact_query OR $like_query )"; } else { $like_query = "( $exact_query )"; } } /*---------------------------------------------------------------*/ /*---------------------- Build relevance ------------------------*/ foreach ( $parts as $part ) { if ( isset($part[1]) && count($part[1]) > 0 ) $r_parts = array_merge( $r_parts, $part[1] ); } $relevance = implode( ' + ', $r_parts ); if ( $args['_post_use_relevance'] != 1 || $relevance == "" ) { $relevance = "(1)"; } else { $relevance = "($relevance)"; } /*---------------------------------------------------------------*/ if ( isset($this->remaining_limit, $this->limit_start) ) { if ($this->limit_start != 0) $limit = $this->limit_start . ", " . $this->remaining_limit; else $limit = $this->remaining_limit; } else { $limit = 10; } return str_replace( array( "{relevance_query}", "{like_query}", "{remaining_limit}" ), array( $relevance, $like_query, $limit ), $query ); } protected function build_cff_query( $post_id_field, $args ) { global $wpdb; $defaults = array( '_post_meta_allow_null' => 1, '_post_meta_logic' => 'AND', 'post_meta_filter' => array() ); $args = wp_parse_args( $args, $defaults ); $parts = array(); $allow_cf_null = $args['_post_meta_allow_null']; foreach ( $args['post_meta_filter'] as $data ) { $operator = $data['operator']; $posted = $data['value']; $field = $data['key']; // Is this a special case of date operator? if (strpos($operator, "datetime") === 0) { switch ($operator) { case 'datetime =': $current_part = "($wpdb->postmeta.meta_value BETWEEN '$posted 00:00:00' AND '$posted 23:59:59')"; break; case 'datetime <>': $current_part = "($wpdb->postmeta.meta_value NOT BETWEEN '$posted 00:00:00' AND '$posted 23:59:59')"; break; case 'datetime <': $current_part = "($wpdb->postmeta.meta_value < '$posted 00:00:00')"; break; case 'datetime >': $current_part = "($wpdb->postmeta.meta_value > '$posted 23:59:59')"; break; default: $current_part = "($wpdb->postmeta.meta_value < '$posted 00:00:00')"; break; } // Is this a special case of timestamp? } else if (strpos($operator, "timestamp") === 0) { switch ($operator) { case 'timestamp =': $current_part = "($wpdb->postmeta.meta_value BETWEEN $posted AND ".($posted + 86399).")"; break; case 'timestamp <>': $current_part = "($wpdb->postmeta.meta_value NOT BETWEEN $posted AND ".($posted + 86399).")"; break; case 'timestamp <': $current_part = "($wpdb->postmeta.meta_value < $posted)"; break; case 'timestamp >': $current_part = "($wpdb->postmeta.meta_value > ".($posted + 86399).")"; break; default: $current_part = "($wpdb->postmeta.meta_value < $posted)"; break; } // Check BETWEEN first -> range slider } else if ( $operator === "BETWEEN" ) { $current_part = "($wpdb->postmeta.meta_value BETWEEN " . $posted[0] . " AND " . $posted[1] . " )"; // If not BETWEEN but value is array, then drop-down or checkboxes } else if ( is_array($posted) ) { // Is there a logic sent? $logic = isset($data['logic']) ? $data['logic'] : "OR"; $values = ''; if ($operator === "IN" ) { $val = implode("','", $posted); if ( !empty($val) ) { if ($values != '') { $values .= " $logic $wpdb->postmeta.meta_value $operator ('" . $val . "')"; } else { $values .= "$wpdb->postmeta.meta_value $operator ('" . $val . "')"; } } } else { foreach ($posted as $v) { if ($operator === "ELIKE") { if ($values != '') { $values .= " $logic $wpdb->postmeta.meta_value $operator '" . $v . "'"; } else { $values .= "$wpdb->postmeta.meta_value $operator '" . $v . "'"; } } else if ($operator === "NOT LIKE" || $operator === "LIKE") { if ($values != '') { $values .= " $logic $wpdb->postmeta.meta_value $operator '%" . $v . "%'"; } else { $values .= "$wpdb->postmeta.meta_value $operator '%" . $v . "%'"; } } else { if ($values != '') { $values .= " $logic $wpdb->postmeta.meta_value $operator " . $v; } else { $values .= "$wpdb->postmeta.meta_value $operator " . $v; } } } } $values = $values == '' ? '0' : $values; $current_part = "($values)"; // String operations } else if ($operator === "NOT LIKE" || $operator === "LIKE") { $current_part = "($wpdb->postmeta.meta_value $operator '%" . $posted . "%')"; } else if ($operator === "ELIKE") { $current_part = "($wpdb->postmeta.meta_value LIKE '$posted')"; // Numeric operations or problematic stuff left } else { $current_part = "($wpdb->postmeta.meta_value $operator $posted )"; } // Finally add the current part to the parts array if ( $current_part != "") { if ( isset($data['allow_missing']) ) $allowance = $data['allow_missing']; else $allowance = $allow_cf_null; $parts[] = array($field, $current_part, $allowance); } } // The correct count is the unique fields count //$meta_count = count( $unique_fields ); $cf_select = ""; $cf_select_arr = array(); /** * NOTE 1: * With the previous NOT EXISTS(...) subquery solution the search would hang in some cases * when checking if empty values are allowed. No idea why though... * Eventually using separate sub-queries for each field is the best. * * NOTE 2: * COUNT(post_id) is a MUST in the nested IF() statement !! Otherwise the query will return empty rows, no idea why either.. */ foreach ( $parts as $k => $part ) { $def = $part[2] ? "( SELECT IF((meta_key IS NULL OR meta_value = ''), -1, COUNT(meta_id)) FROM $wpdb->postmeta WHERE $wpdb->postmeta.post_id = $post_id_field AND $wpdb->postmeta.meta_key='$field' LIMIT 1 ) = -1 OR" : ''; // Allowance $field = $part[0]; // Field name $qry = $part[1]; // Query condition $cf_select_arr[] = " ( $def ( SELECT COUNT(meta_id) as mtc FROM $wpdb->postmeta WHERE $wpdb->postmeta.post_id = $post_id_field AND $wpdb->postmeta.meta_key='$field' AND $qry GROUP BY meta_id ORDER BY mtc LIMIT 1 ) >= 1 )"; } if ( count($cf_select_arr) ) { // Connect them based on the meta logic $cf_select = "( ". implode( $args['_post_meta_logic'], $cf_select_arr ) . " )"; } return $cf_select; } protected function post_process() { $pageposts = is_array( $this->results ) ? $this->results : array(); $options = $this->options; $sd = $this->searchData; $s = $this->s; $_s = $this->_s; // No post processing is needed on non-ajax search if ( isset($options['non_ajax_search']) ) { $this->results = $pageposts; return $pageposts; } $performance_options = get_option('asl_performance'); $comp_options = wd_asl()->o['asl_compatibility']; foreach ( $pageposts as $k => $v ) { $r = &$pageposts[ $k ]; $r->title = w_isset_def( $r->title, null ); $r->content = w_isset_def( $r->content, null ); $r->image = w_isset_def( $r->image, null ); $r->author = w_isset_def( $r->author, null ); $r->date = w_isset_def( $r->date, null ); } /* Images, title, desc */ foreach ( $pageposts as $k => $v ) { // Let's simplify things $r = &$pageposts[ $k ]; $r->title = apply_filters( 'asl_result_title_before_prostproc', $r->title, $r->id ); $r->content = apply_filters( 'asl_result_content_before_prostproc', $r->content, $r->id ); $r->image = apply_filters( 'asl_result_image_before_prostproc', $r->image, $r->id ); $r->author = apply_filters( 'asl_result_author_before_prostproc', $r->author, $r->id ); $r->date = apply_filters( 'asl_result_date_before_prostproc', $r->date, $r->id ); // -------------------------- Woocommerce Fixes ----------------------------- // ---- URL FIX for WooCommerce product variations $wc_prod_var_o = null; // Reset for each loop if ( $r->post_type == 'product_variation' && function_exists('wc_get_product') ) { $wc_prod_var_o = wc_get_product( $r->id ); $r->link = $wc_prod_var_o->get_permalink(); } else { $r->link = get_permalink( $v->id ); } // -------------------------------------------------------------------------- // Filter it though WPML if ( isset( $options['wpml_lang'] ) && w_isset_def( $sd['wpml_compatibility'], 1 ) == 1 ) $r->link = apply_filters( 'wpml_permalink', $r->link, ASL_Helpers::escape( $options['wpml_lang'] ) ); $image_settings = $sd['image_options']; if ( $image_settings['show_images'] != 0 ) { $im = $this->getBFIimage( $r ); if ( $im != '' && strpos( $im, "mshots/v1" ) === false && w_isset_def($performance_options['image_cropping'], 0) == 1 ) { if ( w_isset_def( $image_settings['image_transparency'], 1 ) == 1 ) { $bfi_params = array( 'width' => $image_settings['image_width'], 'height' => $image_settings['image_height'], 'crop' => true ); } else { $bfi_params = array( 'width' => $image_settings['image_width'], 'height' => $image_settings['image_height'], 'crop' => true, 'color' => wpdreams_rgb2hex( $image_settings['image_bg_color'] ) ); } $r->image = bfi_thumb( $im, $bfi_params ); } else { $r->image = $im; } } switch($sd['titlefield']) { case '0': if ( isset($wc_prod_var_o) ) { $r->title = $wc_prod_var_o->get_title(); } else { $r->title = get_the_title($r->id); } break; case '1': if ( ASL_mb::strlen( $r->excerpt ) >= 200 ) { $r->title = wd_substr_at_word( $r->excerpt, 200 ); } else { $r->title = $r->excerpt; } break; case 'c__f': if ( $sd['titlefield_cf'] != '' ) { if ( $comp_options['use_acf_getfield'] == 1 && function_exists('get_field') ) { $mykey_values = get_field($sd['titlefield_cf'], $r->id, true); if (!is_null($mykey_values) && $mykey_values != '' && $mykey_values !== false ) { if (is_array($mykey_values)) { if (!is_object($mykey_values[0])) { $r->title = implode(', ', $mykey_values); break; } } else { $r->title = $mykey_values; break; } } } else { $mykey_values = get_post_custom_values($sd['titlefield_cf'], $r->id); if (isset($mykey_values[0])) { $r->title = $mykey_values[0]; break; } } } break; default: if ( isset($wc_prod_var_o) ) { $r->title = $wc_prod_var_o->get_title(); } else { $r->title = get_the_title($r->id); } break; } if ( ! isset( $sd['striptagsexclude'] ) ) { $sd['striptagsexclude'] = ""; } $_content = ''; switch ($sd['descriptionfield']) { case '1': if (function_exists('qtranxf_use')) { global $q_config; $r->excerpt = qtranxf_use($q_config['default_language'], $r->excerpt, false); } $_content = $r->excerpt; break; case '2': $_content = strip_tags(get_the_title($r->id), $sd['striptagsexclude']); break; case 'c__f': if ($sd['descriptionfield_cf'] != '') { if ($comp_options['use_acf_getfield'] == 1 && function_exists('get_field')) { $mykey_values = get_field($sd['descriptionfield_cf'], $r->id, true); if (!is_null($mykey_values) && $mykey_values != '' && $mykey_values !== false ) { if ( is_array($mykey_values) ) { if ( !is_object($mykey_values[0]) ) { $_content = implode(', ', $mykey_values); break; } } else { $_content = $mykey_values; break; } } } else { $mykey_values = get_post_custom_values($sd['descriptionfield_cf'], $r->id); if (isset($mykey_values[0])) { $_content = $mykey_values[0]; break; } } } break; default: //including option '0', alias content if ( function_exists( 'qtranxf_use' ) ) { global $q_config; $r->content = qtranxf_use($q_config['default_language'], $r->content, false); } // For product variations, do something special if ( isset($wc_prod_var_o) ) { $r->content = $wc_prod_var_o->get_description(); if ( $r->content == '') { $_pprod = wc_get_product($wc_prod_var_o->get_parent_id()); $r->content = $_pprod->get_description(); } } $_content = $r->content; break; } if ( $_content == "" && $r->content != '') { $_content = $r->content; } // Deal with the shortcodes here, for more accuracy if ( $sd['shortcode_op'] == "remove" ) { if ( $_content != "" ) { // Remove shortcodes, keep the content, really fast and effective method $_content = preg_replace("~(?:\[/?)[^\]]+/?\]~su", '', $_content); } } else { if ( $_content != "" ) { $_content = apply_filters( 'the_content', $_content ); } } // Remove styles and scripts $_content = preg_replace( array( '#(.*?)#is', '#(.*?)#is' ), '', $_content ); $_content = strip_tags( $_content ); // Get the words from around the search phrase, or just the description if ( w_isset_def($sd['description_context'], 1) == 1 && count( $_s ) > 0 && $s != '' ) { // Try for an exact match $_ex_content = $this->context_find( $_content, $s, floor($sd['descriptionlength'] / 6), $sd['descriptionlength'], 50000, true ); if ( $_ex_content === false ) { // No exact match, go with the first keyword $_content = $this->context_find( $_content, $_s[0], floor($sd['descriptionlength'] / 6), $sd['descriptionlength'], 50000 ); } else { $_content = $_ex_content; } } else if ( $_content != '' && ( strlen( $_content ) > $sd['descriptionlength'] ) ) { $_content = wd_substr_at_word($_content, $sd['descriptionlength']) . "..."; } $_content = wd_closetags( $_content ); $r->content = $_content; $r->title = apply_filters( 'asl_result_title_after_prostproc', $r->title, $r->id ); $r->content = apply_filters( 'asl_result_content_after_prostproc', $r->content, $r->id ); $r->image = apply_filters( 'asl_result_image_after_prostproc', $r->image, $r->id ); $r->author = apply_filters( 'asl_result_author_after_prostproc', $r->author, $r->id ); $r->date = apply_filters( 'asl_result_date_after_prostproc', $r->date, $r->id ); } /* !Images, title, desc */ $this->results = $pageposts; return $pageposts; } protected function group() { return $this->results; } /** * Fetches an image for BFI class * @param $post WP_POst Post object * @return string image URL */ protected function getBFIimage( $post ) { if ( ! isset( $post->image ) || $post->image == null ) { if ( ! isset( $post->id ) ) { return ""; } $im = ""; for ( $i = 1; $i < 6; $i ++ ) { switch ( $this->imageSettings[ 'image_source' . $i ] ) { case "featured": if ( $this->imageSettings['image_source_featured'] == "original" ) { $im = wp_get_attachment_url(get_post_thumbnail_id($post->id)); } else { $imx = wp_get_attachment_image_src( get_post_thumbnail_id($post->id), $this->imageSettings['image_source_featured'], false ); if ( $imx !== false && isset($imx[0]) ) $im = $imx[0]; } break; case "content": $content = apply_filters('the_content', get_post_field('post_content', $post->id)); $im = wpdreams_get_image_from_content( $content, 0 ); if ( is_multisite() ) { $im = str_replace( home_url(), network_home_url(), $im ); } break; case "excerpt": $excerpt = get_post_field('post_excerpt', $post->id); $im = wpdreams_get_image_from_content( $excerpt, 0 ); if ( is_multisite() ) { $im = str_replace( home_url(), network_home_url(), $im ); } break; case "screenshot": $im = 'http://s.wordpress.com/mshots/v1/' . urlencode( get_permalink( $post->id ) ) . '?w=' . $this->imageSettings['image_width'] . '&h=' . $this->imageSettings['image_height']; break; case "custom": if ( $this->imageSettings['image_custom_field'] != "" ) { $val = get_post_meta( $post->id, $this->imageSettings['image_custom_field'], true ); if ( $val != null && $val != "" ) { if ( is_numeric($val) ) { $im = wp_get_attachment_url( $val ); } else { $im = $val; } } } break; case "default": if ( $this->imageSettings['image_default'] != "" ) { $im = $this->imageSettings['image_default']; } break; default: $im = ""; break; } if ( $im != null && $im != '' ) { break; } } return $im; } else { return $post->image; } } /** * Returns the context of a phrase within a text. * Uses preg_split method to iterate through strings. * * @param $str string context * @param $needle string context * @param $context int length of the context * @param $maxlength int maximum length of the string in characters * @param $str_length_limit int source string maximum length * @param $false_on_no_match bool to return boolean false if no matches were found * @return string */ public function context_find($str, $needle, $context, $maxlength, $str_length_limit = 25000, $false_on_no_match = false) { $haystack = ' '.trim($str).' '; // To prevent memory overflow, we need to limit the hay to relatively low count $haystack = wd_substr_at_word(ASL_mb::strtolower($haystack), $str_length_limit); $needle = ASL_mb::strtolower($needle); if ( $needle == "" ) return $str; /** * This is an interesting issue. Turns out mb_substr($hay, $start, 1) is very ineffective. * the preg_split(..) method is far more efficient in terms of speed, however it needs much more * memory. In our case speed is the top priority. However to prevent memory overflow, the haystack * is reduced to 10000 characters (roughly 1500 words) first. * * Reference ticket: https://wp-dreams.com/forums/topic/search-speed/ * Speed tests: http://stackoverflow.com/questions/3666306/how-to-iterate-utf-8-string-in-php */ $chrArray = preg_split('//u', $haystack, -1, PREG_SPLIT_NO_EMPTY); $hay_length = count($chrArray) - 1; if ( $i = ASL_mb::strpos($haystack, $needle) ) { $start=$i; $end=$i; $spaces=0; while ($spaces < ((int) $context/2) && $start > 0) { $start--; if ($chrArray[$start] == ' ') { $spaces++; } } while ($spaces < ($context +1) && $end < $hay_length) { $end++; if ($chrArray[$end] == ' ') { $spaces++; } } while ($spaces < ($context +1) && $start > 0) { $start--; if ($chrArray[$start] == ' ') { $spaces++; } } $str_start = ($start - 1) < 0 ? 0 : ($start -1); $str_end = ($end - 1) < 0 ? 0 : ($end -1); $result = trim( ASL_mb::substr($str, $str_start, ($str_end - $str_start)) ); // Somewhere inbetween.. if ( $start != 0 && $end < $hay_length ) return "..." . $result . "..."; // Beginning if ( $start == 0 && $end < $hay_length ) return $result . "..."; // End if ( $start != 0 && $end == $hay_length ) return "..." . $result; // If it is too long, strip it if ( ASL_mb::strlen($result) > $maxlength) return wd_substr_at_word( $result, $maxlength ) . "..."; // Else, it is the whole return $result; } else { if ( $false_on_no_match ) return false; // If it is too long, strip it if ( ASL_mb::strlen($str) > $maxlength) return wd_substr_at_word( $str, $maxlength ) . "..."; return $str; } } /** * An empty function to override individual shortcodes, this must be public * * @return string */ public function return_empty_string() { return ""; } } }