'Page Search', 'summary' => 'Provides a page search engine for admin use.', 'version' => 108, 'permanent' => true, 'permission' => 'page-edit', ); } /** * Default operator for text searches * */ const defaultOperator = '%='; /** * Native/system sortable properties * * @var array * */ protected $nativeSorts = array( 'relevance', 'name', 'title', 'id', 'status', 'templates_id', 'parent_id', 'created', 'modified', 'published', 'modified_users_id', 'created_users_id', 'createdUser', 'modifiedUser', 'sort', 'sortfield', ); /** * Names of all Field objects in PW * * @var array * */ protected $fieldOptions = array(); /** * All operators where key is operator and value is description * * @var array * */ protected $operators = array(); /** * Items per pagination * * @var int * */ protected $resultLimit = 25; /** * Lister instance, when applicable * * @var null|ProcessPageLister * */ protected $lister = null; /** * Debug mode? * * @var bool * */ protected $debug = true; public function __construct() { parent::__construct(); $this->set('searchFields', 'title body'); $this->set('searchFields2', 'title'); $this->set('displayField', 'name'); $this->set('operator', self::defaultOperator); $this->set('operator2', '~='); $this->set('searchTypesOrder', array('fields', 'templates', 'modules', 'pages', 'trash')); $this->set('noSearchTypes', array()); // search types that have been removed // make nativeSorts indexed by value $sorts = array(); foreach($this->nativeSorts as $sort) { $sorts[$sort] = $sort; } $this->nativeSorts = $sorts; } /** * Initialize module * */ public function init() { foreach($this->fields as $field) { if($field->type instanceof FieldtypeFieldsetOpen) continue; if($field->type instanceof FieldtypePassword) continue; // @todo add field access control checking $this->fieldOptions[$field->name] = $field->name; } ksort($this->fieldOptions); parent::init(); } public function set($key, $value) { if($key == 'searchFields' || $key == 'searchFields2') { if(is_array($value)) $value = implode(' ', $value); } else if($key == 'noSearchTypes' && !is_array($value)) { $value = explode(' ', $value); } return parent::set($key, $value); } /** * Get operators used for searches, where key is operator and value is description * * @return array * */ static public function getOperators() { $operators = Selectors::getOperators(array( 'getIndexType' => 'operator', 'getValueType' => 'label', )); unset($operators['#=']); // maybe later return $operators; } /** * Setup items needed for full execution, as opposed to the regular search input that appears on all pages * */ protected function fullSetup() { $sanitizer = $this->wire()->sanitizer; $input = $this->wire()->input; $headline = $this->_x('Search', 'headline'); // Headline for search page if($input->get('processHeadline')) { $headline = $sanitizer->entities($sanitizer->text($input->get('processHeadline'))); $this->input->whitelist('processHeadline', $headline); } $this->wire('processHeadline', $headline); $this->operators = self::getOperators(); } /** * Hookable function to optionally modify selector before it is sent to $pages->find() * * Not applicable when Lister is handling the search/render. * * #pw-hooker * * @param string $selector Selector that will be used to find pages * @return string Must return the selector (optionally modified) * */ public function ___findReady($selector) { return $selector; } /** * Return instance of ProcessPageLister or null if not available * * @return ProcessPageLister|null * */ protected function getLister() { $modules = $this->wire()->modules; if($this->lister) return $this->lister; if($this->wire()->user->hasPermission('page-lister')) { if($modules->isInstalled('ProcessPageLister')) { $this->lister = $modules->get('ProcessPageLister'); } } return $this->lister; } /** * Perform an interactive search and provide a search form (default) * */ public function ___execute() { $lister = $this->getLister(); $ajax = $this->wire()->config->ajax; $bookmark = (int) $this->wire()->input->get('bookmark'); if($lister && ($ajax || $bookmark)) { // we will just let Lister do it's thing, since it remembers settings in session return $lister->execute(); } else { $this->fullSetup(); $this->processInput(); list($selector, $displaySelector, $initSelector, $defaultSelector) = $this->buildSelector(); } if($lister) { if(count($_GET)) $lister->sessionClear(); $lister->initSelector = $initSelector; $lister->defaultSelector = $defaultSelector; $lister->defaultSort = 'relevance'; $lister->set('limit', $this->resultLimit); $lister->preview = false; $lister->columns = $this->getDisplayFields(); return $lister->execute(); } else { $selector = $this->findReady($selector); $matches = $this->pages->find($selector); return $this->render($matches, $displaySelector); } } public function executeReset() { $lister = $this->getLister(); return $lister ? $lister->executeReset() : ''; } public function executeEditBookmark() { $lister = $this->getLister(); return $lister ? $lister->executeEditBookmark() : ''; } /** * Perform a non-interactive search (based on URL GET vars) * * This is the preferred input method for links and ajax queries. * * Example /search/for?template=basic-page&body*=example * */ public function ___executeFor() { $languages = $this->wire()->languages; $user = $this->wire()->user; $input = $this->wire()->input; $sanitizer = $this->wire()->sanitizer; if($input->get('admin_search')) return $this->executeLive(); $this->fullSetup(); $selectors = array(); $limit = $this->resultLimit; $start = 0; $status = 0; $names = array(); $userLanguage = null; $superuser = $user->isSuperuser(); $checkEditAccess = false; $hasInclude = ''; $n = 0; $selectorName = $input->get('for_selector_name'); if($selectorName) { $selector = $this->getForSelector($selectorName); if(strlen($selector)) $selectors['for'] = $selector; } // names to skip (must be lowercase) $skipNames = array( 'get', 'display', 'format_name', 'for_selector_name', 'admin_search' ); // names to convert (keys must be lowercase) $convertNames = array( 'hasparent' => 'has_parent', 'checkaccess' => 'check_access', ); foreach($input->get as $name => $value) { $lowerName = strtolower(trim($name)); if(isset($convertNames[$lowerName])) { $name = $convertNames[$lowerName]; $lowerName = strtolower($name); } if(in_array($lowerName, $skipNames)) continue; if($lowerName == 'lang_id') { if($languages) { // force results for specific language $language = $languages->get((int) $value); if(!$language->id) continue; if($user->language->id != $language->id) { $userLanguage = $user->language; $user->language = $language; } } continue; } // operator has no '=', so we'll get the value from the name // so that you can do something like: bedrooms>5 rather than bedrooms>=5 if(!strlen($value) && preg_match('/([^<>]+)\s*([<>])\s*([^<>]+)/', $name, $matches)) { $name = $matches[1]; $operator = $matches[2]; $value = $matches[3]; } else { $operator = '='; $operatorChars = preg_quote(implode('', Selectors::getOperatorChars())); if(preg_match('/^(.+?)([' . $operatorChars . ']+)$/', $name, $matches)) { $name = $matches[1]; $operator = $matches[2] . '='; // if unsupported operator requested, substitute '=' if(!isset($this->operators[$operator])) $operator = '='; } } // replace '-' with '.' since '.' is not allowed in URL variable names if(strpos($name, '-')) $name = str_replace('-', '.', $name); if(strpos($name, ',')) { $name = $sanitizer->names($name, ',', array('_', '.')); } else { $name = $sanitizer->fieldSubfield($name, 2); } if(!$name) continue; $lowerName = strtolower($name); if($lowerName == 'limit') { $limit = (int) $value; $input->whitelist('limit', $value); continue; } if($lowerName == 'start') { $start = (int) $value; $input->whitelist('start', $value); continue; } // if dealing with a user other than superuser, only allow include=hidden if($lowerName == 'include') { $name = $lowerName; $value = strtolower($value); if($value != 'hidden' && !$superuser) { if($user->hasPermission('page-edit') && $input->get('admin_search')) { $value = 'unpublished'; $checkEditAccess = true; } else { $value = 'hidden'; } } $hasInclude = $value; } // don't allow setting of check_access property, except for superuser if($lowerName == 'check_access' && !$superuser) continue; // don't allow setting of the 'status' property, except for superuser if($lowerName == 'status') { if(!$superuser) continue; $status = (int) $value; } // replace URL-compatible comma separators with selector-compatible pipes if(strpos($name, ',')) $name = str_replace(',', '|', $name); $name = $this->filterSelectableFieldName($name); if(!strlen($name)) continue; if(strpos($value, ',')) { // commas between words: split one key=value, into multiple key=value, key=value $valuesAND = explode(',', $value); } else { $valuesAND = array($value); } foreach($valuesAND as $key => $val) { if(strpos($val, '|')) { $valuesOR = explode('|', $val); foreach($valuesOR as $k => $v) { $valuesOR[$k] = $sanitizer->selectorValue($v); } $val = implode('|', $valuesOR); } else { $val = $sanitizer->selectorValue($val); } $valuesAND[$key] = $val; } $value = implode(',', $valuesAND); $input->whitelist($name . rtrim($operator, '='), trim($value, '"\'')); foreach($valuesAND as $val) { $n++; $selectors["input-$n"] = "$name$operator$val"; } $names[] = $name; } // foreach input if($start) $selectors['start'] = "start=$start"; $selectors['limit'] = "limit=$limit"; $displaySelector = implode(',', $selectors); if(!$status && !$hasInclude && $superuser) { // superuser only $selectors['superuser'] = "include=all, status<" . Page::statusTrash; } $selector = implode(', ', $selectors); $selector = $this->findReady($selector); $items = $this->pages->find($selector); if(!$superuser && $checkEditAccess) { // filter out non-editable pages, since some may be included via include=unpublished foreach($items as $item) { if(!$item->editable()) $items->remove($item); } } $out = $this->render($items, $displaySelector); if($userLanguage) $user->language = $userLanguage; return $out; } /** * Execute live search * * @return string * */ public function executeLive() { require_once(dirname(__FILE__) . '/ProcessPageSearchLive.php'); $liveSearch = new ProcessPageSearchLive($this); $liveSearch->setSearchTypesOrder($this->searchTypesOrder); $liveSearch->setNoSearchTypes($this->noSearchTypes); $liveSearch->setDefaultOperators($this->operator, $this->operator2); if($this->wire()->config->ajax) { header('Content-type: application/json'); return $liveSearch->execute(); } else { return $liveSearch->executeViewAll(); } } /** * Get ID of the repeaters root page ID or 0 if not installed * * @return int * */ public function getRepeatersPageID() { $session = $this->wire()->session; $repeaterID = $session->getFor($this, 'repeaterID'); if(is_int($repeaterID)) return $repeaterID; if($this->wire()->modules->isInstalled('FieldtypeRepeater')) { $repeaterPage = $this->wire()->pages->get( "parent_id=" . $this->wire()->config->adminRootPageID . ", " . "name=repeaters, " . "include=all" ); $repeaterID = $repeaterPage->id; $session->setFor($this, 'repeaterID', (int) $repeaterID); } else { $repeaterID = 0; } return $repeaterID; } /** * Return array of fields to display in results * */ protected function getDisplayFields() { $sanitizer = $this->wire()->sanitizer; $input = $this->wire()->input; $display = (string) $input->get('display'); if(!strlen($display)) $display = (string) $input->get('get'); // as required by ProcessPageSearch API if(!strlen($display)) $display = (string) $this->displayField; if(!strlen($display)) $display = 'title path'; $display = str_replace(',', ' ', $display); $display = explode(' ', $display); // convert to array foreach($display as $key => $name) { $name = $sanitizer->fieldName($name); $display[$key] = $name; if($this->isSelectableFieldName($name)) continue; if(in_array($name, array('url', 'path', 'httpUrl'))) continue; unset($display[$key]); } return array_values($display); } /** * As an alternative to getting specific fields, return a format string * * This format string must be pre-populated to session variable: * ProcessPageSearch.[format_name] = '{title} - {path}'; // format string * * The name the session variable must be provided as a GET var: format_name=[name] * * @return array|string * */ protected function getDisplayFormat() { $name = $this->wire()->input->get('format_name'); if(empty($name)) return ''; $data = $this->wire()->session->getFor($this, "format_" . $name); if(empty($data)) return ''; return array( 'name' => $name, 'format' => $data['format'], 'textOnly' => $data['textOnly'] ); } /** * Set a display format * * @param string $name Session var name that will be used, output will be returned in JSON results indexed by $name as well. * @param string $format Format string to pass to $page->getMarkup(str) * @param bool $textOnly * */ public function setDisplayFormat($name, $format, $textOnly = false) { $this->wire()->session->setFor($this, "format_" . $name, array( 'format' => $format, 'textOnly' => $textOnly )); } /** * Set a selector to use when $_GET['for_selector_name'] matches given $name * * This is for cases where you don't want the selector to pass through user input, * and you instead just want to pass the name of it via user input. This enables * use of some features that may not be available through user selectors passing * only through user input. * * Used in executeFor() mode only. * * @param string $name * @param string $selector * @return string Returns URL needed to use this selector * @since 3.0.223 * */ public function setForSelector($name, $selector) { $this->wire()->session->setFor($this, "for_selector_$name", $selector); return $this->config->urls->admin . 'page/search/for?for_selector_name=' . urlencode($name); } /** * Get selector identified by $name that was previously set with setForSelector() * * For executeFor() mode only. * * #pw-internal * * @param string $name * @return string * @since 3.0.223 * */ public function getForSelector($name) { return (string) $this->wire()->session->getFor($this, "for_selector_$name"); } /** * Render the search results * * @param PageArray $matches * @param string $displaySelector * @return string * */ protected function render(PageArray $matches, $displaySelector = '') { $input = $this->wire()->input; $ajax = $this->wire()->config->ajax; $out = ''; if($displaySelector) { $this->message( sprintf( $this->_n('Found %1$d page using selector: %2$s', 'Found %1$d pages using selector: %2$s', $matches->getTotal()), $matches->getTotal(), $displaySelector ) ); } // determine what fields will be displayed $display = array(); if($ajax) $display = $this->getDisplayFormat(); if(empty($display)) { $display = $this->getDisplayFields(); $input->whitelist('display', implode(',', $display)); } if($ajax) { // ajax json output header("Content-type: application/json"); $out = $this->renderMatchesAjax($matches, $display, $displaySelector); } else { // html output $class = ''; if((int) $input->get('show_options') !== 0 && $input->urlSegment1 != 'find') { $out = "\n
"; $out .= "\n\t
" . "\n\t" . "\n\t" . "\n\t
"; $out .= "\n\t" . "\n\t" . "\n\t" . "\n\t
"; $out .= "\n\t" . "\n\t" . "\n\t" . "\n\t"; // Advanced $advCollapsed = true; $out2 = "\n\t" . "\n\t" . "\n\t" . "\n\t
"; $out2.= "\n\t" . "\n\t" . "\n\t" . "\n\t
"; if($sort != 'relevance') { $reverse = $input->whitelist('reverse'); $out2 .= "\n\t" . "\n\t" . "\n\t
"; if($reverse) $advCollapsed = false; } $display = $input->whitelist('display'); $out2 .= "\n\t" . "\n\t" . "\n\t" . "\n\t
"; if($display && $display != 'title,path') $advCollapsed = false; /** @var InputfieldSubmit $submit */ $submit = $modules->get("InputfieldSubmit"); $submit->attr('name', 'submit'); $submit->attr('value', $this->_x('Search', 'submit')); // Search submit button for advanced search $out .= "" . $submit->render() . "
"; /** @var InputfieldForm $form */ $form = $modules->get("InputfieldForm"); $form->attr('id', 'ProcessPageSearchOptionsForm'); $form->method = 'get'; $form->action = './'; /** @var InputfieldMarkup $field */ $field = $modules->get("InputfieldMarkup"); $field->label = $this->_("Search Options"); $field->value = $out; $form->add($field); /** @var InputfieldMarkup $field */ $field = $modules->get("InputfieldMarkup"); if($advCollapsed) $field->collapsed = Inputfield::collapsedYes; $field->label = $this->_("Advanced"); $field->value = $out2; $form->add($field); return $form->render(); } /** * Render a table of matches * * @param PageArray $matches * @param array $display Fields to display (from getDisplayFields method) * @return string * */ protected function renderMatchesTable(PageArray $matches, array $display) { $input = $this->wire()->input; $config = $this->wire()->config; $modules = $this->wire()->modules; if(!count($matches)) return ''; if(!count($display)) $display = array('path'); /** @var MarkupAdminDataTable $table */ $table = $modules->get("MarkupAdminDataTable"); $table->setSortable(false); $table->setEncodeEntities(false); $header = $display; $header[] = ""; $table->headerRow($header); foreach($matches as $match) { $match->setOutputFormatting(true); $editUrl = "{$config->urls->admin}page/edit/?id={$match->id}"; $viewUrl = $match->url(); $row = array(); foreach($display as $name) { $value = $match->get($name); if($value instanceof Page) $value = $value->name; $value = strip_tags($value); if($name == 'created' || $name == 'modified' || $name == 'published') $value = date('Y-m-d H:i:s', $value); $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); $row[] = "$value"; } $row[] = $match->editable() ? "" . $this->_('edit') . "" : ' '; $table->row($row); } if($matches->getTotal() > count($matches)) { /** @var MarkupPagerNav $pager */ $pager = $modules->get('MarkupPagerNav'); if($input->urlSegment1 == 'for') $pager->setBaseUrl($this->wire()->page->url . "for/"); $pager = $pager->render($matches); } else { $pager = ''; } $out = $pager . $table->render() . $pager; return $out; } /** * Render the provided matches as a JSON string for AJAX use * * @param PageArray $matches * @param array $display Array of fields to display, or display format associative array * @param string $selector * @return string * */ protected function renderMatchesAjax(PageArray $matches, $display, $selector) { $a = array( 'selector' => $selector, 'total' => $matches->getTotal(), 'limit' => $matches->getLimit(), 'start' => $matches->getStart(), 'matches' => array(), ); // determine which template label we'll be asking for (for multi-language support) $templateLabel = 'label'; if($this->wire()->languages) { $language = $this->wire()->user->language; if($language && !$language->isDefault()) $templateLabel = "label$language"; } foreach($matches as $page) { /** @var Page $page */ $p = array( 'id' => $page->id, 'parent_id' => $page->parent_id, 'template' => $page->template->name, 'path' => $page->path, 'name' => $page->name, ); if($this->adminSearchMode) { // don't include non-editable pages in admin search mode if(!$page->editable()) { $a['total']--; continue; } // include the type of match and URL to edit, when in adminSearchMode $p['type'] = $this->_x('Pages', 'match-type'); $p['editUrl'] = $page->editable() ? $page->editUrl() : ''; } if(isset($display['name']) && isset($display['format'])) { // use display format, returning a 'value' property containing the formatted value if($display['textOnly']) { $value = $page->getText($display['format'], true, false); } else { $value = $page->getMarkup($display['format']); } $p[$display['name']] = $value; } else { // use display fields foreach($display as $key) { if($key == 'template_label') { $p['template_label'] = $page->template->$templateLabel ? $page->template->$templateLabel : $page->template->label; if(empty($p['template_label'])) $p['template_label'] = $page->template->name; continue; } $value = $page->get($key); if(empty($value) && $this->adminSearchMode) { if($key == 'title') $value = $page->name; // prevent empty title } if(is_object($value)) $value = $this->setupObjectMatch($value); if(is_array($value)) $value = $this->setupArrayMatch($value); $p[$key] = $value; } } $a['matches'][] = $p; } return json_encode($a); } /** * Convert object to an array where possible, otherwise convert to a string * * For use by renderMatchesAjax * * @param Page|WireData|WireArray|Wire|object $o * @return array|string * */ protected function setupObjectMatch($o) { if($o instanceof Page) { return array( 'id' => $o->id, 'parent_id' => $o->parent_id, 'template' => $o->template->name, 'name' => $o->name, 'path' => $o->path, 'title' => $o->title ); } if($o instanceof WireData || $o instanceof WireArray) return $o->getArray(); return (string) $o; } /** * Filter an array converting any indexes containing objects to arrays or strings * * For use by renderMatchesAjax * * @param array $a * @return array * */ protected function setupArrayMatch(array $a) { foreach($a as $key => $value) { if(is_object($value)) $a[$key] = $this->setupObjectMatch($value); else if(is_array($value)) $a[$key] = $this->setupArrayMatch($value); } return $a; } /** * Render search for that submits to this process * * @param string $placeholder Value for placeholder attribute in search input * @return string * */ public function renderSearchForm($placeholder = '') { $sanitizer = $this->wire()->sanitizer; $q = substr((string) $this->wire()->input->get('q'), 0, 128); $q = $sanitizer->entities($q); $adminURL = $this->wire()->config->urls->admin; if($placeholder) { $placeholder = $sanitizer->entities1($placeholder); $placeholder = " placeholder='$placeholder'"; } else { $placeholder = ''; } $action = $adminURL . 'page/search/live/'; $out = "\n"; return $out; } public function getModuleConfigInputfields(array $data) { $modules = $this->wire()->modules; $adminLiveSearchLabel = $this->_('Admin live search'); $inputfields = $this->wire(new InputfieldWrapper()); $textFields = array(); $allSearchTypes = array('pages', 'trash', 'modules'); $textOperators = Selectors::getOperators(array( 'compareType' => Selector::compareTypeFind, 'getIndexType' => 'operator', 'getValueType' => 'label', )); $textOperators['='] = SelectorEqual::getLabel(); unset($textOperators['#=']); if(!isset($data['searchTypesOrder'])) $data['searchTypesOrder'] = array(); if(!isset($data['noSearchTypes'])) $data['noSearchTypes'] = array(); $searchTypesOrder = &$data['searchTypesOrder']; $noSearchTypes = &$data['noSearchTypes']; // find all text fields foreach($this->wire()->fields as $field) { if(!$field->type instanceof FieldtypeText) continue; $textFields[$field->name] = $field; } // ensure that base/built-in search types are present foreach($allSearchTypes as $key) { if(!in_array($key, $searchTypesOrder)) $searchTypesOrder[] = $key; } // find searchable modules foreach($modules as $module) { $info = $modules->getModuleInfoVerbose($module); if(empty($info['searchable'])) continue; $name = $info['searchable']; if(is_bool($name) || ctype_digit($name)) $name = $info['name']; $allSearchTypes[$name] = $name; if(!in_array($name, $searchTypesOrder)) $searchTypesOrder[] = $name; } /** @var InputfieldFieldset $fieldset */ $fieldset = $modules->get('InputfieldFieldset'); $fieldset->label = $adminLiveSearchLabel; $fieldset->icon = 'search'; $inputfields->add($fieldset); /** @var InputfieldAsmSelect $f */ $f = $modules->get('InputfieldAsmSelect'); $f->attr('name', 'searchTypesOrder'); $f->label = $this->_('Search order'); $f->description = $this->_('These are the types of searches that will be performed during an admin live search.') . ' ' . $this->_('Drag them to the order you want the search results to be listed in.'); foreach($allSearchTypes as $name) { $label = $name; if(in_array($name, $noSearchTypes)) $label .= ' ' . $this->_('(excluded)'); $f->addOption($name, $label); } $f->attr('value', $searchTypesOrder); $f->setAsmSelectOption('deletable', false); $f->setAsmSelectOption('addable', false); $fieldset->add($f); /** @var InputfieldAsmSelect $f */ $f = $modules->get('InputfieldAsmSelect'); $f->attr('name', 'noSearchTypes'); $f->label = $this->_('Exclude search types'); $f->description = $this->_('Select any search types that you want to exclude from live search. These might be types you don’t often need to search.') . ' ' . $this->_('The more types excluded, the faster the live search will perform.') . ' ' . $this->_('Any selected types can still be searched if asked for specifically in the search.') . ' ' . $this->_('For example, if you excluded the “trash” type, it could still be searched if you prefixed your search with “trash=”, like “trash=hello”.'); foreach($allSearchTypes as $name) { $f->addOption($name); } $f->attr('value', $noSearchTypes); $fieldset->add($f); /** @var InputfieldFieldset $fieldset */ $fieldset = $modules->get('InputfieldFieldset'); $fieldset->label = $adminLiveSearchLabel . ' ' . $this->_('(settings for pages type)'); $fieldset->icon = 'search'; $fieldset->themeOffset = 'm'; $inputfields->add($fieldset); /** @var InputfieldAsmSelect $f */ $f = $modules->get('InputfieldAsmSelect'); $f->attr('name', 'searchFields2'); $f->label = $this->_('Page fields to search'); $f->description = $this->_('This applies to search results from “pages” and “trash” only.') . ' ' . $this->_("We recommend limiting this to 1 or 2 fields at the most to ensure the live search is fast. Typically you would just search the “title” field."); // Fields to search description foreach($textFields as $field) $f->addOption($field->name); $value = isset($data['searchFields2']) ? $data['searchFields2'] : array('title'); $value = !is_array($value) ? explode(' ', $value) : $value; $f->value = $value; $fieldset->add($f); /** @var InputfieldAsmSelect $f */ $f = $modules->get('InputfieldAsmSelect'); $f->attr('name', 'searchFields'); $f->label = $this->_('Page fields to search if user hits “enter” in the search box'); $f->description = $this->_('Typically this would be the same as above, but you might also want to add additional field(s).') . ' ' . $this->_('For instance, rather than just searching the “title” field, you might want to also search a “body” field as well.'); foreach($textFields as $field) $f->addOption($field->name); $value = isset($data['searchFields']) ? $data['searchFields'] : array('title', 'body'); $value = !is_array($value) ? explode(' ', $value) : $value; $f->value = $value; $fieldset->append($f); /** @var InputfieldSelect $f */ $f = $modules->get("InputfieldSelect"); $f->attr('name', 'operator'); $f->attr('value', isset($data['operator']) ? $data['operator'] : self::defaultOperator); $f->label = $this->_('Default search operator for single and partial word searches'); $f->columnWidth = 50; foreach($textOperators as $operator => $label) { $f->addOption($operator, "$operator $label"); } $fieldset->append($f); /** @var InputfieldSelect $f */ $f = $modules->get("InputfieldSelect"); $f->attr('name', 'operator2'); $f->attr('value', isset($data['operator2']) ? $data['operator2'] : '~='); $f->label = $this->_('Default search operator for multi-word (phrase) searches'); $f->columnWidth = 50; foreach($textOperators as $operator => $label) { $f->addOption($operator, "$operator $label"); } $fieldset->append($f); // displayField: no longer used, except if user lacks page-lister permission /** @var InputfieldHidden $f */ $f = $modules->get("InputfieldHidden"); $f->attr('name', 'displayField'); $f->attr('value', isset($data['displayField']) ? $data['displayField'] : 'name'); $f->label = $this->_("Default field name(s) to display in search results"); $f->description = $this->_("If specifying more than one field, separate each with a space."); $inputfields->append($f); return $inputfields; } }