get($parent_id)->find() rather than $pages->find().
* @property int $template_id Limit results to pages using this template.
* @property array $template_ids Limit results to pages using this templates (alternate to the single template_id).
* @property string $labelFieldName Field to display in the results. (default=title)
* @property string $labelFieldFormat Format string to display in the results, overrides labelFieldName when used (default=blank).
* @property string $searchFields Field(s) to search for text. Separate multiple by a space. (default=title)
* @property string $operator Selector operator to use in performing the search (default: %=)
* @property string $findPagesSelector Optional selector to use for finding pages (default=blank)
* @property int $maxSelectedItems Maximum number of items that may be selected (0=unlimited, default).
* @property bool $useList Whether or not to use a separate selected list. If false specified, selected item will be populated directly to the input. (default=true)
* @property bool $allowAnyValue Allow any value to stay in the input, even if not selectable? (default=false)
* @property string $disableChars Autocomplete won't be triggered if input contains any of the characters in this string. (default=blank)
* @property bool $useAndWords When true, each word will be isolated to a separate searchFields=searchValue, which enables it to duplicate ~= operator behavior while using a %= operator (default=false).
* @property int $lang_id Force use of this language for results
*
* @method string renderList()
* @method string renderListItem($label, $value, $class = '')
* @method string getAjaxUrl() Hookable since 3.0.223
*
*/
class InputfieldPageAutocomplete extends Inputfield implements InputfieldHasArrayValue, InputfieldHasSortableValue {
public static function getModuleInfo() {
return array(
'title' => __('Page Auto Complete', __FILE__), // Module Title
'summary' => __('Multiple Page selection using auto completion and sorting capability. Intended for use as an input field for Page reference fields.', __FILE__), // Module Summary
'version' => 113,
);
}
/**
* Initialize variables used for the autocompletion
*
*/
public function init() {
parent::init();
// limit results to this parent, or if combined with a 'findPagesSelector',
// the search is performed as $pages->get($parent_id)->find() rather than $pages->find()
$this->set('parent_id', 0);
// limit results to pages using this template
$this->set('template_id', 0);
$this->set('template_ids', array());
// field to display in the results
$this->set('labelFieldName', 'title');
// format string to display in the results (instead of labelFieldName, when used)
$this->set('labelFieldFormat', '');
// field(s) to search for text, separate multiple by a space
$this->set('searchFields', 'title');
// operator to use in performing the search
$this->set('operator', '%=');
// optional selector to use for all other properties
$this->set('findPagesSelector', '');
// maximum number of items that may be selected
$this->set('maxSelectedItems', 0);
// whether or not to use the selection list
// if not used, selected value will be populated directly to input
$this->set('useList', true);
// allow a non-selectable value to stay in the autocomplete input?
// useful for situations where autocomplete might be used for collection of other values
// like when used with ProcessPageEditLink
$this->set('allowAnyValue', false);
// autocomplete won't be triggered if input contains any characters present in this string
$this->set('disableChars', '');
// when true, each word will be isolated to a separate searchFields=searchValue, which
// enables it to duplicate ~= operator behavior while using a %= operator.
$this->set('useAndWords', false);
// Force Language when applicable
$this->set('lang_id', 0);
// Whether to allow unpublished pages (null=not set, 0|false=no, 1|true=yes)
$this->set('allowUnpub', null);
}
/**
* Render a selected list item
*
* @param string $label
* @param string $value
* @param string $class
* @return string
*
*/
protected function ___renderListItem($label, $value, $class = '') {
if($class) $class = " $class";
if(strpos($label, '&') !== false) $label = $this->wire()->sanitizer->unentities($label);
$label = $this->wire()->sanitizer->entities($label);
$out =
"
";
return $out;
}
/**
* Convert the CSV string provided in the $input to an array of ints needed for this fieldtype
*
* @param WireInputData $input
* @return $this
*
*/
public function ___processInput(WireInputData $input) {
parent::___processInput($input);
$value = $this->attr('value');
if(is_array($value)) $value = reset($value);
$value = trim($value);
if(strpos($value, ',') !== false) {
$value = explode(',', $value);
} else if($value) {
$value = array($value);
} else {
$value = array();
}
foreach($value as $k => $v) {
if(empty($v)) {
unset($value[$k]);
} else {
$value[$k] = (int) $v;
}
}
$this->attr('value', $value);
return $this;
}
/**
* Get the AJAX search URL that will be queried (minus the actual term)
*
* This URL is focused on using the AJAX API from ProcessPageSearch
*
*/
protected function ___getAjaxUrl() {
$pipe = '%7C'; // encoded pipe "|"
$selector = (string) $this->findPagesSelector;
$selectorLength = strlen($selector);
$name = 'autocomplete_' . $this->attr('name');
$queryStrings = array();
/** @var ProcessPageSearch $pps */
$pps = $this->wire()->modules->get('ProcessPageSearch');
if($this->parent_id) {
if($selectorLength) {
// if a selector was specified, AND a parent, then we'll use the parent as a root
$selector .= ',has_parent=' . (int) $this->parent_id;
} else {
// otherwise matches must be direct children of the parent
$selector = 'parent_id=' . (int) $this->parent_id;
}
}
if(count($this->template_ids)) {
$selector .= ',templates_id=' . implode('|', $this->template_ids);
} else if($this->template_id) {
$selector .= ',templates_id=' . (int) $this->template_id;
}
$allowUnpub = $this->getSetting('allowUnpub');
if(!is_null($allowUnpub) && !$allowUnpub) {
$selector .= ",status<" . Page::statusUnpublished;
}
// allow for full site matches
if(!strlen($selector)) $selector = "id>0";
// include language
if($this->lang_id) $queryStrings[] = 'lang_id=' . (int) $this->lang_id;
// match no more than 50, unless selector specifies it's own limit
if(strpos($selector, 'limit=') === false) $queryStrings[] = 'limit=50';
// specify what label field we want to retrieve
if($this->labelFieldFormat) {
$pps->setDisplayFormat($name, $this->labelFieldFormat, true);
$queryStrings[] = "format_name=$name";
}
$queryStrings[] = 'get=' . urlencode($this->labelFieldName);
// tell ProcessPageSearch to store this selector which we can tell it to use
// by setting $_GET['for_selector_name'] = $name
$url = $pps->setForSelector($name, trim($selector, ', '));
if(count($queryStrings)) $url .= '&' . implode('&', $queryStrings);
// replace any pipes with encoded version
if(strpos($url, '|') !== false) $url = str_replace('|', $pipe, $url);
return $url;
}
/**
* Install the autocomplete module
*
* Make sure we're in InputfieldPage's list of valid page selection widgets
*
*/
public function ___install() {
$data = $this->wire()->modules->getConfig('InputfieldPage');
$data['inputfieldClasses'][] = $this->className();
$this->wire()->modules->saveConfig('InputfieldPage', $data);
}
/**
* Uninstall the autocomplete module
*
* Remove from InputfieldPage's list of page selection widgets
*
*/
public function ___uninstall() {
$data = $this->wire()->modules->getConfig('InputfieldPage');
foreach($data['inputfieldClasses'] as $key => $value) {
if($value == $this->className()) unset($data['inputfieldClasses'][$key]);
}
$this->wire()->modules->saveConfig('InputfieldPage', $data);
}
/**
* Provide configuration options for modifying the behavior when paired with InputfieldPage
*
*/
public function ___getConfigInputfields() {
$modules = $this->wire()->modules;
$inputfields = parent::___getConfigInputfields();
/** @var InputfieldRadios $field */
$field = $modules->get('InputfieldRadios');
$field->setAttribute('name', 'operator');
$field->label = $this->_('Autocomplete search operator');
$field->description = $this->_("The search operator that is used in the API when performing autocomplete matches.");
$field->notes = $this->_("If you aren't sure what you want here, leave it set at the default: *=");
$field->required = false;
$operators = Selectors::getOperators(array(
'compareType' => Selector::compareTypeFind,
'getIndexType' => 'operator',
'getValueType' => 'verbose',
));
foreach($operators as $operator => $info) {
if($operator === '#=') continue;
$opLabel = str_replace('*', '\*', $operator);
$field->addOption($operator, "`$opLabel` **$info[label]** — $info[description]");
}
$field->addOption('=', "`=` **" . SelectorEqual::getLabel() . "** — " . SelectorEqual::getDescription());
$field->attr('value', $this->operator);
$field->collapsed = Inputfield::collapsedNo;
$inputfields->add($field);
/** @var InputfieldText $field */
$field = $modules->get('InputfieldText');
$field->attr('name', 'searchFields');
$field->label = $this->_('Fields to query for autocomplete');
$field->description = $this->_('Enter the names of the fields that should have their text queried for autocomplete matches.');
$field->description .= ' ' . $this->_('Typically this would just be the title field, but you may add others by separating each with a space.');
$field->description .= ' ' . $this->_('Note that this is different from the “label field” because you are specifying what fields will be searched, not what fields will be shown.');
$field->collapsed = Inputfield::collapsedNo;
$field->attr('value', $this->searchFields);
$notes = $this->_('Indexed text fields include:');
foreach($this->wire()->fields as $f) {
/** @var Field $f */
if(!$f->type instanceof FieldtypeText) continue;
$notes .= ' ' . $f->name . ',';
}
$field->notes = rtrim($notes, ',');
$inputfields->add($field);
return $inputfields;
}
}