Major search overhaul

This commit is contained in:
Erik Thuning 2021-09-15 15:53:52 +02:00
parent 9f4bc39e55
commit 78ac0574b9
5 changed files with 260 additions and 242 deletions

@ -1,27 +1,9 @@
<?php
class Entity {
abstract class Entity {
protected function __construct() {
}
protected function specify_search($searchterms, $searchfields) {
if(array_key_exists('fritext', $searchterms)) {
$freeterm = $searchterms['fritext'];
unset($searchterms['fritext']);
foreach($searchfields as $field) {
if(array_key_exists($field, $searchterms)) {
$term = $searchterms[$field];
if(is_array($term)) {
$term[] = $freeterm;
} else {
$searchterms[$field] = array($term, $freeterm);
}
} else {
$searchterms[$field] = $freeterm;
}
}
}
return $searchterms;
}
abstract public function matches($term, $ldap);
}
?>

@ -110,58 +110,117 @@ class Product extends Entity {
return true;
}
public function matches($terms, $matchAll=false) {
print('DEBUG $terms in matches: ');
var_dump($terms);
print('<br><br>');
$terms = $this->specify_search($terms, array('brand',
'name',
'serial',
'invoice',
'status',
'tag'));
print('DEBUG $terms POST TRANSLATION: ');
var_dump($terms);
print('<br><br>');
/*
Return a list of field-value mappings containing all matching search terms.
*/
public function matches($terms, $ldap) {
$matches = array();
foreach($terms as $field => $values) {
if(property_exists($this, $field)) {
if(match($values, $this->$field)) {
$matches[$field] = $this->$field;
} else {
if($matchAll) {
return array();
// Create a list mapping all basic fields to getters
$fields = array('brand' => 'get_brand',
'name' => 'get_name',
'invoice' => 'get_invoice',
'serial' => 'get_serial');
foreach($terms as $term) {
$key = $term->get_key();
$matched = false;
switch($key) {
case 'brand':
case 'name':
case 'invoice':
case 'serial':
// If $key is a standard field, check against its value
$getter = $fields[$key];
$value = $this->$getter();
if(match($term, $value)) {
//Record a successful match
$matches[$key] = $value;
$matched = true;
}
}
} else if(array_key_exists($field, $this->get_info())) {
if(match($values, $this->get_info()[$field])) {
$matches[$field] = $this->get_info()[$field];
} else {
if($matchAll) {
return array();
}
}
} else if($field == 'tag') {
foreach($this->get_tags() as $tag) {
if(match($values, $tag)) {
if(!array_key_exists('tags', $matches)) {
$matches['tags'] = array();
}
$matches['tags'][] = $tag;
} else {
if($matchAll) {
return array();
break;
case 'tag':
// If $key is tag, iterate over the tags
$matched_tags = $this->match_tags($term);
if($matched_tags) {
// Record a successful match
$matched = true;
if(!isset($matches['tags'])) {
// This is the first list of matching tags
$matches['tags'] = $matched_tags;
} else {
// Merge these results with existing results
$matches['tags'] = array_unique(
array_merge($matches['tags'],
$matched_tags));
}
}
}
} else if($field == 'status') {
if(match($values, $this->get_status())) {
$matches['status'] = $this->get_status();
} else {
if($matchAll) {
return array();
break;
case 'fritext':
// if $key is fritext:
// First check basic fields
foreach($fields as $field => $getter) {
$value = $this->$getter();
if(match($term, $value)) {
$matches[$field] = $value;
$matched = true;
}
}
}
// Then tags
$matched_tags = $this->match_tags($term);
if($matched_tags) {
$matched = true;
if(!isset($matches['tags'])) {
$matches['tags'] = $matched_tags;
} else {
$matches['tags'] = array_unique(
array_merge($matches['tags'],
$matched_tags));
}
}
// Then custom fields
foreach($this->get_info() as $field => $value) {
if(match($term, $value)) {
//Record a successful match
$matches[$field] = $value;
$matched = true;
}
}
break;
default:
// Handle custom fields
$info = $this->get_info();
if(isset($info[$key])) {
// If $key is a valid custom field on this product
$value = $info[$key];
if(match($term, $value)) {
//Record a successful match
$matches[$key] = $value;
$matched = true;
}
}
break;
}
// If a mandatory match failed, the entire search has failed
// and we return an empty result.
if($term->is_mandatory() && !$matched) {
return array();
}
// If a negative match succeeded, the entire search has failed
// and we return an empty result.
if($term->is_negative() && $matched) {
return array();
}
}
return $matches;
}
private function match_tags($term) {
$tags = $this->get_tags();
$matches = array();
foreach($tags as $tag) {
if(match($term, $tag)) {
$matches[] = $tag;
}
}
return $matches;

@ -28,11 +28,7 @@ class SearchPage extends Page {
if(!$this->terms) {
return $out;
}
print('<br>');
foreach(array('user', 'product') as $type) {
// print('=== DEBUG $type: ');
// print_r($type);
// print(' ===<br><br>');
$result = $this->search($type, $this->terms);
if($result) {
$out[$type] = $result;
@ -42,148 +38,32 @@ class SearchPage extends Page {
}
private function search($type, $terms) {
/*
==================================================
ORIGINAL CODE || BACKUP || FOR REFERENCE
$items = get_items($type);
$out = array();
foreach($items as $item) {
$result = $item->matches($terms);
if($result) {
$out[] = array($item, $result);
$matches = array();
foreach(get_items($type) as $item) {
if($result = $item->matches($terms, $this->ldap)) {
$matches[] = array($item, $result);
}
}
return $out;
==================================================
*/
$mustMatchArray = array();
$cannotMatchArray = array();
$mayMatchArray = array();
foreach($terms as $key => $value) {
if(!is_array($value)) {
$value = array($value);
}
foreach($value as $term) {
switch ($term[0]) {
case "+":
if (!array_key_exists($key, $mustMatchArray)) {
$mustMatchArray[$key] = array();
}
$mustMatchArray[$key][] = substr($term, 1);
break;
case "!":
case "-":
if (!array_key_exists($key, $cannotMatchArray)) {
$cannotMatchArray[$key] = array();
}
$cannotMatchArray[$key][] = substr($term, 1);
break;
case "~":
if (!array_key_exists($key, $mayMatchArray)) {
$mayMatchArray[$key] = array();
}
$mayMatchArray[$key][] = substr($term, 1);
break;
default:
if (!array_key_exists($key, $mayMatchArray)) {
$mayMatchArray[$key] = array();
}
$mayMatchArray[$key][] = $term;
break;
}
}
}
$items = get_items($type);
$sanitizedItems = array();
foreach($items as $item) {
$result = $item->matches($mustMatchArray, True);
if($result) {
$sanitizedItems[] = array($item, $result);
}
// $mustMatchCheck = array();
// foreach($mustMatchArray as $mustMatchTerm) {
// $matchResult = $item->matches($mustMatchTerm);
// if($matchResult) {
// $mustMatchCheck[] = True;
// } else {
// $mustMatchCheck[] = False;
// }
// }
// if(in_array(False, $mustMatchCheck, True) === False) {
// $sanitizedItems[] = array($item, $matchResult);
// }
// $mustExcludeCheck = array();
// foreach($mustExcludeArray as $mustExcludeTerm) {
// if($item->matches($mustExcludeTerm)) {
// $mustExcludeCheck[] = False;
// } else {
// $mustExcludeCheck[] = True;
// }
// }
// if (in_array(False, $mustIncludeCheck, True) === False) {
// if(in_array(False, $mustExcludeCheck, True) === True) {
// // === IF TRUE DO NOTHING ===
// } else {
// foreach ($canIncludeArray as $canIncludeTerm) {
// $result = $item->matches($canIncludeTerm);
// if($result) {
// $out[] = array($item, $result);
// }
// }
// }
// }
}
print('DEBUG $sanitizedItems: ');
var_dump($sanitizedItems);
print('<br><br>');
// $out = array();
// foreach($sanitizedItems as $sanitizedItem) {
// print('DEBUG $sanitizedItem: ');
// var_dump($sanitizedItem);
// print('<br><br>');
// if($sanitizedItem->matches($cannotMatchArray)) {
// // === IF TRUE DO NOTHING ===
// }
// else {
// $result = $sanitizedItem->matches($mayMatchArray);
// if($result) {
// $out[] = array($sanitizedItem, $result);
// }
// }
// }
// return array();
// return $out;
return $sanitizedItems;
return $matches;
}
private function translate_terms($terms) {
$matches = array();
// If there is a q-query
// and it contains a : character
if(isset($terms['q']) && preg_match('/([^:]+):(.*)/',
$terms['q'],
$matches)) {
// remove the q key
unset($terms['q']);
// insert the term, using whatever came before
// the : as the key and whatever came after as the value
$terms[$matches[1]] = $matches[2];
}
$translated = array();
foreach($terms as $key => $value) {
// Translate all keys into a standard format
foreach($terms as $key => $values) {
$newkey = $key;
switch($key) {
case 'q':
@ -204,17 +84,38 @@ class SearchPage extends Page {
$newkey = 'serial';
break;
case 'tagg':
case 'tags':
$newkey = 'tag';
break;
case 'anteckning':
$newkey = 'note';
break;
case 'e-post':
case 'epost':
case 'mail':
$newkey = 'email';
break;
case 'status':
$value = $this->translate_values($value);
// Translate all status values into a standard format
$values = $this->translate_values($values);
break;
}
if(!array_key_exists($newkey, $translated)) {
$translated[$newkey] = $value;
} else {
$temp = $translated[$newkey];
$translated[$newkey] = array_merge((array)$temp, (array)$value);
// Wrap the value in an array if it isn't one
if(!is_array($values)) {
$values = array($values);
}
// Make a SearchTerm object from each term
foreach($values as $value) {
// Check for flags
$flag = SearchTerm::OPTIONAL;
if(in_array($value[0], array(SearchTerm::MANDATORY,
SearchTerm::OPTIONAL,
SearchTerm::NEGATIVE))) {
$flag = $value[0];
$value = substr($value, 1);
}
// Collect the new SearchTerm
$translated[] = new SearchTerm($key, $value, $flag);
}
}
return $translated;
@ -270,16 +171,15 @@ class SearchPage extends Page {
$terms = '';
if($this->terms) {
$hidden = '';
foreach($this->terms as $key => $value) {
if(!is_array($value)) {
$value = array($value);
}
foreach($value as $item) {
$terms .= replace(array('term' => ucfirst($key).": $item",
'key' => $key,
'value' => $item),
$this->fragments['search_term']);
}
foreach($this->terms as $term) {
$key = $term->get_key();
$flag = $term->get_flag();
$query = $term->get_query();
$fullterm = ucfirst($key).": ".$flag.$query;
$terms .= replace(array('term' => $fullterm,
'key' => $key,
'value' => $flag.$query),
$this->fragments['search_term']);
}
}
$products = 'Inga artiklar hittade.';
@ -327,4 +227,53 @@ class SearchPage extends Page {
. $data . '<br/>';
}
}
class SearchTerm {
public const MANDATORY = '+';
public const OPTIONAL = '~';
public const NEGATIVE = '-';
private $key;
private $query;
private $flag;
public function __construct($key, $query, $flag=SearchTerm::OPTIONAL) {
$this->key = $key;
$this->query = $query;
$this->flag = $flag;
}
public function get_key() {
return $this->key;
}
public function get_query() {
return $this->query;
}
public function get_flag() {
return $this->flag;
}
public function is_optional() {
if($this->flag == SearchTerm::OPTIONAL) {
return true;
}
return false;
}
public function is_mandatory() {
if($this->flag == SearchTerm::MANDATORY) {
return true;
}
return false;
}
public function is_negative() {
if($this->flag == SearchTerm::NEGATIVE) {
return true;
}
return false;
}
}
?>

@ -45,37 +45,60 @@ class User extends Entity {
return true;
}
public function matches($terms, $ldap, $matchAll=false) {
$terms = $this->specify_search($terms, array('name',
'email',
'notes'));
public function matches($terms, $ldap) {
$matches = array();
foreach($terms as $field => $values) {
switch($field) {
foreach($terms as $term) {
// Iterate over the terms
$matched = false;
$key = $term->get_key();
switch($key) {
case 'name':
if(match($values, $this->name)) {
$matches['name'] = $this->name;
// If the key is name, check username and displayname
$name = $this->get_name();
if(match($term, $name)) {
$matches['name'] = $name;
$matched = true;
}
$dname = $this->get_displayname($ldap);
if(match($values, $dname)) {
if(match($term, $dname)) {
$matches['displayname'] = $dname;
$matched = true;
}
break;
case 'note':
// If the key is note, check it.
$note = $this->get_note();
if($note && match($term, $note)) {
$matches['note'] = $note;
$matched = true;
}
break;
case 'email':
$email = $this->get_email($ldap, false);
if($email && match($values, $email)) {
if($email && match($term, $email)) {
$matches['email'] = $email;
$matched = true;
}
break;
case 'notes':
if(match($values, $this->notes)) {
$matches['notes'] = $this->notes;
case 'fritext':
//Check everything if the key is fritext
$name = $this->get_name();
if(match($term, $name)) {
$matches['name'] = $name;
$matched = true;
}
$dname = $this->get_displayname($ldap);
if(match($term, $dname)) {
$matches['displayname'] = $dname;
$matched = true;
}
break;
}
}
if($matchAll && array_diff_assoc($terms, $matches)) {
return array();
if($term->is_mandatory() && !$matched) {
return array();
}
if($term->is_negative() && $matched) {
return array();
}
}
return $matches;
}

@ -253,18 +253,23 @@ function suggest_content($fieldname) {
return $out;
}
function match($searchterms, $subject) {
if(!is_array($searchterms)) {
$searchterms = array($searchterms);
}
foreach($searchterms as $term) {
if(fnmatch('*'.$term.'*', $subject, FNM_CASEFOLD)) {
return true;
}
function match($term, $subject) {
if(fnmatch('*'.$term->get_query().'*', $subject, FNM_CASEFOLD)) {
return true;
}
return false;
}
function match_tags($searchterm, $tags) {
$found = array();
foreach($tags as $tag) {
if(fnmatch('*'.$tag.'*', $searchterm, FNM_CASEFOLD)) {
$found[] = $tag;
}
}
return $found;
}
function format_date($date) {
if($date) {
return gmdate('Y-m-d', $date);