Merge branch 'test' into prod

This commit is contained in:
Erik Thuning 2022-03-03 11:49:01 +01:00
commit c8720d4ecf
11 changed files with 450 additions and 140 deletions

7
README.md Normal file

@ -0,0 +1,7 @@
# Boka2
There should be a description here
Additional line
additional line 2

@ -85,7 +85,7 @@
</th>
</tr>
</thead>
<tbody>
<tbody class="¤type¤">
¤rows¤
</tbody>
</table>
@ -105,6 +105,25 @@
</td>
</tr>
¤¤ product_detail_row ¤¤
<tr>
<td class="status ¤status¤">
</td>
<td colspan="3">
<dl>
¤details¤
</dl>
</td>
</tr>
¤¤ product_detail ¤¤
<dt>
¤name¤:
</dt>
<dd>
¤value¤
</dd>
¤¤ template_management ¤¤
<div>
<h2>Mallar</h2>
@ -133,7 +152,7 @@
</form>
</div>
¤¤ product_details ¤¤
¤¤ product_form ¤¤
<div id="product-details">
<h2>Artikeldata</h2>
<form id="product-data"

9
include/Entity.php Normal file

@ -0,0 +1,9 @@
<?php
abstract class Entity {
protected function __construct() {
}
abstract public function matches($term, $ldap);
}
?>

@ -49,7 +49,7 @@ class NewPage extends Page {
'info' => $fields,
'label' => '',
'hidden' => 'hidden'),
$this->fragments['product_details']);
$this->fragments['product_form']);
return $out;
}
}

