/** * Allows for category groups filtering: (1|2|3) && (4|5|6) * * @access public * @return void */ public function filter($entry_ids) { // -------------------------------------- // See if there are groups present, with correct values // -------------------------------------- $groups = array_filter($this->params->get_prefixed('category:'), 'low_param_is_numeric'); // -------------------------------------- // Bail out if there are no groups // -------------------------------------- if (empty($groups)) { return $entry_ids; } // -------------------------------------- // Log it // -------------------------------------- $this->_log('Applying ' . __CLASS__); // -------------------------------------- // Loop through groups, compose SQL // -------------------------------------- foreach ($groups as $key => $val) { // Prep the value $val = $this->params->prep($key, $val); // Get the parameter list($ids, $in) = low_explode_param($val); // Match all? $all = (bool) strpos($val, '&'); // One query per group ee()->db->select('entry_id')->from('category_posts')->{$in ? 'where_in' : 'where_not_in'}('cat_id', $ids); // Limit by already existing ids if ($entry_ids) { ee()->db->where_in('entry_id', $entry_ids); } // Do the having-trick to account for *all* given entry ids if ($in && $all) { ee()->db->select('COUNT(*) AS num')->group_by('entry_id')->having('num', count($ids)); } // Execute query $query = ee()->db->get(); // And get the entry ids $entry_ids = low_flatten_results($query->result_array(), 'entry_id'); $entry_ids = array_unique($entry_ids); // Bail out if there aren't any matches if (is_array($entry_ids) && empty($entry_ids)) { break; } } return $entry_ids; }
function low_merge_params($haystack, $needles, $as_param = FALSE) { // Prep the haystack if (!is_array($haystack)) { // Explode the param, forget about the 'not ' list($haystack, ) = low_explode_param($haystack); } // Prep the needles if (!is_array($needles)) { list($needles, $in) = low_explode_param($needles); } else { $in = TRUE; } // Choose function to merge $method = $in ? 'array_intersect' : 'array_diff'; // Do the merge thing $merged = $method($haystack, $needles); // Change back to parameter syntax if necessary if ($as_param) { $merged = low_implode_param($merged); } return $merged; }
/** * Search parameters for (parents|children):field params and return set of ids that match it * * @access public * @return void */ public function filter($entry_ids) { // -------------------------------------- // Check prefixed parameters needed // -------------------------------------- $rels = array_filter(array_merge($this->params->get_prefixed('parent:'), $this->params->get_prefixed('child:')), 'low_param_is_numeric'); // -------------------------------------- // Don't do anything if nothing's there // -------------------------------------- if (empty($rels)) { return $entry_ids; } // -------------------------------------- // Log it // -------------------------------------- $this->_log('Applying ' . __CLASS__); // -------------------------------------- // Loop through relationships // -------------------------------------- foreach ($rels as $key => $val) { // List out match list($type, $field) = explode(':', $key, 2); // Get the field id, skip if non-existent if (!($field_id = $this->_get_field_id($field))) { continue; } // Prep the value $val = $this->params->prep($key, $val); // Get the parameter list($ids, $in) = low_explode_param($val); // Match all? $all = (bool) strpos($val, '&'); // Init vars $rel_ids = $table = FALSE; $get_children = $type == 'parent'; // Native relationship field if ($this->_is_rel_field($field)) { // Account for new EE rels $prefix = version_compare(APP_VER, '2.6.0', '<') ? 'rel_' : ''; // Set the table & attributes $table = 'relationships'; $select = $get_children ? $prefix . 'child_id' : $prefix . 'parent_id'; $where = $get_children ? $prefix . 'parent_id' : $prefix . 'child_id'; } elseif ($this->_is_playa_field($field_id)) { // Set the table $table = 'playa_relationships'; $select = $get_children ? 'child_entry_id' : 'parent_entry_id'; $where = $get_children ? 'parent_entry_id' : 'child_entry_id'; // Focus on specific field ee()->db->where('parent_field_id', $field_id); } // Execute query if ($table) { ee()->db->select($select . ' AS entry_id')->from($table)->{$in ? 'where_in' : 'where_not_in'}($where, $ids); // Limit by already existing ids if ($entry_ids) { ee()->db->where_in($select, $entry_ids); } // Do the having-trick to account for *all* given entry ids if ($in && $all) { ee()->db->select('COUNT(*) AS num')->group_by($select)->having('num', count($ids)); } // Execute query $query = ee()->db->get(); // And get the entry ids $entry_ids = low_flatten_results($query->result_array(), 'entry_id'); $entry_ids = array_unique($entry_ids); // Bail out if there aren't any matches if (is_array($entry_ids) && empty($entry_ids)) { break; } } } return $entry_ids; }
/** * Allows for tag filtering: (1|2|3) && (4|5|6) * * @access public * @return void */ public function filter($entry_ids) { // ------------------------------------------- // Make sure addons-library is loaded // ------------------------------------------- ee()->load->library('addons'); // ------------------------------------------- // Solspace Tag or DevDemon Tagger? // ------------------------------------------- if (ee()->addons->is_package('tag')) { $tables = array('tag_tags', 'tag_entries'); } elseif (ee()->addons->is_package('tagger')) { $tables = array('tagger', 'tagger_links'); } // -------------------------------------- // See if there are tag params present // -------------------------------------- $tag_names = $this->params->get_prefixed('tag_name'); $tag_ids = $this->params->get_prefixed('tag_id'); // -------------------------------------- // Bail out if there are no tags // -------------------------------------- if (empty($tables) || empty($tag_names) && empty($tag_ids)) { return $entry_ids; } // -------------------------------------- // Log it // -------------------------------------- $this->_log('Applying ' . __CLASS__); // ------------------------------------------- // Check tag names and convert to tag IDs // ------------------------------------------- if ($tag_names) { $unique_tags = array(); foreach ($tag_names as $key => $val) { // Get the tags list($tags, $in) = low_explode_param($val); $unique_tags = array_merge($unique_tags, $tags); } // Remove duplicates and convert $unique_tags = array_unique($unique_tags); $unique_tags = array_map(array($this, '_convert_tag'), $unique_tags); // Get IDs for unique tags $query = ee()->db->select('tag_id, tag_name')->from($tables[0])->where_in('site_id', $this->params->site_ids())->where_in('tag_name', $unique_tags)->get(); // clean up unset($unique_tags); // Get tag map: [tag name] => tag_id $tag_map = low_flatten_results($query->result_array(), 'tag_id', 'tag_name'); // Now, loop through original tags thing and convert to tag IDs foreach ($tag_names as $key => $val) { // Initiate tag ids $ids = array(); // Read parameter value list($tags, $in) = low_explode_param($val); // Loop through tags and map them to IDs foreach ($tags as $tag) { $tag = $this->_convert_tag($tag); if (isset($tag_map[$tag])) { $ids[] = $tag_map[$tag]; } } if ($ids) { // Check separator and implode back to parameter $sep = strpos($val, '&') === FALSE ? '|' : '&'; $str = implode($sep, $ids); // Add negator back if (!$in) { $str = 'not ' . $ids; } // Add final parameter string to IDs $tag_ids[$key] = $str; } } } // -------------------------------------- // Get channel IDs before starting the query // -------------------------------------- $channel_ids = ee()->low_search_collection_model->get_channel_ids($this->params->get('collection')); // -------------------------------------- // Loop through groups, compose SQL // -------------------------------------- foreach ($tag_ids as $key => $val) { // Prep the value $val = $this->params->prep($key, $val); // Get the parameter list($ids, $in) = low_explode_param($val); // Match all? $all = (bool) strpos($val, '&'); // One query per group ee()->db->distinct()->select('entry_id')->from($tables[1])->where_in('site_id', $this->params->site_ids())->{$in ? 'where_in' : 'where_not_in'}('tag_id', $ids); // Limit by already existing ids if ($entry_ids) { ee()->db->where_in('entry_id', $entry_ids); } // Limit by channel ID if ($channel_ids) { ee()->db->where_in('channel_id', $channel_ids); } // Do the having-trick to account for *all* given entry ids if ($in && $all) { ee()->db->select('COUNT(*) AS num')->group_by('entry_id')->having('num', count($ids)); } // Execute query $query = ee()->db->get(); // And get the entry ids $entry_ids = low_flatten_results($query->result_array(), 'entry_id'); // Bail out if there aren't any matches if (is_array($entry_ids) && empty($entry_ids)) { break; } } return $entry_ids; }
/** * Get collection IDs by parameter on a site ID * * @access public * @param int Site ID * @return array */ public function get_by_param($param) { list($ids, $in) = low_explode_param($param); $attr = low_array_is_numeric($ids) ? $this->pk() : 'collection_name'; return $this->_get_by_attr($ids, $attr, $in); }
/** * Display entries in order * * @access public * @return string */ public function entries() { // Set low_reorder param so extension kicks in ee()->TMPL->tagparams['low_reorder'] = 'yes'; // -------------------------------------- // Initiate set to get set_id, cat_id and entry_ids // -------------------------------------- $this->_init_set(); $this->_prep_no_results(); $this->_remove_var_prefix(); // -------------------------------------- // Cache the set_id and cat_it // -------------------------------------- low_set_cache($this->package, 'set_id', $this->set_id); low_set_cache($this->package, 'cat_id', $this->cat_id); // -------------------------------------- // Check fallback parameter // -------------------------------------- $fallback = ee()->TMPL->fetch_param('fallback') == 'yes' ? '_channel_entries' : '_empty_set'; // -------------------------------------- // Check if that results into entry_ids // -------------------------------------- if (empty($this->entry_ids)) { return $this->{$fallback}(); } // -------------------------------------- // Check existing entry_id parameter // -------------------------------------- if ($entry_ids = ee()->TMPL->fetch_param('entry_id')) { $this->_log('entry_id parameter found, filtering ordered entries accordingly'); // Get the parameter value list($ids, $in) = low_explode_param($entry_ids); // Either remove $ids from $entry_ids OR limit $entry_ids to $ids $method = $in ? 'array_intersect' : 'array_diff'; // Get list of entry ids that should be listed $this->entry_ids = $method($this->entry_ids, $ids); } // If that results in empty ids, bail out again if (empty($this->entry_ids)) { return $this->{$fallback}(); } // -------------------------------------- // Add fixed_order to parameters // -------------------------------------- $orderby = ee()->TMPL->fetch_param('orderby'); $param = empty($orderby) ? 'fixed_order' : 'entry_id'; $this->set['parameters'][$param] = implode('|', $this->entry_ids); // -------------------------------------- // Set template parameters // -------------------------------------- // Check whether to force template params or not $force = ee()->TMPL->fetch_param('force_set_params', 'no') == 'yes'; // Set the params $this->_set_template_parameters($force); // -------------------------------------- // Use channel module to generate entries // -------------------------------------- return $this->_channel_entries(); }
/** * Create a list of where-clauses for given search parameters * * @access private * @param array * @param string * @return array */ private function _search_where($search = array(), $prefix = '') { // -------------------------------------- // Initiate where array // -------------------------------------- $where = array(); // -------------------------------------- // Get field ids for given search fields // -------------------------------------- $fields = $this->_get_channel_fields(); // -------------------------------------- // Loop through search filters and create where clause accordingly // -------------------------------------- foreach ($search as $key => $val) { // Skip non-existent fields if (!isset($fields[$key])) { continue; } // Initiate some vars $exact = $all = FALSE; $field = $prefix . 'field_id_' . $fields[$key]; // Exact matches if (substr($val, 0, 1) == '=') { $val = substr($val, 1); $exact = TRUE; } // All items? -> && instead of | if (strpos($val, '&&') !== FALSE) { $all = TRUE; } // Convert parameter to bool and array list($items, $in) = low_explode_param($val); // Init sql for where clause $sql = array(); // Loop through each sub-item of the filter an create sub-clause foreach ($items as $item) { // Convert IS_EMPTY constant to empty string $empty = $item == 'IS_EMPTY'; $item = str_replace('IS_EMPTY', '', $item); // greater/less than matches if (preg_match('/^([<>]=?)(\\d+)$/', $item, $matches)) { $gtlt = $matches[1]; $item = $matches[2]; } else { $gtlt = FALSE; } // whole word? Regexp search if (substr($item, -2) == '\\W') { $operand = $in ? 'REGEXP' : 'NOT REGEXP'; $item = '[[:<:]]' . preg_quote(substr($item, 0, -2)) . '[[:>:]]'; } else { // Not a whole word if ($exact || $empty) { // Use exact operand if empty or = was the first char in param $operand = $in ? '=' : '!='; $item = "'" . ee()->db->escape_str($item) . "'"; } elseif ($gtlt !== FALSE) { $operand = $gtlt; $item = "'" . ee()->db->escape_str($item) . "'"; } else { // Use like operand in all other cases $operand = $in ? 'LIKE' : 'NOT LIKE'; $item = "'%" . ee()->db->escape_str($item) . "%'"; } } // Add sub-clause to this statement $sql[] = sprintf("(%s %s %s)", $field, $operand, $item); } // Inclusive or exclusive $andor = $all ? ' AND ' : ' OR '; // Add complete clause to where array $where[] = '(' . implode($andor, $sql) . ')'; } // -------------------------------------- // Where now contains a list of clauses // -------------------------------------- return $where; }
/** * Catch search form submission * * @access public * @return void */ public function catch_search() { // -------------------------------------- // Initiate data array; will be encrypted // and put in the URI later // -------------------------------------- $data = array(); if ($params = ee()->input->post('params')) { $data = low_search_decode($params); } // -------------------------------------- // Check other data // -------------------------------------- foreach (array_merge($_GET, $_POST) as $key => $val) { // Keys to skip if (in_array($key, array('ACT', 'XID', 'csrf_token', 'params', 'site_id'))) { continue; } // Add post var to data $data[$key] = is_array($val) ? implode('|', array_filter($val, 'low_not_empty')) : $val; } // -------------------------------------- // Clean up the data array // -------------------------------------- $data = array_filter($data, 'low_not_empty'); // -------------------------------------- // 'low_search_catch_search' extension hook // - Check incoming data and optionally change it // -------------------------------------- if (ee()->extensions->active_hook('low_search_catch_search') === TRUE) { $data = ee()->extensions->call('low_search_catch_search', $data); if (ee()->extensions->end_script === TRUE) { return; } // Clean again to be sure $data = array_filter($data, 'low_not_empty'); } // -------------------------------------- // Check for required parameter // -------------------------------------- if (isset($data['required'])) { // Init errors $errors = array(); // Get required as array list($required, $in) = low_explode_param($data['required']); foreach ($required as $req) { // Break out when empty // @TODO: enhance for multiple fields if (empty($data[$req])) { $errors[] = $req; } } // Go back if ($errors) { ee()->session->set_flashdata('errors', $errors); $this->_go_back('fields_missing'); } // remove from data unset($data['required']); } // -------------------------------------- // Optionally log search query // -------------------------------------- $this->_log_search($data); // -------------------------------------- // Result URI: result page & cleaned up data, encoded // -------------------------------------- $url = $this->_create_url($data, '&'); // -------------------------------------- // Redirect to result page // -------------------------------------- // Empty out flashdata to avoid serving of JSON for ajax request if (AJAX_REQUEST && count(ee()->session->flashdata)) { ee()->session->flashdata = array(); } ee()->functions->redirect($url); }
/** * Get WHERE clause for given field and parameter value * * @access private * @return void */ public function _get_where($field, $val) { // Initiate some vars $exact = $all = $starts = $ends = FALSE; // Exact matches if (substr($val, 0, 1) == '=') { $val = substr($val, 1); $exact = TRUE; } // Starts with matches if (substr($val, 0, 1) == '^') { $val = substr($val, 1); $starts = TRUE; } // Ends with matches if (substr($val, -1) == '$') { $val = rtrim($val, '$'); $ends = TRUE; } // All items? -> && instead of | if (strpos($val, '&&') !== FALSE) { $all = TRUE; $val = str_replace('&&', '|', $val); } // Convert parameter to bool and array list($items, $in) = low_explode_param($val); // Init sql for where clause $sql = array(); // Loop through each sub-item of the filter an create sub-clause foreach ($items as $item) { // Convert IS_EMPTY constant to empty string $empty = $item == 'IS_EMPTY'; $item = str_replace('IS_EMPTY', '', $item); // whole word? Regexp search if (substr($item, -2) == '\\W') { $operand = $in ? 'REGEXP' : 'NOT REGEXP'; $item = preg_quote(substr($item, 0, -2)); $item = str_replace("'", "\\'", $item); $item = "'[[:<:]]{$item}[[:>:]]'"; } else { if (preg_match('/^([<>]=?)([\\d\\.]+)$/', $item, $match)) { // Numeric operator! $operand = $match[1]; $item = $match[2]; } elseif ($exact || $empty || $starts && $ends) { // Use exact operand if empty or = was the first char in param $operand = $in ? '=' : '!='; $item = "'" . ee()->db->escape_str($item) . "'"; } else { // Use like operand in all other cases $operand = $in ? 'LIKE' : 'NOT LIKE'; $item = '%' . ee()->db->escape_like_str($item) . '%'; // Allow for starts/ends with matching if ($starts) { $item = ltrim($item, '%'); } if ($ends) { $item = rtrim($item, '%'); } $item = "'{$item}'"; } } // Add sub-clause to this statement $sql[] = sprintf("(%s %s %s)", $field, $operand, $item); } // Inclusive or exclusive $andor = $all ? ' AND ' : ' OR '; // Get complete clause, with parenthesis and everything $where = count($sql) == 1 ? $sql[0] : '(' . implode($andor, $sql) . ')'; return $where; }
/** * Check if a value is present in a parameter */ public function in_param($val, $param) { $it = FALSE; if ($param = $this->get($param)) { list($fields, $in) = low_explode_param($param); $it = in_array($val, $fields); } return $it; }
/** * Allows for keywords="" parameter, along with its associated params * * @access private * @return void */ public function filter($entry_ids) { // -------------------------------------- // Log it // -------------------------------------- $this->_log('Applying ' . __CLASS__); // -------------------------------------- // Set internal collections array based on param // -------------------------------------- $this->_collections = ($cols = $this->params->get('collection')) ? ee()->low_search_collection_model->get_by_param($cols) : array(); // -------------------------------------- // Check validity of search mode and loose ends // -------------------------------------- $this->_search_mode(); $this->_loose_ends(); // -------------------------------------- // Check keywords and set the search terms // -------------------------------------- $this->_set_terms(); // -------------------------------------- // Only perform actual search if keywords are given // -------------------------------------- if (empty($this->_terms)) { $this->_log('No keyword search'); $this->_prep_params(); return $entry_ids; } // -------------------------------------- // Reset results // -------------------------------------- $this->_results = array(); // -------------------------------------- // Begin composing query // -------------------------------------- ee()->db->select($this->_fulltext ? "entry_id, collection_id, MATCH(index_text) AGAINST('{$this->_query()}') AS score" : 'entry_id, collection_id, index_text', FALSE)->from(ee()->low_search_index_model->table()); // -------------------------------------- // Filters used by both searches // -------------------------------------- // Limit query by collection if ($this->_collections) { ee()->db->where_in('collection_id', low_flatten_results($this->_collections, 'collection_id')); } // Limit query by site if ($site_ids = $this->params->site_ids()) { ee()->db->where_in('site_id', array_values($site_ids)); } // If entry ids were given, limit to those if ($entry_ids) { ee()->db->where_in('entry_id', $entry_ids); } // Add where clause ee()->db->where($this->_fulltext ? $this->_fulltext_keywords() : $this->_fallback_keywords(), NULL, FALSE); if ($this->_fulltext) { // Actual fulltext search ee()->db->order_by('score', 'desc'); // Limit by min_score if ($this->params->get('min_score')) { list($score, $include) = $this->_min_score(); $oper = $include ? '>=' : '>'; ee()->db->having("score {$oper}", $score); } } // -------------------------------------- // Extra search stuff // -------------------------------------- if ($add_to_query = $this->params->get_prefixed('keywords-query:', TRUE)) { foreach ($add_to_query as $field => $val) { if (ee()->db->field_exists($field, ee()->low_search_index_model->table())) { list($items, $in) = low_explode_param($val); ee()->db->{$in ? 'where_in' : 'where_not_in'}($field, $val); } else { $this->_log("Field {$field} does not exist in " . ee()->low_search_index_model->table()); } } } // -------------------------------------- // Perform the search // -------------------------------------- $this->_log('Starting search ' . ($this->_fulltext ? '(fulltext)' : '(fallback)')); $query = ee()->db->get(); // -------------------------------------- // If the search had no results, return no results bit // -------------------------------------- if ($query->num_rows == 0) { $this->_log('Searched but found nothing. Returning no results.'); return array(); } // -------------------------------------- // If we do have results, continue // -------------------------------------- $this->_results = $this->_fulltext ? low_associate_results($query->result_array(), 'entry_id') : $this->_get_fallback_results($query); // Bail out if no entry falls above the min_score threshold if (empty($this->_results)) { $this->_log('No valid results after scoring'); return array(); } // -------------------------------------- // Modify scores for each collection // -------------------------------------- if ($modifiers = array_unique(low_flatten_results($this->_collections, 'modifier'))) { if (!(count($modifiers) == 1 && $modifiers[0] == 1.0)) { $this->_log('Applying collection modifier to search results'); foreach ($this->_results as &$row) { if ($mod = (double) $this->_collections[$row['collection_id']]['modifier']) { $row['score'] = $row['score'] * $mod; } } } } // ------------------------------------- // 'low_search_modify_score' hook. // - Modify scoring for keyword searches // ------------------------------------- if (ee()->extensions->active_hook('low_search_modify_score') === TRUE) { $this->_results = ee()->extensions->call('low_search_modify_score', $this->_results); if (empty($this->_results) || ee()->extensions->end_script === TRUE) { return array(); } } // -------------------------------------- // Sort by score // -------------------------------------- uasort($this->_results, 'low_by_score'); // -------------------------------------- // Add results to cache, so extension can look this up // -------------------------------------- return array_keys($this->_results); }