240 lines
9.2 KiB
PHP
240 lines
9.2 KiB
PHP
<?php
|
|
|
|
/* --------------------------------------------------------------------
|
|
|
|
This file is part of Chevereto Free.
|
|
https://chevereto.com/free
|
|
|
|
(c) Rodolfo Berrios <rodolfo@chevereto.com>
|
|
|
|
For the full copyright and license information, please view the LICENSE
|
|
file that was distributed with this source code.
|
|
|
|
--------------------------------------------------------------------- */
|
|
|
|
namespace CHV;
|
|
|
|
use G;
|
|
use Exception;
|
|
|
|
class Search
|
|
{
|
|
public static $excluded = ['storage', 'ip'];
|
|
|
|
public function __construct()
|
|
{
|
|
$this->DBEngine = DB::queryFetchSingle("SHOW TABLE STATUS WHERE Name = '".DB::getTable('images')."';")['Engine'];
|
|
}
|
|
|
|
public function build()
|
|
{
|
|
// Validate type
|
|
if (!in_array($this->type, ['images', 'albums', 'users'])) {
|
|
throw new SearchException('Invalid search type in '.__METHOD__, 100);
|
|
}
|
|
|
|
// Advanced search handle
|
|
$as_handle = ['as_q' => null, 'as_epq' => null, 'as_oq' => null, 'as_eq' => null, 'as_cat' => 'category'];
|
|
$as_handle_admin = ['as_stor' => 'storage', 'as_ip' => 'ip'];
|
|
if ($this->requester['is_content_manager']) {
|
|
$as_handle = array_merge($as_handle, $as_handle_admin);
|
|
}
|
|
// Build "command like" q
|
|
foreach ($as_handle as $k => $v) {
|
|
if (G\check_value($this->request[$k]) and strpos($this->q, $v) === false) {
|
|
$this->q .= ' '.(!is_null($v) ? $v.':' : '').$this->request[$k];
|
|
}
|
|
}
|
|
|
|
// Clean q
|
|
$this->q = trim(preg_replace(['#\"+#', '#\'+#'], ['"', '\''], $this->q));
|
|
|
|
$search_op = $this->handleSearchOperators($this->q, $this->requester['is_content_manager'] == true);
|
|
|
|
// Make q pretty
|
|
$this->q = null;
|
|
foreach ($search_op as $operator) {
|
|
$this->q .= implode(' ', $operator).' ';
|
|
}
|
|
if ($this->q) {
|
|
$this->q = preg_replace('/\s+/', ' ', trim($this->q));
|
|
}
|
|
|
|
// Get the full text match query
|
|
$q_match = $this->q; // Note... Why MyISAM has issues with exclusions like -700 ?
|
|
$seach_binds = [];
|
|
|
|
foreach ($search_op['named'] as $v) {
|
|
$q_match = trim(preg_replace('/\s+/', ' ', str_replace($v, '', $q_match)));
|
|
|
|
$op = explode(':', $v);
|
|
if (!in_array($op[0], ['category', 'ip', 'storage'])) {
|
|
continue;
|
|
}
|
|
|
|
switch ($this->type) {
|
|
case 'images':
|
|
switch ($op[0]) {
|
|
case 'category':
|
|
$search_op_wheres[] = 'category_url_key = :category';
|
|
$seach_binds[] = ['param' => ':category', 'value' => $op[1]];
|
|
break;
|
|
|
|
case 'ip':
|
|
$search_op_wheres[] = 'image_uploader_ip LIKE REPLACE(:ip, "*", "%")';
|
|
$seach_binds[] = ['param' => ':ip', 'value' => G\str_replace_first('ip:', null, $this->q)];
|
|
break;
|
|
|
|
case 'storage':
|
|
if (!filter_var($op[1], FILTER_VALIDATE_INT) and !in_array($op[1], ['local', 'external'])) {
|
|
break;
|
|
}
|
|
$storage_operator_clause = [
|
|
$op[1] => '= :storage_id',
|
|
'local' => 'IS NULL',
|
|
'external' => 'IS NOT NULL',
|
|
];
|
|
|
|
if (filter_var($op[1], FILTER_VALIDATE_INT)) {
|
|
$seach_binds[] = ['param' => ':storage_id', 'value' => $op[1]];
|
|
}
|
|
|
|
$search_op_wheres[] = 'image_storage_id '.($storage_operator_clause[$op[1]]);
|
|
break;
|
|
}
|
|
break;
|
|
case 'albums':
|
|
case 'users':
|
|
switch ($op[0]) {
|
|
case 'ip':
|
|
$seach_binds[] = ['param' => ':ip', 'value' => G\str_replace_first('ip:', null, $this->q)];
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($q_match) {
|
|
$q_value = $q_match;
|
|
if ($this->DBEngine == 'InnoDB') {
|
|
$q_value = trim($q_value, '><');
|
|
}
|
|
$seach_binds[] = ['param' => ':q', 'value' => $q_value];
|
|
}
|
|
|
|
$this->binds = $seach_binds;
|
|
$this->op = $search_op;
|
|
$wheres = null;
|
|
|
|
$q_replacement = $this->DBEngine == 'InnoDB' ? "':q'" : ':q';
|
|
|
|
switch ($this->type) {
|
|
case 'images':
|
|
if ($q_match) {
|
|
$wheres = 'WHERE MATCH(`image_name`,`image_title`,`image_description`,`image_original_filename`) AGAINST(:q IN BOOLEAN MODE)';
|
|
}
|
|
if (is_array($search_op_wheres) && count($search_op_wheres) > 0) {
|
|
$wheres .= (is_null($wheres) ? 'WHERE ' : ' AND ').implode(' AND ', $search_op_wheres);
|
|
}
|
|
break;
|
|
case 'albums':
|
|
if (!$seach_binds) {
|
|
$wheres = 'WHERE album_id < 0';
|
|
} else {
|
|
$wheres = ($op[0] == 'ip' ? 'album_creation_ip LIKE REPLACE(:ip, "*", "%")' : 'WHERE MATCH(`album_name`,`album_description`) AGAINST(:q)');
|
|
}
|
|
break;
|
|
case 'users':
|
|
if (!$seach_binds) {
|
|
$wheres = 'WHERE user_id < 0';
|
|
} else {
|
|
if ($op[0] == 'ip') {
|
|
$wheres = 'user_registration_ip LIKE REPLACE(:ip, "*", "%")';
|
|
} else {
|
|
$clauses = [
|
|
'name_username' => 'WHERE MATCH(`user_name`,`user_username`) AGAINST(:q)',
|
|
'email' => '`user_email` LIKE CONCAT("%", :q, "%")',
|
|
];
|
|
if ($this->requester['is_content_manager']) {
|
|
$pos = strpos($this->q, '@');
|
|
if ($pos !== false) {
|
|
if (preg_match_all('/\s+/', $this->q)) {
|
|
$wheres = $clauses['name_username'];
|
|
if ($clauses['email']) {
|
|
$wheres .= ' OR '.$clauses['email'];
|
|
}
|
|
} else {
|
|
$wheres = $clauses['email'];
|
|
}
|
|
} else {
|
|
$wheres = $clauses['name_username'];
|
|
}
|
|
} else {
|
|
$wheres = $clauses['name_username'];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
$this->wheres = $wheres;
|
|
|
|
$this->display = [
|
|
'type' => $this->type,
|
|
'q' => $this->q,
|
|
'd' => strlen($this->q) >= 25 ? (substr($this->q, 0, 22).'...') : $this->q,
|
|
];
|
|
}
|
|
|
|
protected function handleSearchOperators($q, $full = true)
|
|
{
|
|
$return = [];
|
|
$operators = ['any' => [], 'exact_phrases' => [], 'excluded' => [], 'named' => []];
|
|
|
|
$raw_ops = [];
|
|
$raw_regex = [
|
|
'named' => '[\S]+\:[\S]+', // take all the like:this operators
|
|
'quoted' => '-*[\"\']+.+[\"\']+', // take all the "quoted stuff" "like" "this, one"
|
|
'spaced' => '\S+', // Take all the space separated stuff
|
|
];
|
|
|
|
foreach ($raw_regex as $k => $v) {
|
|
if ($k == 'spaced') {
|
|
$q = str_replace(',', '', $q); // Don't need commas
|
|
}
|
|
if (preg_match_all('/'.$v.'/', $q, $match)) {
|
|
$raw_ops[$k] = $match[0];
|
|
foreach ($match[0] as $v) {
|
|
switch ($k) {
|
|
case 'named':
|
|
if (!$full) {
|
|
$named_operator = explode(':', $v);
|
|
if (in_array($named_operator[0], self::$excluded)) {
|
|
continue 2;
|
|
}
|
|
}
|
|
$operators[$k][] = $v;
|
|
break;
|
|
default:
|
|
if (0 === strpos($v, '-')) {
|
|
$operators['excluded'][] = $v;
|
|
} elseif (0 === strpos($v, '"')) {
|
|
$operators['exact_phrases'][] = $v;
|
|
} else {
|
|
$operators['any'][] = $v;
|
|
}
|
|
break;
|
|
}
|
|
$q = trim(preg_replace('/\s+/', ' ', str_replace($v, '', $q)));
|
|
}
|
|
}
|
|
}
|
|
|
|
return $operators;
|
|
}
|
|
}
|
|
|
|
class SearchException extends Exception
|
|
{
|
|
}
|