@ -131,50 +131,75 @@ abstract class Page extends Responder {
final protected function build_product_table($products) {
$rows = '';
foreach($products as $product) {
$prodlink = replace(array('id' => $product->get_id(),
'name' => $product->get_name(),
'page' => 'products'),
$this->fragments['item_link']);
$note = 'Tillgänglig';
$status = $product->get_status();
switch($status) {
case 'discarded':
$discarded = format_date($product->get_discardtime());
$note = 'Skrotad '.$discarded;
break;
case 'service':
$service = $product->get_active_service();
$note = 'På service sedan '
.format_date($service->get_starttime());
break;
case 'on_loan':
case 'overdue':
$loan = $product->get_active_loan();
$user = $loan->get_user();
$replacements = array('name' => $user->get_displayname($this->ldap),
'id' => $user->get_id(),
'page' => 'users');
$userlink = replace($replacements,
$this->fragments['item_link']);
$note = 'Utlånad till '.$userlink;
if($loan->is_overdue()) {
$note .= ', försenad';
} else {
$note .= ', slutdatum '
.format_date($loan->get_endtime());
}
break;
}
$rows .= replace(array('status' => $status,
'item_link' => $prodlink,
'serial' => $product->get_serial(),
'note' => $note),
$this->fragments['product_row']);
$rows .= $this->build_product_row($product);
}
return replace(array('rows' => $rows),
return replace(array('rows' => $rows,
'type' => 'single'),
$this->fragments['product_table']);
}
final protected function build_product_row($product, $matches = null) {
$prodlink = replace(array('id' => $product->get_id(),
'name' => $product->get_name(),
'page' => 'products'),
$this->fragments['item_link']);
$note = 'Tillgänglig';
$status = $product->get_status();
switch($status) {
case 'discarded':
$discarded = format_date($product->get_discardtime());
$note = 'Skrotad '.$discarded;
break;
case 'service':
$service = $product->get_active_service();
$note = 'På service sedan '
.format_date($service->get_starttime());
break;
case 'on_loan':
case 'overdue':
$loan = $product->get_active_loan();
$user = $loan->get_user();
$replacements = array('name' => $user->get_displayname($this->ldap),
'id' => $user->get_id(),
'page' => 'users');
$userlink = replace($replacements,
$this->fragments['item_link']);
$note = 'Utlånad till '.$userlink;
if($loan->is_overdue()) {
$note .= ', försenad';
} else {
$note .= ', slutdatum '
.format_date($loan->get_endtime());
}
break;
}
$out = replace(array('status' => $status,
'item_link' => $prodlink,
'serial' => $product->get_serial(),
'note' => $note),
$this->fragments['product_row']);
if($matches) {
$details = $this->build_product_details($product, $matches);
$out .= replace(array('status' => $status,
'details' => $details),
$this->fragments['product_detail_row']);
}
return $out;
}
final protected function build_product_details($product, $matches) {
$out = '';
foreach($matches as $name => $value) {
if(is_array($value)) {
$value = implode(', ', $value);
}
$out .= replace(array('name' => $product->get_label($name),
'value' => $value),
$this->fragments['product_detail']);
}
return $out;
}
final protected function build_user_loan_table($loans) {
$rows = '';
foreach($loans as $loan) {
@ -260,7 +285,8 @@ abstract class Page extends Responder {
'note' => $note),
$this->fragments['product_row']);
}
return replace(array('rows' => $rows),
return replace(array('rows' => $rows,
'type' => 'single'),
$this->fragments['product_table']);
}

@ -1,5 +1,5 @@
<?php
class Product {
class Product extends Entity {
private $id = 0;
private $brand = '';
private $name = '';
@ -43,6 +43,7 @@ class Product {
}
public function __construct($clue, $type = 'id') {
parent::__construct();
$search = null;
switch($type) {
case 'id':
@ -109,37 +110,145 @@ class Product {
return true;
}
// Ldap object must be passed to keep the arglist in sync
// with User->matches()
/*
Return a list of field-value mappings containing all matching search terms.
*/
public function matches($terms, $ldap) {
foreach($terms as $field => $values) {
$matchvalues = array();
if(property_exists($this, $field)) {
$matchvalues[] = $this->$field;
} else if(array_key_exists($field, $this->get_info())) {
$matchvalues[] = $this->get_info()[$field];
} else {
switch($field) {
case 'tag':
$matchvalues = $this->get_tags();
case 'status':
$matchvalues[] = $this->get_status();
case 'fritext':
$matchvalues[] = $this->brand;
$matchvalues[] = $this->name;
$matchvalues[] = $this->serial;
$matchvalues[] = $this->invoice;
$matchvalues = array_merge($matchvalues,
$this->get_tags(),
array_values(
$this->get_info()));
}
$matches = array();
// Create a list mapping all basic fields to getters
$fields = array('brand' => 'get_brand',
'name' => 'get_name',
'invoice' => 'get_invoice',
'serial' => 'get_serial',
'status' => 'get_status');
foreach($terms as $term) {
$key = $term->get_key();
$matched = false;
switch($key) {
case 'brand':
case 'name':
case 'invoice':
case 'serial':
case 'status':
// 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;
}
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));
}
}
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(!match($values, $matchvalues)) {
return false;
// 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 true;
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;
}
public function get_label($name) {
switch($name) {
case 'brand':
return 'Tillverkare';
break;
case 'name':
return 'Namn';
break;
case 'invoice':
return 'Fakturanummer';
break;
case 'serial':
return 'Serienummer';
break;
case 'tags':
return 'Taggar';
break;
default:
return ucfirst($name);
break;
}
}
public function get_id() {

@ -38,12 +38,12 @@ class ProductPage extends Page {
$this->fragments['product_page']));
break;
case 'show':
print($this->build_product_details());
print($this->build_product_form());
break;
}
}
private function build_product_details() {
private function build_product_form() {
$info = '';
foreach($this->product->get_info() as $key => $value) {
$info .= replace(array('name' => ucfirst($key),
@ -81,7 +81,7 @@ class ProductPage extends Page {
$fields['service'] = 'Avsluta service';
}
}
return replace($fields, $this->fragments['product_details']);
return replace($fields, $this->fragments['product_form']);
}
private function build_history_table($history) {

@ -37,16 +37,33 @@ class SearchPage extends Page {
return $out;
}
private function search($type, $terms) {
$matches = array();
foreach(get_items($type) as $item) {
if($result = $item->matches($terms, $this->ldap)) {
$matches[] = array($item, $result);
}
}
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':
@ -67,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($newkey, $value, $flag);
}
}
return $translated;
@ -127,48 +165,96 @@ class SearchPage extends Page {
}
return $translated;
}
private function search($type, $terms) {
$items = get_items($type);
$out = array();
foreach($items as $item) {
if($item->matches($terms, $this->ldap)) {
$out[] = $item;
}
}
return $out;
}
protected function render_body() {
$hidden = 'hidden';
$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.';
$prod_table = 'Inga artiklar hittade.';
if($this->product_hits) {
$products = $this->build_product_table($this->product_hits);
$products = '';
foreach($this->product_hits as $hit) {
$products .= $this->build_product_row($hit[0], $hit[1]);
}
$prod_table = replace(array('rows' => $products,
'type' => 'double'),
$this->fragments['product_table']);
}
$users = 'Inga användare hittade.';
$user_table = 'Inga användare hittade.';
if($this->user_hits) {
$users = $this->build_user_table($this->user_hits);
$users = array();
foreach($this->user_hits as $hit) {
$users[] = $hit[0];
}
$user_table = $this->build_user_table($users);
}
print(replace(array('terms' => $terms,
'hidden' => $hidden,
'product_results' => $products,
'user_results' => $users),
'product_results' => $prod_table,
'user_results' => $user_table),
$this->fragments['search_form']));
}
}
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;
}
}
?>

@ -1,5 +1,5 @@
<?php
class User {
class User extends Entity {
private $id = 0;
private $name = '';
private $notes = '';
@ -12,6 +12,7 @@ class User {
}
public function __construct($clue, $type = 'id') {
parent::__construct();
$find = null;
switch($type) {
case 'id':
@ -45,25 +46,61 @@ class User {
}
public function matches($terms, $ldap) {
foreach($terms as $field => $values) {
$matchvalues = array();
if($field == 'name') {
$matchvalues[] = $this->name;
$matchvalues[] = $this->get_displayname($ldap);
} else if(property_exists($this, $field)) {
$matchvalues[] = $this->$field;
} else if($field == 'fritext') {
$matchvalues[] = $this->name;
$matchvalues[] = $this->get_displayname($ldap);
$matchvalues[] = $this->notes;
} else {
return false;
$matches = array();
foreach($terms as $term) {
// Iterate over the terms
$matched = false;
$key = $term->get_key();
switch($key) {
case '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($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($term, $email)) {
$matches['email'] = $email;
$matched = true;
}
break;
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;
}
}
if(!match($values, $matchvalues)) {
return false;
if($term->is_mandatory() && !$matched) {
return array();
}
if($term->is_negative() && $matched) {
return array();
}
}
return true;
return $matches;
}
public function get_displayname($ldap) {
@ -78,7 +115,10 @@ class User {
try {
return $ldap->get_user_email($this->name);
} catch(Exception $e) {
return 'Mailadress saknas';
if($format) {
return 'Mailadress saknas';
}
return false;
}
}

@ -253,22 +253,21 @@ function suggest_content($fieldname) {
return $out;
}
function match($testvalues, $matchvalues) {
# match only presence of field (if no value given)
if(!$testvalues && $matchvalues) {
function match($term, $subject) {
if(fnmatch('*'.$term->get_query().'*', $subject, FNM_CASEFOLD)) {
return true;
}
if(!is_array($testvalues)) {
$testvalues = array($testvalues);
}
foreach($testvalues as $value) {
foreach($matchvalues as $candidate) {
if(fnmatch('*'.$value.'*', $candidate, 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 false;
return $found;
}
function format_date($date) {

@ -104,10 +104,25 @@ tbody tr {
background-color: #d7e0eb;
}
tbody tr:nth-child(odd) {
tbody.single tr:nth-child(odd) {
background-color: #ebf0f5;
}
tbody.double tr:is(:nth-child(4n+1), :nth-child(4n+2)) {
background-color: #ebf0f5;
}
tbody dl {
margin: 0;
display: grid;
grid-template-columns: 2fr 5fr;
}
tbody dd {
margin: 0;
grid-column: 2;
}
thead th, tfoot tr {
background-color: #c3d1e2;
}