<?php
/*
* This file is part of Chevereto.
*
* (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 Chevereto\Legacy\Classes;
use Exception;
use function Chevereto\Legacy\G\str_replace_first;
class Search
{
public array $display;
public static array $excluded = ['storage', 'ip'];
public string $DBEngine = 'InnoDB';
public string $wheres;
public string $q = '';
public string $type;
public array $request;
public array $requester;
public array $binds;
public array $op;
public function build(): void
{
if (!in_array($this->type, ['images', 'albums', 'users'], true)) {
throw new Exception('Invalid search type', 600);
}
$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'] ?? false) {
$as_handle = array_merge($as_handle, $as_handle_admin);
}
$this->q = str_replace('@', '', $this->q);
foreach ($as_handle as $k => $v) {
if (isset($this->request[$k]) && $this->request[$k] !== '') {
if ($k === 'as_epq') {
$this->q .= ' "' . $this->request[$k] . '"';
} else {
$this->q .= ' '
. (isset($v) ? ($v . ':') : '')
. $this->request[$k];
}
}
}
$this->q = trim(
preg_replace(
['#"+#', '#\'+#'],
['"', '\''],
$this->q ?? ''
)
);
$exact_phrase = '';
$is_exact_phrase_only = false;
if (isset($this->request['as_epq']) && $this->request['as_epq'] !== '') {
$exact_phrase = $this->request['as_epq'];
$is_exact_phrase_only = true;
$search_binds = [];
} elseif (isset($this->request['q'])) {
$clean_q = html_entity_decode(trim($this->request['q']), ENT_QUOTES | ENT_HTML5);
if (preg_match('/^"(.*?)"$/', $clean_q, $matches)) {
$exact_phrase = $matches[1];
$is_exact_phrase_only = true;
$search_binds = [];
}
}
$search_op = $this->handleSearchOperators($this->q, $this->requester['is_content_manager'] ?? false);
$this->q = '';
foreach ($search_op as $operator) {
$this->q .= implode(' ', $operator) . ' ';
}
if ($this->q !== '') {
$this->q = trim($this->q);
$this->q = preg_replace('/\s+/', ' ', $this->q) ?? '';
}
$this->q ??= '';
$q_match = $this->q;
$search_binds ??= [];
$search_op_wheres = [];
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'], true)) {
continue;
}
switch ($this->type) {
case 'albums':
case 'users':
case 'images':
if ($op[0] === 'ip') {
$search_binds[] = ['param' => ':ip', 'value' => str_replace_first('ip:', '', $this->q)];
}
break;
}
}
if ($q_match !== '' && !$is_exact_phrase_only) {
$q_value = $q_match;
if ($this->DBEngine === 'InnoDB') {
$q_value = trim($q_value, '><');
}
$search_binds[] = ['param' => ':q', 'value' => $q_value];
$q_strip = preg_replace('/(-[\S]+|".+?")/u', '', $q_match);
$search_binds[] = ['param' => ':like_q', 'value' => '%' . $q_strip . '%'];
}
$this->binds = $search_binds;
$this->op = $search_op;
switch ($this->type) {
case 'albums':
if ($is_exact_phrase_only) {
$this->binds = [[
'param' => ':phrase',
'value' => '%' . $exact_phrase . '%',
]];
$this->wheres = 'WHERE album_name LIKE :phrase';
} elseif (empty($search_binds)) {
$this->wheres = 'WHERE album_id < 0';
} elseif (($op[0] ?? null) === 'ip') {
$this->wheres = 'album_creation_ip LIKE REPLACE(:ip, "*", "%")';
} else {
$this->wheres = 'WHERE (
MATCH(`album_name`,`album_description`) AGAINST (:q)
OR album_name LIKE :like_q
OR album_description LIKE :like_q
)';
}
break;
case 'images':
if ($is_exact_phrase_only) {
$this->binds = [[
'param' => ':phrase',
'value' => '%' . $exact_phrase . '%',
]];
$this->wheres = 'WHERE (
image_name LIKE :phrase
OR image_title LIKE :phrase
OR image_description LIKE :phrase
OR image_original_filename LIKE :phrase
)';
} elseif ($q_match !== '') {
$this->wheres = 'WHERE (
MATCH(`image_name`,`image_title`,`image_description`,`image_original_filename`) AGAINST (:q IN BOOLEAN MODE)
OR BINARY image_name LIKE BINARY :like_q
OR BINARY image_title LIKE BINARY :like_q
OR BINARY image_description LIKE BINARY :like_q
OR BINARY image_original_filename LIKE BINARY :like_q
)';
}
if ($search_op_wheres !== []) {
$this->wheres .= ($this->wheres === '' ? 'WHERE ' : ' AND ') . implode(' AND ', $search_op_wheres);
}
break;
default:
$this->wheres = '';
break;
}
$this->display = [
'type' => $this->type,
'q' => $this->q,
'd' => strlen($this->q) >= 25 ? (substr($this->q, 0, 22) . '...') : $this->q,
];
}
protected function handleSearchOperators(string $q, bool $full = true): array
{
$operators = [
'any' => [],
'exact_phrases' => [],
'excluded' => [],
'named' => [],
];
$raw_regex = [
'named' => '[\S]+\:[\S]+',
'quoted' => '-*[\"\']+.+[\"\']+',
'spaced' => '\S+',
];
foreach ($raw_regex as $k => $v) {
if ($k === 'spaced') {
$q = str_replace(',', '', $q);
}
if (preg_match_all('/' . $v . '/', $q, $match)) {
foreach ($match[0] as $qMatch) {
switch ($k) {
case 'named':
if (!$full) {
$named_operator = explode(':', $qMatch);
if (in_array($named_operator[0], self::$excluded, false)) {
continue 2;
}
}
$operators[$k][] = $qMatch;
break;
default:
if (strpos($qMatch, '-') === 0) {
$operators['excluded'][] = $qMatch;
} elseif (strpos($qMatch, '"') === 0) {
$operators['exact_phrases'][] = $qMatch;
} else {
$operators['any'][] = $qMatch;
}
break;
}
$q = trim(preg_replace('/\s+/', ' ', str_replace($qMatch, '', $q)));
}
}
}
return $operators;
}
}