<?php # Lifter010: TODO /** * QuickSearch.php - GUI class for quciksearch * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * @author Rasmus <fuhse@data-quest.de> * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 * @category Stud.IP */ /** * This class provides a small and intuitive GUI-element for an instant search of * courses, persons, institutes or other items. Mainly the structure to include * a QuickSearch-field is the following: * //code-begin * $sf = new QuickSearch("username"); * $sf->withButton(); * $sf->specialSQL("SELECT username, CONCAT(Vorname, \" \", Nachname) " . * "FROM auth_user_md5 " . * "WHERE CONCAT(Vorname, \" \", Nachname) LIKE :input " . * "AND perms = 'dozent'", _("Dozenten suchen")); * print $sf->render(); * //code-end * This code should be included into an html <form>-tag. It will provide a nice looking * input-field with a button (this is the $sf->withButton() command for), a javascript * instant-search for your items (in this case 'Dozenten') and also a non-javascript * oldschool version of a searchfield with a select-box in step 2 of the search (first * write what you search, click on the button and then select in the select-box what * you wanted to have). * You can handle the searchfield in your form-tag as if you have written an * '<input type="text" name="username">'. * * For most cases you may only want to search for persons, courses or institutes. * Thus a shortcut is implemented in this class. You may write * //code-begin * $sf = new QuickSearch("username", "username"); * $sf->withButton(); * print $sf->render(); * //code-end * to receive a searchfield that is automatically searching for users and inserts * the selected users's username in the searchfield. The first parameter of the * constructor 'new Quicksearch' is the name of the variable in your form and is * completely free to name. The (optional) second parameter describes what you are * searching for: username, user_id, Seminar_id or Institut_id. * * Also you can do method-chaining with this class, so you can press everything * you need infront of your semicolon. Watch this example: * //code-begin * print QuickSearch::get("username", "username")->withButton->render(); * //code-end * * Lastly you can replace the second argument of the constructor (or get-method) * by an object whose class extends the SearchType-class. This might be * useful to create your own searches and handle them with oop-style or even * use a totally different search-engine like lucene-index! All you need to * do so is implement your searchclass and follow this example: * //code-begin * class TeacherSearch extends SearchType { * ... * } * $searcher = new TeacherSearch(); * print QuickSearch::get("username", $searcher)->withButton->render(); * //code-end * Watch the SearchType class in lib/classes/searchtypes/SearchType.php * for details. * Enjoy! */ class QuickSearch { const GC_LIFETIME = 10800; // = 3 * 60 * 60 = 3 hours static $count_QS = 0; //static counter of all instances of this class private $name; //name of the input/select field private $search; //may be an object or a string private $avatarLike; //like "user_id", "username", "Seminar_id" or stuff private $withButton; //if true, the field will be displayed with a looking-glass-button to click on private $selectBox = true; private $withAttributes = []; private $box_width = "233"; //width of the box withButton private $box_align = "right";//align of the lookingglass in the withButton-box private $autocomplete_disabled = false; private $search_button_name; private $reset_button_name; private $defaultID = null; private $defaultName = null; private $jsfunction = null; private $inputClass = null; private $inputStyle = null; private $specialQuery = null; private $minLength = 3; /** * Deletes all older requests that have not been used for three hours * from the session * * @return int Number of removed searches */ public static function garbageCollect() { if (empty($_SESSION['QuickSearches'])) { return 0; } $count = count($_SESSION['QuickSearches']); $_SESSION['QuickSearches'] = array_filter($_SESSION['QuickSearches'], function ($query) { return $query['time'] + QuickSearch::GC_LIFETIME > time(); }); return $count - count($_SESSION['QuickSearches']); } /** * Retrieves the search object for the given id previously stored in * the session. * * @param String $query_id Id of the quicksearch object * @return SearchType Quicksearch object * @throws RuntimeException when the given query does not exist in session */ public static function getFromSession($query_id) { self::garbageCollect(); if (!isset($_SESSION['QuickSearches'][$query_id])) { throw new RuntimeException('Quicksearch id not in session'); } // Store last access to search $_SESSION['QuickSearches'][$query_id]['time'] = time(); $query = $_SESSION['QuickSearches'][$query_id]; if ($query['includePath']) { include_once $query['includePath']; } return unserialize($query['object']); } /** * returns an instance of QuickSearch so you can use it as singleton * * @param string $name the name of the destinated variable in your html-form. Handle it * as if it was an '<input type="text" name="yourname">' input. * @param string $search if set to user_id, username, Seminar_id, Arbeitsgruppe_id or Institute_id * the searchfield will automatically search for persons, courses, workgroups, institutes and * you don't need to call the specialSearch-method. * * @return static */ public static function get($name, $search = NULL) { return new static($name, $search); } /** * constructor which prepares a searchfield for persons, courses, institutes or * special items you may want to search for. This is a GUI-class, see * QuickSearch.php for further documentation. * * @param string $name the name of the destinated variable in your html-form. Handle it * as if it was an '<input type="text" name="yourname">' input. * @param string $search if set to user_id, username, Seminar_id, Arbeitsgruppe_id or Institute_id * the searchfield will automatically search for persons, courses, workgroups, institutes and * you don't need to call the specialSearch-method. * * @return void */ final public function __construct($name, $search = NULL) { self::$count_QS++; $this->name = $name; $this->withButton = false; $this->avatarLike = ""; if ($search instanceof SearchType) { $this->search = $search; } else { $this->search = NULL; } $this->setAttributes([]); } /** * if set to true, the searchfield will be a nice-looking grey searchfield with * a magnifier-symbol as a submit-button. Set this to false to create your own * submit-button and style of the form. * @param mixed $design associative array of params. * * @return QuickSearch */ public function withButton($design = []) { $this->withButton = true; if (isset($design['width'])) { $this->box_width = $design['width']; } $this->box_align = $design['align'] ?? "right"; $this->search_button_name = $design['search_button_name'] ?? ''; $this->reset_button_name = $design['reset_button_name'] ?? ''; return $this; } /** * this will disable a submit button for the searchfield * * @return QuickSearch */ public function withoutButton() { $this->withButton = false; return $this; } /** * Here you can set a default-value for the searchfield * * @param string $valueID the default-ID that should be stored * @param string $valueName the default value, that should be displayed * - remember that these may not be the same, they may be * something like "ae2b1fca515949e5d54fb22b8ed95575", "test_dozent" * * @return QuickSearch */ public function defaultValue($valueID, $valueName) { $this->defaultID = $valueID; $this->defaultName = $valueName; return $this; } /** * defines a css class for the searchfield * * @param string $class any css class name for the "input type=text" tag * * @return QuickSearch */ public function setInputClass($class) { $this->withAttributes['class'] = $class; return $this; } /** * defines css-proporties for searchfield that will be included as 'style="$style"' * * @param string $style one or more css-proporties separated with ";" * * @return QuickSearch */ public function setInputStyle($style) { $this->withAttributes['style'] = $style; return $this; } /** * Set the minimum length to start searching * * @param int $minLength * * @return QuickSearch */ public function setMinLength(int $minLength) { $this->minLength = $minLength; return $this; } /** * disables the select-box, which is displayed for non-JS users who will * choose with this box, which item they want to have. * * @param bool $set false if we DO want a select-box, false otherwise * * @return QuickSearch */ public function noSelectbox($set = true) { $this->selectBox = !$set; return $this; } /** * disables the ajax autocomplete for this searchfield * If you want to disable all QuickSearches, you better use the * config variable global -> AJAX_AUTOCOMPLETE_DISABLED * @param disable boolean: true (default) to disable, false to enable * autocomplete via ajax. * @return QuickSearch */ public function disableAutocomplete($disable = true) { $this->autocomplete_disabled = $disable; return $this; } /** * set a JavaScript-function to be fired after the user has selected a * value in the QuickSearch field. Arguments are: * function fireme(id_of_item, text_of_item) * example setting: QS->fireJSFunctionOnSelect('fireme'); * * @param string $function_name the name of the javascript function * * @return QuickSearch */ public function fireJSFunctionOnSelect($function_name) { $this->jsfunction = $function_name; return $this; } /** * assigns special attributes to the html-element of the searchfield * * @param array $ttr_array like array("title" => "hello world") * * @return QuickSearch */ public function setAttributes($attr_array) { if (is_array($attr_array)) { $this->withAttributes = $attr_array; } if (!isset($this->withAttributes['aria-label']) && !isset($this->withAttributes['aria-labelledby']) && $this->search) { $this->withAttributes['aria-label'] = $this->search->getTitle(); } return $this; } /** * Returns whether the underlying search type requires an extended * layout. * * @return bool indicating whether an extended layout is required */ public function hasExtendedLayout() { return !empty($this->search->extendedLayout); } /** * last step: display everything and be happy! * comment: the Ajax-Result (for the javascript-instant-search) will be also displayed here, * but that does not need to concern you. * * @return string */ public function render() { if (trim(Request::get($this->name.'_parameter')) && (Request::get($this->name.'_parameter') != $this->beschriftung()) && !Request::get($this->name) && $this->selectBox) { //No Javascript activated and having searched: $searchresults = $this->searchresults(Request::get($this->name.'_parameter')); $template = $GLOBALS['template_factory']->open('quicksearch/selectbox.php'); $template->set_attribute('withButton', $this->withButton); $template->set_attribute('box_align', $this->box_align); $template->set_attribute('box_width', $this->box_width); $template->set_attribute('withAttributes', $this->withAttributes); $template->set_attribute('searchresults', $searchresults); $template->set_attribute('name', $this->name); $template->set_attribute('search_button_name', $this->search_button_name); $template->set_attribute('reset_button_name', $this->reset_button_name); $template->set_attribute('extendedLayout', $this->hasExtendedLayout()); return $template->render(); } else { $query_id = $this->storeSearchInSession(); //Ausgabe: $template = $GLOBALS['template_factory']->open('quicksearch/inputfield.php'); $template->set_attribute('withButton', $this->withButton); $template->set_attribute('box_align', $this->box_align); $template->set_attribute('box_width', $this->box_width); $template->set_attribute('inputStyle', $this->inputStyle ?? ''); $template->set_attribute('beschriftung', $this->beschriftung()); $template->set_attribute('name', $this->name); $template->set_attribute('defaultID', $this->defaultID); $template->set_attribute('defaultName', $this->defaultName); $template->set_attribute('withAttributes', $this->withAttributes ? $this->withAttributes : []); $template->set_attribute('jsfunction', $this->jsfunction); $template->set_attribute('autocomplete_disabled', Config::get()->getValue("AJAX_AUTOCOMPLETE_DISABLED") || $this->autocomplete_disabled); $template->set_attribute('count_QS', self::$count_QS); $template->set_attribute('id', $this->getId()); $template->set_attribute('query_id', $query_id); $template->set_attribute('minLength', $this->minLength); $template->set_attribute('search_button_name', $this->search_button_name); $template->set_attribute('reset_button_name', $this->reset_button_name); $template->set_attribute('extendedLayout', $this->hasExtendedLayout()); return $template->render(); } } /** * Convert quicksearch to string by rendering it * * @return string rendered html */ public function __toString() { return $this->render(); } /** * returns the id string used for the input field * * @return string */ public function getId() { return "qs_".md5($this->name) . '_' . (int)self::$count_QS; } ////////////////////////////////////////////////////////////////////////////// // private-methods // ////////////////////////////////////////////////////////////////////////////// /** * private method to get a result-array in the way of array(array(item_id, item-name)). * * @param string $request the request from the searchfield typed by the user. * * @return array array(array(item_id, item-name), ...). */ private function searchresults($request) { if ($this->search instanceof SearchType) { try { $results = $this->search->getResults($request, $_REQUEST); } catch (Exception $exception) { //Der Programmierer will ja seine Fehler sehen: return [["", $exception->getMessage()]]; } return $results; } else { $result = [["", _("Kein korrektes Suchobjekt angegeben.")]]; return $result; } } /** * get the label of the searchfield that is written in javascript and disappears * when the user focusses on the searchfield. * * @return string localized-string */ private function beschriftung() { if ($this->search instanceof SearchType) { return $this->search->getTitle(); } else { return ""; } } /** * Abfrage in der Session speichern * * @return string */ protected function storeSearchInSession(): string { $query_id = md5(serialize($this->search)); // Prepare object $item = [ 'time' => time(), ]; if ($this->search instanceof SearchType) { $item['object'] = serialize($this->search); if ($this->search instanceof SearchType) { $item['includePath'] = $this->search->includePath(); } } else { $item['query'] = $this->search; } // Actually storing in session if (!isset($_SESSION['QuickSearches'])) { $_SESSION['QuickSearches'] = []; } $_SESSION['QuickSearches'][$query_id] = $item; return $query_id; } }