diff options
Diffstat (limited to 'lib/classes/QuickSearch.php')
| -rw-r--r-- | lib/classes/QuickSearch.php | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/lib/classes/QuickSearch.php b/lib/classes/QuickSearch.php new file mode 100644 index 0000000..f3e18cc --- /dev/null +++ b/lib/classes/QuickSearch.php @@ -0,0 +1,503 @@ +<?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 object of type QuickSearch + */ + 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; + } +} |
