1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-18 12:31:17 +02:00

Some code cleanup in ProcessPageSearch, plus add a hook for processwire/processwire-issues#584

This commit is contained in:
Ryan Cramer
2018-05-08 08:48:52 -04:00
parent 5127be3b35
commit 2f20fe402c

View File

@@ -8,9 +8,11 @@
* For more details about how Process modules work, please see: * For more details about how Process modules work, please see:
* /wire/core/Process.php * /wire/core/Process.php
* *
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer * ProcessWire 3.x, Copyright 2018 by Ryan Cramer
* https://processwire.com * https://processwire.com
* *
* @method string findReady($selector)
*
*/ */
class ProcessPageSearch extends Process implements ConfigurableModule { class ProcessPageSearch extends Process implements ConfigurableModule {
@@ -25,8 +27,18 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
); );
} }
/**
* Default operator for text searches
*
*/
const defaultOperator = '%='; const defaultOperator = '%=';
/**
* Native/system sortable properties
*
* @var array
*
*/
protected $nativeSorts = array( protected $nativeSorts = array(
'relevance', 'relevance',
'name', 'name',
@@ -46,11 +58,36 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
'sortfield', 'sortfield',
); );
/**
* Names of all Field objects in PW
*
* @var array
*
*/
protected $fieldOptions = array(); protected $fieldOptions = array();
protected $customSorts = array();
/**
* All operators where key is operator and value is description
*
* @var array
*
*/
protected $operators = array(); protected $operators = array();
/**
* Items per pagination
*
* @var int
*
*/
protected $resultLimit = 25; protected $resultLimit = 25;
protected $maxLimit = 250;
/**
* Lister instance, when applicable
*
* @var null|ProcessPageLister
*
*/
protected $lister = null; protected $lister = null;
/** /**
@@ -62,6 +99,10 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
*/ */
protected $adminSearchMode = false; protected $adminSearchMode = false;
/**
* Initialize module
*
*/
public function init() { public function init() {
foreach($this->fields as $field) { foreach($this->fields as $field) {
@@ -74,6 +115,12 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
parent::init(); parent::init();
} }
/**
* Get operators used for searches, where key is operator and value is description
*
* @return array
*
*/
static public function getOperators() { static public function getOperators() {
$f = __FILE__; $f = __FILE__;
return array( return array(
@@ -105,6 +152,20 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$this->operators = self::getOperators(); $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)
*
*/
protected function ___findReady($selector) {
return $selector;
}
/** /**
* Perform an interactive search and provide a search form (default) * Perform an interactive search and provide a search form (default)
@@ -140,6 +201,7 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$lister->columns = $this->getDisplayFields(); $lister->columns = $this->getDisplayFields();
return $lister->execute(); return $lister->execute();
} else { } else {
$selector = $this->findReady($selector);
$matches = $this->pages->find($selector); $matches = $this->pages->find($selector);
return $this->render($matches, $displaySelector); return $this->render($matches, $displaySelector);
} }
@@ -157,7 +219,6 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$this->fullSetup(); $this->fullSetup();
$selector = ''; $selector = '';
$displaySelector = '';
$limit = $this->resultLimit; $limit = $this->resultLimit;
$start = 0; $start = 0;
$status = 0; $status = 0;
@@ -314,6 +375,7 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
} }
} }
$selector = $this->findReady($selector);
$items = $this->pages->find($selector); $items = $this->pages->find($selector);
if(!$superuser && $checkEditAccess) { if(!$superuser && $checkEditAccess) {
@@ -396,11 +458,24 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
/** /**
* Render the search results * Render the search results
* *
* @param PageArray $matches
* @param string $displaySelector
* @return string
*
*/ */
protected function render(PageArray $matches, $displaySelector) { protected function render(PageArray $matches, $displaySelector) {
$out = ''; $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));
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 // determine what fields will be displayed
$display = array(); $display = array();
@@ -423,7 +498,10 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$class = 'show_options'; $class = 'show_options';
} }
$out .= "\n<div id='ProcessPageSearchResults' class='$class'>" . $this->renderMatchesTable($matches, $display) . "\n</div>"; $out .=
"\n<div id='ProcessPageSearchResults' class='$class'>" .
$this->renderMatchesTable($matches, $display) .
"\n</div>";
} }
return $out; return $out;
@@ -432,11 +510,21 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
/** /**
* Build a selector based upon interactive choices from the search form * Build a selector based upon interactive choices from the search form
* *
* Only used by execute(), not used by executeFor()
*
* ~~~~~
* Returns array(
* 0 => $selector, // string, main selector for search
* 1 => $displaySelector, // string, selector for display purposes
* 2 => $initSelector, // string, selector for initialization in Lister (the part user cannot change)
* 3 => $defaultSelector // string default selector used by Lister (the part user can change)
* );
* ~~~~~
* @return array
*
*/ */
protected function buildSelector() { protected function buildSelector() {
$selector = ''; // for regular ProcessPageSearch $selector = ''; // for regular ProcessPageSearch
$initSelector = ''; // for Lister, non-changable part of the selector
$defaultSelector = ''; // for Lister, changeable filters
// search query text // search query text
$q = $this->input->whitelist('q'); $q = $this->input->whitelist('q');
@@ -485,7 +573,7 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$s = ''; // anything added to this will be populated to both $selector and $initSelector below $s = ''; // anything added to this will be populated to both $selector and $initSelector below
// limit results for pagination // limit results for pagination
$s = ", limit={$this->resultLimit}"; $s .= ", limit={$this->resultLimit}";
$adminRootPage = $this->wire('pages')->get($this->wire('config')->adminRootPageID); $adminRootPage = $this->wire('pages')->get($this->wire('config')->adminRootPageID);
@@ -560,8 +648,12 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$this->input->whitelist('sort', 'relevance'); $this->input->whitelist('sort', 'relevance');
if($this->input->get->sort) { if($this->input->get->sort) {
$sort = $this->sanitizer->fieldName($this->input->get->sort); $sort = $this->sanitizer->fieldName($this->input->get->sort);
if($sort && (in_array($sort, $this->nativeSorts) || in_array($sort, $this->fieldOptions))) $this->input->whitelist('sort', $sort); if($sort && (in_array($sort, $this->nativeSorts) || in_array($sort, $this->fieldOptions))) {
if($this->input->get->reverse) $this->input->whitelist('reverse', 1); $this->input->whitelist('sort', $sort);
}
if($this->input->get->reverse) {
$this->input->whitelist('reverse', 1);
}
} }
// template // template
@@ -577,6 +669,10 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
/** /**
* Is the given field name selectable? * Is the given field name selectable?
* *
* @param string $name
* @param int $level
* @return bool
*
*/ */
protected function isSelectableFieldName($name, $level = 0) { protected function isSelectableFieldName($name, $level = 0) {
@@ -618,12 +714,14 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$out = "\n\t<p id='wrap_search_query'>"; $out = "\n\t<p id='wrap_search_query'>";
$out .= "\n\t<p id='wrap_search_field'>" . $out .=
"\n\t<p id='wrap_search_field'>" .
"\n\t<label for='search_field'>" . $this->_('Search in field(s):') . "</label>" . "\n\t<label for='search_field'>" . $this->_('Search in field(s):') . "</label>" .
"\n\t<input type='text' name='field' value='" . htmlentities($this->searchFields, ENT_QUOTES) . "' />" . "\n\t<input type='text' name='field' value='" . htmlentities($this->searchFields, ENT_QUOTES) . "' />" .
"\n\t</p>"; "\n\t</p>";
$out .= "\n\t<p id='wrap_search_operator'>" . $out .=
"\n\t<p id='wrap_search_operator'>" .
"\n\t<label for='search_operator'>" . $this->_('Type of search:') . "</label>" . "\n\t<label for='search_operator'>" . $this->_('Type of search:') . "</label>" .
"\n\t<select id='search_operator' name='operator'>"; "\n\t<select id='search_operator' name='operator'>";
@@ -633,10 +731,12 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$out .= "\n\t\t<option$attrs value='$n'>$desc (a" . htmlentities($operator) . "b)</option>"; $out .= "\n\t\t<option$attrs value='$n'>$desc (a" . htmlentities($operator) . "b)</option>";
$n++; $n++;
} }
$out .= "\n\t</select>" . $out .=
"\n\t</select>" .
"\n\t</p>"; "\n\t</p>";
$out .= "\n\t<label class='ui-priority-primary' for='search_query'>" . $this->_('Search for:') . "</label>" . $out .=
"\n\t<label class='ui-priority-primary' for='search_query'>" . $this->_('Search for:') . "</label>" .
"\n\t<input id='search_query' type='text' name='q' value='" . htmlentities($this->input->whitelist('q'), ENT_QUOTES, "UTF-8") . "' />" . "\n\t<input id='search_query' type='text' name='q' value='" . htmlentities($this->input->whitelist('q'), ENT_QUOTES, "UTF-8") . "' />" .
"\n\t<input type='hidden' name='show_options' value='1' />" . "\n\t<input type='hidden' name='show_options' value='1' />" .
"\n\t</p>"; "\n\t</p>";
@@ -646,7 +746,8 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$advCollapsed = true; $advCollapsed = true;
$out2 = "\n\t<p id='wrap_search_template'>" . $out2 =
"\n\t<p id='wrap_search_template'>" .
"\n\t<label for='search_template'>" . $this->_('Limit to template:') . "</label>" . "\n\t<label for='search_template'>" . $this->_('Limit to template:') . "</label>" .
"\n\t<select id='search_template' name='template'>" . "\n\t<select id='search_template' name='template'>" .
"\n\t\t<option></option>"; "\n\t\t<option></option>";
@@ -658,11 +759,13 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$out2 .= "\n\t<option$attrs>{$template->name}</option>"; $out2 .= "\n\t<option$attrs>{$template->name}</option>";
} }
$out2 .= "\n\t</select>" . $out2 .=
"\n\t</select>" .
"\n\t</p>"; "\n\t</p>";
$out2.= "\n\t<p id='wrap_search_sort'>" . $out2.=
"\n\t<p id='wrap_search_sort'>" .
"\n\t<label for='search_sort'>" . $this->_('Sort by:') . "</label>" . "\n\t<label for='search_sort'>" . $this->_('Sort by:') . "</label>" .
"\n\t<select id='search_sort' name='sort'>"; "\n\t<select id='search_sort' name='sort'>";
@@ -677,35 +780,41 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$out2 .= "\n\t\t<option$attrs>$s</option>"; $out2 .= "\n\t\t<option$attrs>$s</option>";
} }
$out2 .= "\n\t</select>" . $out2 .=
"\n\t</select>" .
"\n\t</p>"; "\n\t</p>";
if($sort != 'relevance') { if($sort != 'relevance') {
$reverse = $this->input->whitelist('reverse'); $reverse = $this->input->whitelist('reverse');
$out2 .= "\n\t<p id='wrap_search_options'>" . $out2 .=
"\n\t<p id='wrap_search_options'>" .
"\n\t<label><input type='checkbox' name='reverse' value='1' " . ($reverse ? "checked='checked' " : '') . "/> " . $this->_('Reverse sort?') . "</label>" . "\n\t<label><input type='checkbox' name='reverse' value='1' " . ($reverse ? "checked='checked' " : '') . "/> " . $this->_('Reverse sort?') . "</label>" .
"\n\t</p>"; "\n\t</p>";
if($reverse) $advCollapsed = false; if($reverse) $advCollapsed = false;
} }
$display = $this->input->whitelist('display'); $display = $this->input->whitelist('display');
$out2.= "\n\t<p id='wrap_search_display'>" . $out2 .=
"\n\t<p id='wrap_search_display'>" .
"\n\t<label for='search_display'>" . $this->_('Display field(s):') . "</label>" . "\n\t<label for='search_display'>" . $this->_('Display field(s):') . "</label>" .
"\n\t<input type='text' name='display' value='" . htmlentities($display, ENT_QUOTES) . "' />" . "\n\t<input type='text' name='display' value='" . htmlentities($display, ENT_QUOTES) . "' />" .
"\n\t</p>"; "\n\t</p>";
if($display && $display != 'title,path') $advCollapsed = false; if($display && $display != 'title,path') $advCollapsed = false;
/** @var InputfieldSubmit $submit */
$submit = $this->modules->get("InputfieldSubmit"); $submit = $this->modules->get("InputfieldSubmit");
$submit->attr('name', 'submit'); $submit->attr('name', 'submit');
$submit->attr('value', $this->_x('Search', 'submit')); // Search submit button for advanced search $submit->attr('value', $this->_x('Search', 'submit')); // Search submit button for advanced search
$out .= "<p>" . $submit->render() . "</p>"; $out .= "<p>" . $submit->render() . "</p>";
/** @var InputfieldForm $form */
$form = $this->modules->get("InputfieldForm"); $form = $this->modules->get("InputfieldForm");
$form->attr('id', 'ProcessPageSearchOptionsForm'); $form->attr('id', 'ProcessPageSearchOptionsForm');
$form->method = 'get'; $form->method = 'get';
$form->action = './'; $form->action = './';
/** @var InputfieldMarkup $field */
$field = $this->modules->get("InputfieldMarkup"); $field = $this->modules->get("InputfieldMarkup");
$field->label = $this->_("Search Options"); $field->label = $this->_("Search Options");
$field->value = $out; $field->value = $out;
@@ -719,7 +828,7 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$form->add($field); $form->add($field);
/* Remove temporarily /* no longer in use
$field = $this->modules->get("InputfieldMarkup"); $field = $this->modules->get("InputfieldMarkup");
$field->id = 'ProcessPageSearchShortcuts'; $field->id = 'ProcessPageSearchShortcuts';
$field->collapsed = true; $field->collapsed = true;
@@ -733,46 +842,22 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
} }
protected function renderShortcuts() {
$out = ''; /**
$links = array( * Render a table of matches
'Quick Links', *
"All by creation date" => '?q=&submit=Search&display=title+path+created&sort=created&reverse=1' , * @param PageArray $matches
"All by latest edit date" => '?q=&submit=Search&display=title+path+created&sort=modified&reverse=1', * @param array $display Fields to display (from getDisplayFields method)
"Users by creation date" => '?q=&template=user&submit=Search&operator=~%3D&display=name+email+created&sort=created&reverse=1', * @return string
'New pages by template', *
); */
protected function renderMatchesTable(PageArray $matches, array $display) {
foreach($this->templates as $template) {
// Quick links only for content with more than one page
// if($template->getNumPages() < 2) continue;
// Users get own quick link earlier, others are rather irrelevant
if($template->flags & Template::flagSystem) continue;
$links[$template->name] = "?q=&template={$template->name}&submit=Search&operator=~%3D&display=title+path+created&sort=created&reverse=1";
}
foreach($links as $label => $value) {
if(is_int($label)) {
$out .= "<h4>$value</h4>";
} else {
$value .= "&show_options=1";
$value = htmlspecialchars($value);
$out .= "<a href='$value'>$label</a>";
}
}
return $out;
}
protected function renderMatchesTable(PageArray $matches, array $display, $id = 'ProcessPageSearchResultsList') {
if(!count($display)) $display = array('path'); if(!count($display)) $display = array('path');
$out = ''; $out = '';
if(!count($matches)) return $out; if(!count($matches)) return $out;
/** @var MarkupAdminDataTable $table */
$table = $this->modules->get("MarkupAdminDataTable"); $table = $this->modules->get("MarkupAdminDataTable");
$table->setSortable(false); $table->setSortable(false);
$table->setEncodeEntities(false); $table->setEncodeEntities(false);
@@ -817,7 +902,7 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
* *
* Applicable to adminSearchMode only. * Applicable to adminSearchMode only.
* *
* @param $q Text to find * @param string $q Text to find
* @return array Array of matches * @return array Array of matches
* *
*/ */
@@ -872,8 +957,9 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
* Render the provided matches as a JSON string for AJAX use * Render the provided matches as a JSON string for AJAX use
* *
* @param PageArray $matches * @param PageArray $matches
* @param array Array of fields to display, or display format associative array * @param array $display Array of fields to display, or display format associative array
* @param string $selector * @param string $selector
* @return string
* *
*/ */
protected function renderMatchesAjax(PageArray $matches, $display, $selector) { protected function renderMatchesAjax(PageArray $matches, $display, $selector) {
@@ -965,6 +1051,9 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
* *
* For use by renderMatchesAjax * For use by renderMatchesAjax
* *
* @param Page|WireData|WireArray|Wire|object $o
* @return array
*
*/ */
protected function setupObjectMatch($o) { protected function setupObjectMatch($o) {
if($o instanceof Page) { if($o instanceof Page) {
@@ -986,6 +1075,9 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
* *
* For use by renderMatchesAjax * For use by renderMatchesAjax
* *
* @param array $a
* @return array
*
*/ */
protected function setupArrayMatch(array $a) { protected function setupArrayMatch(array $a) {
foreach($a as $key => $value) { foreach($a as $key => $value) {
@@ -995,6 +1087,13 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
return $a; 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 = '') { public function renderSearchForm($placeholder = '') {
$q = substr($this->input->get->q, 0, 128); $q = substr($this->input->get->q, 0, 128);
@@ -1008,9 +1107,10 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
$placeholder = ''; $placeholder = '';
} }
$out = "\n<form id='ProcessPageSearchForm' data-action='{$adminURL}page/search/' action='{$adminURL}page/search/' method='get'>" . $out =
"\n<form id='ProcessPageSearchForm' data-action='{$adminURL}page/search/' action='{$adminURL}page/search/' method='get'>" .
"\n\t<label for='ProcessPageSearchQuery'><i class='fa fa-search'></i></label>" . "\n\t<label for='ProcessPageSearchQuery'><i class='fa fa-search'></i></label>" .
"\n\t<input type='text' id='ProcessPageSearchQuery' name='q' value='$q'$placeholder />" . "\n\t<input type='text' id='ProcessPageSearchQuery' name='q' value='$q' $placeholder />" .
"\n\t<input type='submit' id='ProcessPageSearchSubmit' name='search' value='Search' />" . //" . $this->_x('Search', 'input') . "' />" . // Text that appears as the placeholder text in the top search submit input "\n\t<input type='submit' id='ProcessPageSearchSubmit' name='search' value='Search' />" . //" . $this->_x('Search', 'input') . "' />" . // Text that appears as the placeholder text in the top search submit input
"\n\t<input type='hidden' name='show_options' value='1' />" . "\n\t<input type='hidden' name='show_options' value='1' />" .
"\n\t<span id='ProcessPageSearchStatus'></span>" . "\n\t<span id='ProcessPageSearchStatus'></span>" .
@@ -1064,5 +1164,42 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
return $inputfields; return $inputfields;
} }
/*
* No longer in use, but here for reference:
*
protected function renderShortcuts() {
$out = '';
$links = array(
'Quick Links',
"All by creation date" => '?q=&submit=Search&display=title+path+created&sort=created&reverse=1' ,
"All by latest edit date" => '?q=&submit=Search&display=title+path+created&sort=modified&reverse=1',
"Users by creation date" => '?q=&template=user&submit=Search&operator=~%3D&display=name+email+created&sort=created&reverse=1',
'New pages by template',
);
foreach($this->templates as $template) {
// Quick links only for content with more than one page
// if($template->getNumPages() < 2) continue;
// Users get own quick link earlier, others are rather irrelevant
if($template->flags & Template::flagSystem) continue;
$links[$template->name] = "?q=&template={$template->name}&submit=Search&operator=~%3D&display=title+path+created&sort=created&reverse=1";
}
foreach($links as $label => $value) {
if(is_int($label)) {
$out .= "<h4>$value</h4>";
} else {
$value .= "&show_options=1";
$value = htmlspecialchars($value);
$out .= "<a href='$value'>$label</a>";
}
}
return $out;
}
*/
} }