1603 lines
54 KiB
Text
1603 lines
54 KiB
Text
<?php namespace ProcessWire;
|
|
|
|
/**
|
|
* An Inputfield for handling relational Page inputs
|
|
*
|
|
* Delegates the actual input control to a user-defined Inputfield derived from InputfieldSelect
|
|
*
|
|
* @method PageArray|null getSelectablePages(Page $page)
|
|
* @method PageArray findPagesCode(Page $page)
|
|
*
|
|
* Can be accessed from $this or from $field:
|
|
*
|
|
* @property int $template_id
|
|
* @property array $template_ids
|
|
* @property int $parent_id
|
|
* @property string $inputfield Inputfield class used for input
|
|
* @property string $labelFieldName Field name to use for label (note: this will be "." if $labelFieldFormat is in use).
|
|
* @property string $labelFieldFormat Formatting string for $page->getMarkup() as alternative to $labelFieldName
|
|
* @property string $findPagesCode
|
|
* @property string $findPagesSelector
|
|
* @property string $findPagesSelect Same as findPageSelector, but configured interactively with InputfieldSelector .
|
|
* @property int|bool $addable
|
|
* @property int|bool $allowUnpub
|
|
* @property int $derefAsPage
|
|
* @property-read string $inputfieldClass Public property alias of protected getInputfieldClass() method
|
|
* @property array $inputfieldClasses
|
|
*
|
|
* @method string renderAddable()
|
|
* @method void processInputAddPages(WireInputData $input)
|
|
*
|
|
* @todo make findPagesCode disabled by default
|
|
*
|
|
*/
|
|
class InputfieldPage extends Inputfield implements ConfigurableModule {
|
|
|
|
public static function getModuleInfo() {
|
|
return array(
|
|
'title' => 'Page',
|
|
'version' => 108,
|
|
'summary' => 'Select one or more pages',
|
|
'permanent' => true,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @var Inputfield|null
|
|
*
|
|
*/
|
|
protected $inputfieldWidget = null;
|
|
|
|
/**
|
|
* Default options for Inputfield classes
|
|
*
|
|
* @var array
|
|
*
|
|
*/
|
|
protected static $defaultInputfieldClasses = array(
|
|
'InputfieldSelect',
|
|
'InputfieldSelectMultiple',
|
|
'InputfieldCheckboxes',
|
|
'InputfieldRadios',
|
|
'InputfieldAsmSelect',
|
|
'InputfieldPageListSelect',
|
|
'InputfieldPageAutocomplete',
|
|
'InputfieldTextTags',
|
|
);
|
|
|
|
/**
|
|
* Default configuration values
|
|
*
|
|
* @var array
|
|
*
|
|
*/
|
|
protected static $defaultConfig = array(
|
|
'parent_id' => 0,
|
|
'template_id' => 0,
|
|
'template_ids' => array(),
|
|
'inputfield' => '',
|
|
'labelFieldName' => '',
|
|
'labelFieldFormat' => '',
|
|
'findPagesCode' => '',
|
|
'findPagesSelect' => '',
|
|
'findPagesSelector' => '',
|
|
'derefAsPage' => 0,
|
|
'addable' => 0,
|
|
'allowUnpub' => 0, // This option configured by FieldtypePage:Advanced
|
|
);
|
|
|
|
/**
|
|
* Contains true when this module is in configuration state (via it's getConfigInputfields function)
|
|
*
|
|
*/
|
|
protected $configMode = false;
|
|
|
|
/**
|
|
* True when processInput is currently processing
|
|
*
|
|
*/
|
|
protected $processInputMode = false;
|
|
|
|
/**
|
|
* True when in renderValue mode
|
|
*
|
|
* @var bool
|
|
*
|
|
*/
|
|
protected $renderValueMode = false;
|
|
|
|
/**
|
|
* PageArray of pages that were added in the request
|
|
*
|
|
* @var PageArray|null
|
|
*
|
|
*/
|
|
protected $pagesAdded;
|
|
|
|
/**
|
|
* CSS class names added to the Inputfield (will be applied to delegate Inputfield)
|
|
*
|
|
* @var array
|
|
*
|
|
*/
|
|
protected $classesAdded = array();
|
|
|
|
/**
|
|
* Construct
|
|
*
|
|
*/
|
|
public function __construct() {
|
|
$this->set('inputfieldClasses', self::$defaultInputfieldClasses);
|
|
parent::__construct();
|
|
}
|
|
|
|
/**
|
|
* Init (populate default values)
|
|
*
|
|
*/
|
|
public function init() {
|
|
foreach(self::$defaultConfig as $key => $value) {
|
|
$this->set($key, $value);
|
|
}
|
|
$this->attr('value', $this->wire()->pages->newPageArray());
|
|
parent::init();
|
|
}
|
|
|
|
/**
|
|
* Add a CSS class name (extends Inputfield::addClass)
|
|
*
|
|
* @param array|string $class
|
|
* @param string $property
|
|
* @return InputfieldPage|Inputfield
|
|
*
|
|
*/
|
|
public function addClass($class, $property = 'class') {
|
|
if($property == 'class') {
|
|
$this->classesAdded[] = $class;
|
|
}
|
|
return parent::addClass($class, $property);
|
|
}
|
|
|
|
/**
|
|
* Set an input attribute
|
|
*
|
|
* Overrides Inputfield::setAttribute() to capture 'value' attribute
|
|
*
|
|
* @param array|string $key
|
|
* @param array|int|string $value
|
|
* @return InputfieldPage|Inputfield
|
|
*
|
|
*/
|
|
public function setAttribute($key, $value) {
|
|
|
|
if($key == 'value') {
|
|
$pages = $this->wire()->pages;
|
|
if(is_string($value) || is_int($value)) {
|
|
// setting the value attr from a string, whether 1234 or 123|446|789
|
|
|
|
if(ctype_digit("$value")) {
|
|
// i.e. "1234"
|
|
$a = $pages->newPageArray();
|
|
$page = $pages->get((int) $value);
|
|
if($page->id) $a->add($page);
|
|
$value = $a;
|
|
|
|
} else if(strpos($value, '|') !== false) {
|
|
// i.e. 123|456|789
|
|
$a = $pages->newPageArray();
|
|
foreach(explode('|', $value) as $id) {
|
|
if(!ctype_digit("$id")) continue;
|
|
$page = $pages->get((int) $id);
|
|
if($page->id) $a->add($page);
|
|
}
|
|
$value = $a;
|
|
} else {
|
|
// unrecognized format
|
|
}
|
|
}
|
|
|
|
}
|
|
return parent::setAttribute($key, $value);
|
|
}
|
|
|
|
/**
|
|
* Is the given $page valid for the given $field?
|
|
*
|
|
* Note that this validates all but findPagesCode (eval) based page selections.
|
|
* This is primarily for use by FieldtypePage, but kept here since the config options
|
|
* it uses to check are part of this module's config.
|
|
*
|
|
* If false is returned and given an $editPage, a reason for the false will be populated
|
|
* to the $editPage->_isValidPage property.
|
|
*
|
|
* @param Page $page
|
|
* @param Field|InputfieldPage|string|int $field Field instance of field name (string) or ID
|
|
* @param Page $editPage Page being edited
|
|
* @return bool
|
|
* @throws WireException
|
|
*
|
|
*/
|
|
public static function isValidPage(Page $page, $field, Page $editPage = null) {
|
|
|
|
$pages = $page->wire()->pages;
|
|
$user = $page->wire()->user;
|
|
|
|
if(!$field instanceof Field && !$field instanceof InputfieldPage) {
|
|
$field = $page->wire()->fields->get($field);
|
|
if(!$field instanceof Field) throw new WireException('isValidPage requires a valid Field or field name');
|
|
}
|
|
|
|
if($editPage && $editPage->id && $page->id == $editPage->id) {
|
|
$editPage->setQuietly('_isValidPage', "Page is referencing itself and circular page reference not allowed");
|
|
return false; // prevent circular reference
|
|
}
|
|
|
|
if($pages->cloning) {
|
|
return true; // bypass check when cloning is active
|
|
}
|
|
|
|
$valid = true;
|
|
$findPagesSelector = $field->get('findPagesSelector');
|
|
if(empty($findPagesSelector)) $findPagesSelector = $field->get('findPagesSelect');
|
|
|
|
if($findPagesSelector) {
|
|
$selector = $findPagesSelector;
|
|
if($editPage && $editPage->id) $selector = self::populateFindPagesSelector($editPage, $selector);
|
|
if(!$page->matches($selector)) {
|
|
// failed in-memory check, attempt $page->count() check...
|
|
$selector .= ", id=$page->id";
|
|
if($pages->count($selector)) {
|
|
// looks like its okay
|
|
} else {
|
|
// also fails $pages->cont() check, so definitely not valid
|
|
if($editPage) {
|
|
$editPage->setQuietly('_isValidPage',
|
|
"Page $page does not match " .
|
|
($user->isSuperuser() ? "findPagesSelector: $selector" : "required selector")
|
|
);
|
|
}
|
|
$valid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if($field->findPagesCode) { } // we don't currently validate these
|
|
|
|
$parent_id = $field->get('parent_id');
|
|
if($parent_id && $parent_id != $page->parent_id) {
|
|
$inputfieldClass = ltrim($field->get('inputfield'), '_');
|
|
if(empty($inputfieldClass)) $inputfieldClass = 'InputfieldSelect';
|
|
if(version_compare(PHP_VERSION, '5.3.8') >= 0) {
|
|
$interfaces = wireClassImplements($inputfieldClass);
|
|
if(in_array('InputfieldPageListSelection', $interfaces)) {
|
|
// parent_id represents a root parent
|
|
$rootParent = $pages->get($parent_id);
|
|
if(!$page->parents()->has($rootParent)) $valid = false;
|
|
} else {
|
|
// parent_id represents a direct parent
|
|
$valid = false;
|
|
}
|
|
if(!$valid && $editPage) {
|
|
$editPage->setQuietly('_isValidPage', "Page $page does not have required parent $parent_id");
|
|
}
|
|
} else {
|
|
// PHP version prior to 5.3.8
|
|
// @deprecated
|
|
$reflector = new \ReflectionClass($inputfieldClass);
|
|
$valid = $reflector->implementsInterface('InputfieldPageListSelection');
|
|
}
|
|
}
|
|
|
|
$hasRequiredTemplate = true;
|
|
$template_ids = FieldtypePage::getTemplateIDs($field);
|
|
if(!empty($template_ids)) {
|
|
$hasRequiredTemplate = in_array($page->template->id, $template_ids);
|
|
}
|
|
if(!$hasRequiredTemplate) {
|
|
$valid = false;
|
|
if($editPage) {
|
|
$editPage->setQuietly('_isValidPage', "Page $page does not have required template(s): " . implode(',', $template_ids));
|
|
}
|
|
}
|
|
|
|
return $valid;
|
|
}
|
|
|
|
/**
|
|
* Execute the findPagesCode
|
|
*
|
|
* @param Page $page The page being edited
|
|
* @return PageArray (hopefully)
|
|
* @deprecated Use hook to InputfieldPage::getSelectablePages() instead
|
|
*
|
|
*/
|
|
protected function ___findPagesCode(Page $page) {
|
|
$pages = $this->wire()->pages; // so that it is locally scoped to the eval
|
|
if(empty($this->findPagesCode)) return $pages->newPageArray();
|
|
return eval($this->findPagesCode);
|
|
}
|
|
|
|
public function has($key) {
|
|
// ensures it accepts any config value (like those for delegate inputfields)
|
|
return true;
|
|
}
|
|
|
|
public function getSetting($key) {
|
|
if($key === 'inputfieldClass') return $this->getInputfieldClass();
|
|
if($key === 'template_ids') return $this->getTemplateIDs();
|
|
$value = parent::getSetting($key);
|
|
if($key === 'template_id' && empty($value)) {
|
|
$templateIDs = $this->getTemplateIDs();
|
|
if(!empty($templateIDs)) $value = reset($templateIDs);
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Return PageArray of selectable pages for this input
|
|
*
|
|
* @param Page $page The Page being edited
|
|
* @return PageArray|null
|
|
*
|
|
*/
|
|
public function ___getSelectablePages(Page $page) {
|
|
|
|
$pages = $this->wire()->pages;
|
|
$lockedModes = array(Inputfield::collapsedNoLocked, Inputfield::collapsedYesLocked, Inputfield::collapsedBlankLocked);
|
|
$statusUnder = $this->allowUnpub ? Page::statusTrash : Page::statusUnpublished;
|
|
$children = null;
|
|
$templateIDs = $this->getTemplateIDs(true);
|
|
$findPagesSelector = $this->getSetting('findPagesSelector');
|
|
if(empty($findPagesSelector)) $findPagesSelector = $this->getSetting('findPagesSelect');
|
|
|
|
if($this->configMode) {
|
|
$children = $pages->newPageArray();
|
|
|
|
} else if($this->renderValueMode || in_array($this->getSetting('collapsed'), $lockedModes)) {
|
|
$children = $this->attr('value');
|
|
// convert to PageArray if not already
|
|
if($children instanceof Page) {
|
|
$children = $children->and();
|
|
} else if(!$children instanceof PageArray) {
|
|
$children = $pages->newPageArray();
|
|
}
|
|
|
|
} else if($findPagesSelector) {
|
|
// a find() selector
|
|
if($this->processInputMode) {
|
|
$instance = $this;
|
|
} else if(strpos($findPagesSelector, '=page.') || strpos($findPagesSelector, '=item.')) {
|
|
$instance = $this;
|
|
} else {
|
|
$instance = null;
|
|
}
|
|
$selector = self::populateFindPagesSelector($page, $findPagesSelector, $instance);
|
|
if($templateIDs) $selector = trim("$selector, templates_id=$templateIDs", ", ");
|
|
if($this->parent_id) $selector = trim("$selector, parent_id=$this->parent_id", ", ");
|
|
$children = $pages->find($selector);
|
|
|
|
} else if($this->findPagesCode) {
|
|
// php statement that returns a PageArray or a Page (to represent a parent)
|
|
$children = $this->findPagesCode($page);
|
|
if($children instanceof Page) $children = $children->children(); // @teppokoivula
|
|
|
|
} else if($this->parent_id) {
|
|
$parent = $pages->get($this->parent_id);
|
|
if($parent) {
|
|
if($templateIDs) {
|
|
$children = $parent->children("templates_id=$templateIDs, check_access=0, status<$statusUnder");
|
|
} else {
|
|
$children = $parent->children("check_access=0, status<$statusUnder");
|
|
}
|
|
}
|
|
|
|
} else if($templateIDs) {
|
|
$children = $pages->find("templates_id=$templateIDs, check_access=0, status<$statusUnder");
|
|
|
|
} else {
|
|
$children = $pages->newPageArray();
|
|
}
|
|
|
|
if($children && $children->has($page)) {
|
|
$children->remove($page); // don't allow page being edited to be selected
|
|
}
|
|
|
|
return $children;
|
|
}
|
|
|
|
/**
|
|
* Return array or string of configured template IDs
|
|
*
|
|
* @param bool $getString Specify true to return a 1|2|3 style string rather than an array
|
|
* @return array|string
|
|
*
|
|
*/
|
|
public function getTemplateIDs($getString = false) {
|
|
$templateIDs = parent::getSetting('template_ids');
|
|
$templateID = parent::getSetting('template_id');
|
|
return FieldtypePage::getTemplateIDs(array($templateIDs, $templateID), $getString);
|
|
}
|
|
|
|
/**
|
|
* Populate any variables in findPagesSelector
|
|
*
|
|
* @param Page $page
|
|
* @param string $selector
|
|
* @param Inputfield $inputfield
|
|
* @return string
|
|
*
|
|
*/
|
|
protected static function populateFindPagesSelector(Page $page, $selector, $inputfield = null) {
|
|
|
|
// find variables identified by: page.field or page.field.subfield
|
|
if(strpos($selector, '=page.') !== false || strpos($selector, '=item.') !== false) {
|
|
|
|
// if an $inputfield is passed in, then we want to retrieve dependent values directly
|
|
// from the form, rather than from the $page
|
|
$repeaterWrappers = array(); // 0, 1, or 2+ if nested repeaters
|
|
|
|
/** @var InputfieldWrapper $form */
|
|
if($inputfield) {
|
|
// locate the $form
|
|
$n = 0;
|
|
$form = $inputfield;
|
|
do {
|
|
if($form instanceof InputfieldWrapper && $form->hasClass('InputfieldRepeaterItem')) {
|
|
$repeaterWrappers[] = $form;
|
|
}
|
|
$f = $form->getParent();
|
|
if($f) $form = $f;
|
|
if(++$n > 10) break;
|
|
} while($f && !wireInstanceOf($f, 'InputfieldForm'));
|
|
} else {
|
|
$form = null;
|
|
}
|
|
|
|
preg_match_all('/=(page|item)\.([_.a-zA-Z0-9]+)/', $selector, $matches);
|
|
|
|
foreach($matches[0] as $key => $tag) {
|
|
$type = $matches[1][$key]; // page or item
|
|
$field = $matches[2][$key];
|
|
$subfield = '';
|
|
if(strpos($field, '.')) list($field, $subfield) = explode('.', $field);
|
|
$value = null;
|
|
if($form && (!$subfield || $subfield == 'id')) {
|
|
// attempt to get value from the form, to account for ajax changes that would not yet be reflected on the page
|
|
$in = $form instanceof InputfieldWrapper ? $form->getChildByName($field) : null;
|
|
if($type === 'item' && count($repeaterWrappers)) {
|
|
// fields in repeaters use a namespaced name attribute so match by hasField instead
|
|
foreach($repeaterWrappers as $repeaterWrapper) {
|
|
/** @var InputfieldWrapper $repeaterWrapper */
|
|
$value = null;
|
|
foreach($repeaterWrapper->getAll() as $in) {
|
|
/** @var Inputfield $in */
|
|
if("$in->hasField" !== $field) continue;
|
|
$value = $in->val();
|
|
break;
|
|
}
|
|
if($value !== null) break;
|
|
}
|
|
} else if($in) {
|
|
$value = $in->attr('value');
|
|
}
|
|
}
|
|
if(is_null($value)) {
|
|
if($type === 'page' && $page instanceof RepeaterPage) {
|
|
$value = $page->getForPageRoot()->get($field);
|
|
} else {
|
|
$value = $page->get($field);
|
|
}
|
|
}
|
|
if(is_object($value) && $subfield) $value = $value->$subfield;
|
|
if(is_array($value)) $value = implode('|', $value);
|
|
if(!strlen("$value") && (!$subfield || $subfield == 'id')) $value = '-1'; // force fail
|
|
$selector = str_replace($tag, "=$value", $selector);
|
|
}
|
|
}
|
|
|
|
return $selector;
|
|
}
|
|
|
|
/**
|
|
* Create a page finding selector from all configured properties
|
|
*
|
|
* @param array $options
|
|
* @return string|array
|
|
*
|
|
*/
|
|
public function createFindPagesSelector(array $options = array()) {
|
|
|
|
$defaults = array(
|
|
'page' => null, // optional $page for context (for selectors where it might matter)
|
|
'findRaw' => false, // include properties only recognized by $pages->findRaw()?
|
|
'getArray' => false, // return array rather than string?
|
|
);
|
|
|
|
$options = array_merge($defaults, $options);
|
|
|
|
$id = $this->getSetting('parent_id');
|
|
if($id) {
|
|
if(is_array($id)) $id = implode('|', $id);
|
|
$selector['parent_id'] = $id;
|
|
}
|
|
|
|
$ids = $this->getTemplateIDs();
|
|
if(count($ids)) {
|
|
$selector['templates_id'] = implode('|', $ids);
|
|
} else {
|
|
$id = $this->getSetting('template_id');
|
|
if(is_array($id)) $id = implode('|', $id);
|
|
if($id) $selector['templates_id'] = $id;
|
|
}
|
|
|
|
$findPagesCode = $this->getSetting('findPagesCode');
|
|
if(strlen($findPagesCode) && $options['page'] && empty($selector['parent_id'])) {
|
|
// via teppokoivula: use findPagesCode to return single parent page
|
|
$parent = $this->findPagesCode($options['page']);
|
|
if($parent instanceof Page) $selector['parent_id'] = $parent->id;
|
|
}
|
|
|
|
$s = $this->getSetting('findPagesSelector');
|
|
if(!strlen($s)) $s = $this->getSetting('findPagesSelect');
|
|
if(strlen($s)) {
|
|
if($options['page']) $s = self::populateFindPagesSelector($options['page'], $s, $this);
|
|
// @todo getstring vs getarray
|
|
$selector['selector'] = $s;
|
|
}
|
|
|
|
if($this->getSetting('allowUnpub')) {
|
|
$selector['include'] = 'unpublished';
|
|
} else {
|
|
$selector['include'] = 'hidden';
|
|
}
|
|
|
|
if($options['findRaw']) {
|
|
$labelFieldName = $this->getSetting('labelFieldName');
|
|
$labelFieldFormat = $this->getSetting('labelFieldFormat');
|
|
if(strlen($labelFieldFormat) && $labelFieldName === '.') {
|
|
// @todo find raw does not support labelFieldFormat
|
|
$selector['field'] = "$labelFieldFormat";
|
|
} else if($labelFieldName) {
|
|
$selector['field'] = $labelFieldName == '.' ? "name" : "$labelFieldName";
|
|
} else {
|
|
$selector['field'] = 'title';
|
|
}
|
|
}
|
|
|
|
if($options['getArray']) return $selector;
|
|
|
|
$a = array();
|
|
$operatorChars = Selectors::getOperatorChars();
|
|
foreach($selector as $key => $value) {
|
|
$v = substr($value, 0, 1);
|
|
$operator = strlen($v) && isset($operatorChars[$v]) ? '' : '='; // omit operator if already in $value
|
|
$a[] = "$key$operator$value";
|
|
}
|
|
|
|
return implode(', ', $a);
|
|
}
|
|
|
|
/**
|
|
* Get a label for the given page
|
|
*
|
|
* @param Page $page
|
|
* @param bool $allowMarkup Whether or not to allow markup in the label (default=false)
|
|
* @return string
|
|
*
|
|
*/
|
|
public function getPageLabel(Page $page, $allowMarkup = false) {
|
|
$label = '';
|
|
if(strlen($this->labelFieldFormat) && $this->labelFieldName == '.') {
|
|
$label = $page->getMarkup($this->labelFieldFormat);
|
|
} else if($this->labelFieldName === '.') {
|
|
// skip
|
|
} else if($this->labelFieldName) {
|
|
$label = $page->get($this->labelFieldName);
|
|
}
|
|
if(!strlen("$label")) $label = $page->name;
|
|
if($page->hasStatus(Page::statusUnpublished)) $label .= ' ' . $this->_('(unpublished)');
|
|
if(!$allowMarkup) $label = $this->wire()->sanitizer->markupToLine($label);
|
|
return $label;
|
|
}
|
|
|
|
/**
|
|
* Get the selected Inputfield class for input (adjuted version of $this->inputfield)
|
|
*
|
|
* @return string
|
|
*
|
|
*/
|
|
protected function getInputfieldClass() {
|
|
return ltrim($this->getSetting('inputfield'), '_');
|
|
}
|
|
|
|
/**
|
|
* Get delegate Inputfield for page selection
|
|
*
|
|
* @return Inputfield|null
|
|
* @throws WireException
|
|
*
|
|
*/
|
|
public function getInputfield() {
|
|
|
|
if($this->inputfieldWidget && ((string) $this->inputfieldWidget) == $this->getInputfieldClass()) {
|
|
return $this->inputfieldWidget;
|
|
}
|
|
|
|
/** @var Inputfield $inputfield */
|
|
$inputfield = $this->wire()->modules->get($this->getInputfieldClass());
|
|
if(!$inputfield) return null;
|
|
|
|
$inputfield->set('hasField', $this->hasField);
|
|
$inputfield->set('hasInputfield', $this);
|
|
|
|
$page = $this->page;
|
|
$input = $this->wire()->input;
|
|
$process = $this->wire()->process;
|
|
|
|
if($this->derefAsPage) $inputfield->set('maxSelectedItems', 1);
|
|
if($process instanceof WirePageEditor) $page = $process->getPage();
|
|
|
|
$inputfield->attr('name', $this->attr('name'));
|
|
$inputfield->attr('id', $this->attr('id'));
|
|
$keys = array('label', 'description', 'notes', 'detail');
|
|
foreach($keys as $key) {
|
|
$value = $this->getSetting($key);
|
|
if(strlen($value)) $inputfield->set($key, $value);
|
|
}
|
|
|
|
$collapsed = $this->getSetting('collapsed');
|
|
if($collapsed == Inputfield::collapsedYesAjax ||
|
|
($collapsed == Inputfield::collapsedBlankAjax && $this->isEmpty())) {
|
|
// quick exit when possible due to ajax field, and not being time to render or process it
|
|
if($this->getParent()) {
|
|
// limit only to inputfields that have a parent, to keep out of other form contexts like Lister
|
|
$renderInputfieldAjax = $input->get('renderInputfieldAjax');
|
|
$processInputfieldAjax = $input->post('processInputfieldAjax');
|
|
if(!is_array($processInputfieldAjax)) $processInputfieldAjax = array();
|
|
if($renderInputfieldAjax != $this->attr('id') && !in_array($this->attr('id'), $processInputfieldAjax)) {
|
|
$this->inputfieldWidget = $inputfield;
|
|
return $inputfield;
|
|
}
|
|
}
|
|
}
|
|
|
|
$value = $this->attr('value');
|
|
$valueArray = array();
|
|
if($value instanceof Page) {
|
|
$valueArray[$value->id] = $value;
|
|
} else if($value instanceof PageArray) {
|
|
foreach($value as $v) {
|
|
$valueArray[$v->id] = $v;
|
|
}
|
|
}
|
|
|
|
if($inputfield instanceof InputfieldSupportsPageSelector && $inputfield->setPageSelector('') !== false) {
|
|
// Inputfield has ability to find pages with a selector
|
|
|
|
$selector = $this->createFindPagesSelector(array('page' => $page));
|
|
$inputfield->setPageSelector($selector);
|
|
|
|
if($inputfield instanceof InputfieldHasSelectableOptions) {
|
|
foreach($valueArray as $p) {
|
|
$inputfield->addOption($p->id, $this->getPageLabel($p));
|
|
}
|
|
}
|
|
|
|
} else if(method_exists($inputfield, 'addOption') || $inputfield instanceof InputfieldHasSelectableOptions) {
|
|
// All selectable options types
|
|
if($this->hasPage && $this->hasPage->id != $page->id && wireInstanceOf($this->hasPage, 'RepeaterPage')) {
|
|
if($this->hasField && !$page->hasField($this->hasField) && $this->hasPage->hasField($this->hasField)) {
|
|
// replace page with RepeaterPage
|
|
$page = $this->hasPage;
|
|
$inputfield->set('hasPage', $page);
|
|
}
|
|
}
|
|
|
|
$children = $this->getSelectablePages($page);
|
|
|
|
if($children) {
|
|
foreach($children as $child) {
|
|
$label = $this->getPageLabel($child);
|
|
$inputfield->addOption($child->id, $label);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// InputfieldPageAutocomplete or similar (older style InputfieldSupportsPageSelector)
|
|
|
|
$parent_id = $this->getSetting('parent_id');
|
|
$template_id = $this->getSetting('template_id');
|
|
$template_ids = $this->getTemplateIDs();
|
|
$findPagesCode = $this->getSetting('findPagesCode');
|
|
$findPagesSelector = $this->getSetting('findPagesSelector');
|
|
$labelFieldName = $this->getSetting('labelFieldName');
|
|
$labelFieldFormat = $this->getSetting('labelFieldFormat');
|
|
|
|
if(empty($findPagesSelector)) $findPagesSelector = $this->getSetting('findPagesSelect');
|
|
|
|
if($parent_id) {
|
|
$inputfield->set('parent_id', $parent_id);
|
|
} else if($findPagesCode) {
|
|
// @teppokoivula: use findPagesCode to return single parent page
|
|
$child = $this->findPagesCode($page);
|
|
if($child instanceof Page) $inputfield->set('parent_id', $child->id);
|
|
}
|
|
|
|
if($template_id) $inputfield->set('template_id', $template_id);
|
|
if(!empty($template_ids)) $inputfield->set('template_ids', $template_ids);
|
|
|
|
if($findPagesSelector) {
|
|
$inputfield->set('findPagesSelector', self::populateFindPagesSelector($page, $findPagesSelector, $this));
|
|
}
|
|
|
|
if(strlen($labelFieldFormat) && $labelFieldName === '.') {
|
|
$inputfield->set('labelFieldName', $labelFieldFormat);
|
|
$inputfield->set('labelFieldFormat', $labelFieldFormat);
|
|
} else {
|
|
$inputfield->set('labelFieldName', $labelFieldName == '.' ? 'name' : $labelFieldName);
|
|
$inputfield->set('labelFieldFormat', '');
|
|
}
|
|
}
|
|
|
|
if($value instanceof Page) {
|
|
$inputfield->attr('value', $value->id); // derefAsPage
|
|
} else if($inputfield instanceof InputfieldPageListSelect) {
|
|
if($value instanceof PageArray) $value = $value->first();
|
|
$inputfield->attr('value', $value);
|
|
} else if($inputfield instanceof InputfieldHasArrayValue || $inputfield instanceof InputfieldSupportsArrayValue) {
|
|
$inputfield->attr('value', array_keys($valueArray));
|
|
} else {
|
|
$inputfield->attr('value', $value);
|
|
}
|
|
|
|
// pass along any relevant configuration items
|
|
foreach($this->data as $key => $value) {
|
|
if(in_array($key, array('value', 'collapsed')) || array_key_exists($key, self::$defaultConfig)) continue;
|
|
if($key == 'required' && empty($this->data['defaultValue'])) continue; // for default value support with InputfieldSelect
|
|
$inputfield->set($key, $value);
|
|
}
|
|
|
|
$inputfield->set('allowUnpub', $this->getSetting('allowUnpub'));
|
|
|
|
$this->inputfieldWidget = $inputfield;
|
|
|
|
return $inputfield;
|
|
}
|
|
|
|
/**
|
|
* Called before render()
|
|
*
|
|
* @param Inputfield $parent
|
|
* @param bool $renderValueMode
|
|
* @return bool
|
|
*
|
|
*/
|
|
public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
|
|
|
|
$this->renderValueMode = $renderValueMode;
|
|
parent::renderReady($parent, $renderValueMode);
|
|
$inputfield = $this->getInputfield();
|
|
|
|
if(!$inputfield) {
|
|
$this->error($this->_('This field needs to be configured before it can be used.'));
|
|
return false;
|
|
}
|
|
|
|
$this->addClass('InputfieldNoFocus', 'wrapClass');
|
|
|
|
return $inputfield->renderReady($this, $renderValueMode);
|
|
}
|
|
|
|
/**
|
|
* Render
|
|
*
|
|
* @return string
|
|
* @throws WireException
|
|
*
|
|
*/
|
|
public function ___render() {
|
|
|
|
$inputfield = $this->getInputfield();
|
|
if(!$inputfield) return $this->attr('name');
|
|
|
|
$classes = InputfieldWrapper::getClasses();
|
|
$class = $inputfield->className();
|
|
if(isset($classes[$class]['item_content'])) $class .= " " . $classes[$class]['item_content'];
|
|
|
|
foreach($this->classesAdded as $addClass) {
|
|
$inputfield->addClass($addClass);
|
|
}
|
|
|
|
$out = "<div class='$class'>";
|
|
$out .= $inputfield->render();
|
|
$out .= $this->renderAddable();
|
|
|
|
$findPagesSelector = $this->getSetting('findPagesSelector');
|
|
if(empty($findPagesSelector)) $findPagesSelector = $this->getSetting('findPagesSelect');
|
|
$labelFieldFormat = $this->getSetting('labelFieldFormat');
|
|
$labelFieldName = $this->getSetting('labelFieldName');
|
|
|
|
if($findPagesSelector) {
|
|
$selector = $this->wire()->sanitizer->entities($findPagesSelector);
|
|
$formatName = '';
|
|
if($this->wire()->user->hasPermission('page-edit') && strlen($labelFieldFormat) && $labelFieldName === '.') {
|
|
/** @var ProcessPageSearch $pps */
|
|
$formatName = "page_" . $this->attr('name');
|
|
try {
|
|
/** @var ProcessPageSearch $pps */
|
|
$pps = $this->wire()->modules->get('ProcessPageSearch');
|
|
$pps->setDisplayFormat($formatName, $labelFieldFormat);
|
|
} catch(\Exception $e) {
|
|
// most likely user does not have access to ProcessPageSearch
|
|
}
|
|
}
|
|
$labelFieldName = $labelFieldName == '.' ? 'name' : $labelFieldName;
|
|
$out .= "<input " .
|
|
"type='hidden' " .
|
|
"class='findPagesSelector' " .
|
|
"data-formatname='$formatName' " .
|
|
"data-label='$labelFieldName' " .
|
|
"value='$selector' />";
|
|
}
|
|
|
|
$out .= "</div>";
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Render the add page(s) section
|
|
*
|
|
* @return string
|
|
* @throws WireException
|
|
*
|
|
*/
|
|
protected function ___renderAddable() {
|
|
|
|
$pages = $this->wire()->pages;
|
|
|
|
$parent_id = $this->getSetting('parent_id');
|
|
$template_id = $this->getSetting('template_id');
|
|
$labelFieldName = $this->getSetting('labelFieldName');
|
|
|
|
if(!$this->getSetting('addable') || !$parent_id || !$template_id) return '';
|
|
|
|
if($labelFieldName && $labelFieldName != 'title') return '';
|
|
|
|
$parent = $pages->get($parent_id);
|
|
|
|
$test = $pages->newPage($template_id);
|
|
$test->parent = $parent;
|
|
$test->id = -1; // prevents permissions check from failing
|
|
|
|
if(!$parent->addable($test)) return '';
|
|
if(!$test->publishable()) return '';
|
|
|
|
$inputfield = $this->wire()->modules->get($this->getInputfieldClass());
|
|
if(!$inputfield) return '';
|
|
|
|
$key = "_{$this->name}_add_items";
|
|
|
|
if($inputfield instanceof InputfieldHasArrayValue || $inputfield instanceof InputfieldSupportsArrayValue) {
|
|
// multi value
|
|
$description = $this->_('Enter the titles of the items you want to add, one per line. They will be created and added to your selection when you save the page.');
|
|
$input = "<textarea id='$key' name='$key' rows='5'></textarea>";
|
|
} else {
|
|
// single value
|
|
$description = $this->_('Enter the title of the item you want to add. It will become selected when you save the page.');
|
|
$input = "<input type='text' name='$key' id='$key' />";
|
|
}
|
|
|
|
$notes = sprintf($this->_('New pages will be added to %s'), $parent->path);
|
|
$label = wireIconMarkup('plus-circle', 'fw') . $this->_('Create New');
|
|
|
|
$out =
|
|
"<div class='InputfieldPageAdd'>" .
|
|
"<p class='InputfieldPageAddButton'><a href='#'>$label</a></p>" .
|
|
"<p class='InputfieldPageAddItems'>" .
|
|
"<label class='description' for='$key'>$description</label>" .
|
|
"$input" .
|
|
"<span class='detail'>$notes</span>" .
|
|
"</p>" .
|
|
"</div>";
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Render non-editable value
|
|
*
|
|
* @return string
|
|
*
|
|
*/
|
|
public function ___renderValue() {
|
|
|
|
if($this->labelFieldName == '.') {
|
|
$labelFieldFormat = $this->labelFieldFormat;
|
|
$labelFieldName = 'title|name';
|
|
} else {
|
|
$labelFieldFormat = '';
|
|
$labelFieldName = $this->labelFieldName ? $this->labelFieldName : 'title';
|
|
$labelFieldName .= "|name";
|
|
}
|
|
|
|
$value = $this->attr('value');
|
|
|
|
if(is_array($value) || $value instanceof PageArray) {
|
|
$out = '<ul class="PageArray pw-bullets">';
|
|
foreach($value as $p) {
|
|
$of = $p->of();
|
|
$p->of(true);
|
|
$v = $labelFieldFormat ? $p->getText($labelFieldFormat, true, true) : $p->get($labelFieldName);
|
|
if(!strlen("$v")) $v = (string) $p->get('name');
|
|
$out .= "<li>$v</li>";
|
|
$p->of($of);
|
|
}
|
|
$out .= "</ul>";
|
|
|
|
} else if($value instanceof Page) {
|
|
$of = $value->of();
|
|
$value->of(true);
|
|
$out = $labelFieldFormat ? $value->getText($labelFieldFormat, true, true) : $value->get($labelFieldName);
|
|
if(!strlen("$out")) $out = (string) $value->get('name');
|
|
$value->of($of);
|
|
|
|
} else {
|
|
$out = $value;
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Process input
|
|
*
|
|
* @param WireInputData $input
|
|
* @return $this|Inputfield
|
|
* @throws WireException
|
|
*
|
|
*/
|
|
public function ___processInput(WireInputData $input) {
|
|
|
|
$process = $this->wire()->process;
|
|
$pages = $this->wire()->pages;
|
|
$user = $this->wire()->user;
|
|
|
|
$this->processInputMode = true;
|
|
$inputfield = $this->getInputfield();
|
|
if(!$inputfield) return $this;
|
|
$inputfield->processInput($input);
|
|
|
|
$value = $this->attr('value');
|
|
$existingValueStr = $value ? "$value" : '';
|
|
$newValue = null;
|
|
|
|
// the $editPage is used when InputfieldPage used without FieldtypePage
|
|
if($process instanceof WirePageEditor) {
|
|
$editPage = $process->getPage();
|
|
} else if($this->hasPage) {
|
|
$editPage = $this->hasPage;
|
|
} else {
|
|
$editPage = new Page();
|
|
}
|
|
|
|
if($inputfield instanceof InputfieldSupportsArrayValue) {
|
|
$value = $inputfield->getArrayValue();
|
|
} else {
|
|
$value = $inputfield->attr('value');
|
|
}
|
|
|
|
if(is_array($value)) {
|
|
$newValue = $pages->newPageArray();
|
|
foreach($value as $v) {
|
|
$id = (int) $v;
|
|
if(!$id) continue;
|
|
if($id > 0) {
|
|
// existing page
|
|
$page = $pages->get($id);
|
|
|
|
if(!$this->hasFieldtype && !self::isValidPage($page, $this, $editPage)) {
|
|
// extra validation for usage without FieldtypePage
|
|
$error = $editPage->get('_isValidPage');
|
|
if($error) $this->error($error);
|
|
continue;
|
|
|
|
} else if($page->hasStatus(Page::statusUnpublished) && !$this->getSetting('allowUnpub')) {
|
|
// disallow unpublished
|
|
$warning = sprintf($this->_('Unpublished page %1$s is not allowed in field "%2$s"'), "$page->id", $this->label);
|
|
if($user->isSuperuser()) {
|
|
$warning .= ' ' . sprintf($this->_('To allow unpublished pages, edit the “%s” field and see the setting on the “Details” tab.'), $this->name);
|
|
}
|
|
$this->warning($warning);
|
|
continue;
|
|
}
|
|
} else {
|
|
// placeholder for new page, to be sorted later
|
|
$page = $pages->newNullPage();
|
|
}
|
|
$newValue->add($page);
|
|
}
|
|
|
|
} else if($value) {
|
|
$newValue = $pages->get((int) $value);
|
|
if($newValue->hasStatus(Page::statusUnpublished) && !$this->getSetting('allowUnpub')) {
|
|
$newValue = null; // disallow unpublished
|
|
} else if($newValue && $newValue->id && !$this->hasFieldtype) {
|
|
if(!self::isValidPage($newValue, $this, $editPage)) {
|
|
$error = $editPage->get('_isValidPage');
|
|
if($error) $this->error($error);
|
|
$newValue = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if($this->derefAsPage < 1 && $newValue instanceof Page) {
|
|
// i.e. value from a PageListSelect (single) when using PageArray value
|
|
$newValuePage = $newValue;
|
|
$newValue = $pages->newPageArray();
|
|
$newValue->add($newValuePage);
|
|
}
|
|
|
|
$this->setAttribute('value', $newValue);
|
|
$this->processInputAddPages($input);
|
|
|
|
// if pages were added, re-sort them in case they were dragged to a different order
|
|
// an example of this would be when used with the InputfieldPageAutocomplete
|
|
if(count($this->pagesAdded) && is_array($value)) {
|
|
$sortedValue = $pages->newPageArray();
|
|
foreach($newValue as $page) {
|
|
if($page->id < 1) $page = $this->pagesAdded->shift();
|
|
if($page->id && !$sortedValue->has($page)) $sortedValue->add($page);
|
|
}
|
|
$newValue = $sortedValue;
|
|
$this->setAttribute('value', $newValue);
|
|
}
|
|
|
|
if("$newValue" != "$existingValueStr") {
|
|
$this->trackChange('value');
|
|
}
|
|
|
|
$this->processInputMode = false;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Check for the addable pages option and process if applicable
|
|
*
|
|
* @param WireInputData $input
|
|
*
|
|
*/
|
|
protected function ___processInputAddPages($input) {
|
|
|
|
$pages = $this->wire()->pages;
|
|
$sanitizer = $this->wire()->sanitizer;
|
|
|
|
$this->pagesAdded = $pages->newPageArray();
|
|
$parent_id = $this->getSetting('parent_id');
|
|
$template_id = $this->getSetting('template_id');
|
|
$template = $this->wire()->templates->get((int) $template_id);
|
|
|
|
if(!$this->getSetting('addable') || !$parent_id || !$template_id) return;
|
|
|
|
$user = $this->wire()->user;
|
|
$key = "_{$this->name}_add_items";
|
|
$value = trim((string) $input->$key);
|
|
|
|
if(empty($value)) return;
|
|
|
|
$parent = $pages->get($parent_id);
|
|
$sort = $parent->numChildren;
|
|
$titles = explode("\n", $value);
|
|
$n = 0;
|
|
|
|
foreach($titles as $title) {
|
|
|
|
// check if there is an existing page using this title
|
|
$selector = "include=all, templates_id=$template_id, title=" . $sanitizer->selectorValue($title);
|
|
$existingPage = $parent->child($selector);
|
|
|
|
if($existingPage->id) {
|
|
// use existing page
|
|
$this->pagesAdded->add($existingPage);
|
|
if($this->value instanceof PageArray) {
|
|
$this->value->add($existingPage);
|
|
continue;
|
|
} else {
|
|
$this->value = $existingPage;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// create a new page
|
|
$page = $pages->newPage(array(
|
|
'template' => $template,
|
|
'parent' => $parent,
|
|
'title' => trim($title),
|
|
'sort' => $sort++,
|
|
'id' => -1, // prevents the permissions check from failing
|
|
));
|
|
|
|
// on first iteration perform a page-context access check
|
|
if(!$n && (!$parent->addable($page) || !$page->publishable())) {
|
|
$this->error("No access to add {$page->template} pages to {$parent->path}");
|
|
break;
|
|
}
|
|
$page->id = 0;
|
|
|
|
try {
|
|
$page->save();
|
|
$this->message(sprintf($this->_('Added page %s'), $page->path));
|
|
if($this->value instanceof PageArray) $this->value->add($page);
|
|
else $this->value = $page;
|
|
$this->pagesAdded->add($page);
|
|
$this->trackChange('value');
|
|
$n++;
|
|
|
|
} catch(\Exception $e) {
|
|
$error = sprintf($this->_('Error adding page "%s"'), $page->title);
|
|
if($user->isSuperuser()) $error .= " - " . $e->getMessage();
|
|
$this->error($error);
|
|
break;
|
|
}
|
|
|
|
if($this->value instanceof Page) break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Does this Inputfield have an empty value?
|
|
*
|
|
* @return bool
|
|
*
|
|
*/
|
|
public function isEmpty() {
|
|
$value = $this->attr('value');
|
|
|
|
if($value instanceof Page) {
|
|
// derefAsPage
|
|
return $value->id < 1;
|
|
|
|
} else if($value instanceof PageArray) {
|
|
// derefAsPageArray
|
|
if(!count($value)) return true;
|
|
|
|
} else {
|
|
// null
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get field configuration Inputfields
|
|
*
|
|
* @return InputfieldWrapper
|
|
* @throws WireException
|
|
* @todo move to separate config.php file
|
|
*
|
|
*/
|
|
public function ___getConfigInputfields() {
|
|
$modules = $this->wire()->modules;
|
|
|
|
// let the module know it's being used for configuration purposes
|
|
$this->configMode = true;
|
|
$exampleLabel = $this->_('Example:') . ' ';
|
|
$defaultLabel = ' ' . $this->_('(default)');
|
|
|
|
$inputfields = new InputfieldWrapper();
|
|
$this->wire($inputfields);
|
|
|
|
/** @var InputfieldFieldset $fieldset */
|
|
$fieldset = $modules->get('InputfieldFieldset');
|
|
$fieldset->label = $this->_('Selectable pages');
|
|
$fieldset->attr('name', '_selectable_pages');
|
|
$fieldset->description = $this->_('Use at least one of the options below to determine which pages will be selectable with this field.');
|
|
$fieldset->icon = 'files-o';
|
|
$selectablePagesFieldset = $fieldset;
|
|
|
|
/** @var InputfieldPageListSelect $field */
|
|
$field = $modules->get('InputfieldPageListSelect');
|
|
$field->setAttribute('name', 'parent_id');
|
|
$field->label = $this->_('Parent');
|
|
$field->attr('value', (int) $this->parent_id);
|
|
$field->description = $this->_('Select the parent of the pages that are selectable.');
|
|
$field->required = false;
|
|
$field->icon = 'folder-open-o';
|
|
$field->collapsed = Inputfield::collapsedBlank;
|
|
$fieldset->append($field);
|
|
|
|
/** @var InputfieldSelect $field */
|
|
$field = $modules->get('InputfieldSelect');
|
|
$field->setAttribute('name', 'template_id');
|
|
$field->label = $this->_('Template');
|
|
$field->description = $this->_('Select the template of the pages that are selectable. May be used instead of, or in addition to, the parent above.'); // Description for Template of selectable pages
|
|
foreach($this->templates as $template) {
|
|
$field->addOption($template->id, $template->name);
|
|
}
|
|
$template_id = $this->getSetting('template_id');
|
|
$field->attr('value', $template_id);
|
|
$field->collapsed = Inputfield::collapsedBlank;
|
|
$field->icon = 'cube';
|
|
$fieldset->append($field);
|
|
|
|
$templateIDs = $this->getTemplateIDs();
|
|
$key = array_search($template_id, $templateIDs);
|
|
if(is_int($key)) unset($templateIDs[$key]);
|
|
|
|
/** @var InputfieldAsmSelect $field */
|
|
$field = $modules->get('InputfieldAsmSelect');
|
|
$field->attr('name', 'template_ids');
|
|
$field->label = $this->_('Additional templates');
|
|
$field->description = $this->_('If you need additional templates for selectable pages, select them here.');
|
|
// $field->description .= ' ' . $this->_('This may not be supported by all input types.');
|
|
$field->icon = 'cubes';
|
|
foreach($this->templates as $template) {
|
|
$field->addOption($template->id, $template->name);
|
|
}
|
|
$field->attr('value', $templateIDs);
|
|
$field->collapsed = Inputfield::collapsedBlank;
|
|
$field->showIf = 'template_id!=0';
|
|
if(count($templateIDs) == 1 && reset($templateIDs) == $this->getSetting('template_id')) {
|
|
$field->collapsed = Inputfield::collapsedYes;
|
|
}
|
|
$fieldset->append($field);
|
|
|
|
$extra = $this->_('While this overrides parent and template selections above, those selections (if present) are still used for validation.'); // Additional notes
|
|
|
|
/** @var InputfieldSelector $field */
|
|
$field = $modules->get('InputfieldSelector');
|
|
$field->description = $this->_('Add one or more fields below to create a query that finds the pages you want to make selectable.');
|
|
$field->description .= ' ' . $this->_('This creates a selector that finds pages at runtime. If you prefer to enter this manually, use the “Selector string” option below instead.');
|
|
$field->description .= ' ' . $extra;
|
|
$field->attr('name', 'findPagesSelect');
|
|
$field->label = $this->_('Custom find');
|
|
$field->attr('value', $this->get('findPagesSelect'));
|
|
//$field->collapsed = Inputfield::collapsedBlank;
|
|
$field->icon = 'search-plus';
|
|
$field->addLabel = $this->_('Add field to query');
|
|
$field->allowSystemCustomFields = true;
|
|
$field->allowSystemTemplates = true;
|
|
$field->showFieldLabels = 1;
|
|
$field->collapsed = Inputfield::collapsedBlank;
|
|
$fieldset->append($field);
|
|
|
|
|
|
/** @var InputfieldText $field */
|
|
$field = $modules->get('InputfieldText');
|
|
$field->attr('name', 'findPagesSelector');
|
|
$field->label = $this->_('Selector string');
|
|
$field->attr('value', $this->findPagesSelector);
|
|
$field->description = $this->_('If you want to find selectable pages using a ProcessWire selector, enter the selector string to find the selectable pages. This selector will be passed to a `$pages->find("your selector");` call.'); // Description for Custom selector to find selectable pages
|
|
$field->description .= ' ' . $extra;
|
|
$field->notes = $exampleLabel . $this->_('parent=/products/, template=product, sort=name'); // Example of Custom selector to find selectable pages
|
|
$field->collapsed = Inputfield::collapsedBlank;
|
|
$field->icon = 'search';
|
|
$fieldset->append($field);
|
|
|
|
if($this->findPagesCode) {
|
|
// allow only if already present, as this option is deprecated
|
|
/** @var InputfieldTextarea $field */
|
|
$field = $modules->get('InputfieldTextarea');
|
|
$field->attr('name', 'findPagesCode');
|
|
$field->attr('value', $this->findPagesCode);
|
|
$field->attr('rows', 4);
|
|
$field->description = $this->_('If you want to find selectable pages using a PHP code snippet rather than selecting a parent page or template (above) then enter the code to find the selectable pages. This statement has access to the $page and $pages API variables, where $page refers to the page being edited.'); // Description for Custom PHP to find selectable pages 1
|
|
$field->description .= ' ' . $this->_('The snippet should return either a PageArray, Page or NULL. If it returns a Page, children of that Page are used as selectable pages. Using this is optional, and if used, it overrides the parent/template/selector fields above.'); // Description for Custom PHP to find selectable pages 2
|
|
$field->description .= ' ' . $extra;
|
|
$field->description .= ' ' . $this->_('NOTE: Not compatible with PageListSelect or Autocomplete input field types.'); // Description for Custom PHP to find selectable pages 3
|
|
$field->notes = $exampleLabel . $this->_('return $page->parent->parent->children("name=locations")->first()->children();'); // Example of Custom PHP code to find selectable pages
|
|
$field->collapsed = Inputfield::collapsedBlank;
|
|
} else {
|
|
/** @var InputfieldMarkup $field */
|
|
$field = $modules->get('InputfieldMarkup');
|
|
$field->attr('name', '_findPagesCode');
|
|
$field->collapsed = Inputfield::collapsedYes;
|
|
$if = "\$event->object->" .
|
|
($this->name ? "hasField == '<strong>$this->name</strong>'" : "name == '<strong>your_field_name</strong>'");
|
|
$field->value = '<p>' .
|
|
sprintf($this->_('Add the following hook to a %s file and modify per your needs. The hook should find and return selectable pages in a PageArray.'), '<u>/site/ready.php</u>') .
|
|
"</p><pre><code>" .
|
|
"\$wire->addHookAfter('InputfieldPage::getSelectablePages', function(\$event) {" .
|
|
"\n if($if) {" .
|
|
"\n \$event->return = \$event->pages->find('<strong>your selector here</strong>');" .
|
|
"\n }" .
|
|
"\n});" .
|
|
"</code></pre>" .
|
|
"<p>" .
|
|
sprintf($this->_('If you need to know the page being edited, it is accessible from: %s'),
|
|
"<code>\$event->arguments('page');</code>") .
|
|
"</p>";
|
|
}
|
|
$field->label = $this->_('Custom PHP code');
|
|
$field->icon = 'code';
|
|
$field->showIf = 'inputfield!=InputfieldPageAutocomplete|InputfieldPageListSelect|InputfieldPageListSelectMultiple';
|
|
$fieldset->append($field);
|
|
|
|
$inputfields->append($fieldset);
|
|
|
|
/** @var InputfieldSelect $field */
|
|
$field = $modules->get('InputfieldSelect');
|
|
$field->attr('name', 'labelFieldName');
|
|
$field->label = $this->_('Label field');
|
|
$field->required = true;
|
|
$field->icon = 'thumb-tack';
|
|
$field->description = $this->_('Select the page field that you want to be used in generating the labels for each selectable page.'); // Description for Label Field
|
|
$field->notes = $this->_('Select "Custom format" if you want to specify multiple fields, or other fields you do not see above.');
|
|
$field->addOption('.', $this->_('Custom format (multiple fields)' . ' ...'));
|
|
$field->columnWidth = 50;
|
|
|
|
if($this->wire()->fields->get('title')) {
|
|
$field->addOption('title', 'title' . $defaultLabel);
|
|
$field->addOption('name', 'name');
|
|
$titleIsDefault = true;
|
|
} else {
|
|
$field->addOption('name', 'name' . $defaultLabel);
|
|
$titleIsDefault = false;
|
|
}
|
|
$field->addOption('path', 'path');
|
|
|
|
foreach($this->wire()->fields as $f) {
|
|
if(!$f->type instanceof FieldtypeText) continue;
|
|
if($f->type instanceof FieldtypeTextarea) continue;
|
|
if($titleIsDefault && $f->name == 'title') continue;
|
|
$field->addOption($f->name);
|
|
}
|
|
if(!$this->labelFieldFormat) {
|
|
if($this->labelFieldName === '.') {
|
|
// they want a custom format, but they didn't provide one
|
|
$this->labelFieldName = $titleIsDefault ? 'title' : 'name';
|
|
}
|
|
}
|
|
if(!$this->labelFieldName) {
|
|
// no label field name means we fall back to default
|
|
$this->labelFieldName = $titleIsDefault ? 'title' : 'name';
|
|
}
|
|
$field->attr('value', $this->labelFieldName);
|
|
$inputfields->append($field);
|
|
|
|
/** @var InputfieldText $field */
|
|
$field = $modules->get('InputfieldText');
|
|
$field->attr('name', 'labelFieldFormat');
|
|
$field->attr('value', $this->labelFieldFormat);
|
|
$field->label = $this->_('Custom page label format');
|
|
$field->description = $this->_('Specify one or more field names surrounded by curly {brackets} along with any additional characters, spacing or punctuation.'); // Description for custom page label format
|
|
$field->notes = $this->_('Example: {parent.title} - {title}, {date}');
|
|
$field->columnWidth = 50;
|
|
$field->showIf = 'labelFieldName=.';
|
|
$field->required = true;
|
|
$field->requiredIf = 'labelFieldName=.';
|
|
$inputfields->add($field);
|
|
|
|
if(!$this->inputfield) $this->inputfield = 'InputfieldSelect';
|
|
|
|
/** @var InputfieldSelect $field */
|
|
$field = $modules->get('InputfieldSelect');
|
|
$field->setAttribute('name', 'inputfield');
|
|
$field->setAttribute('value', $this->inputfield);
|
|
$field->label = $this->_('Input field type');
|
|
$field->description = $this->_('The type of input field (Inputfield module) that will be used to select page(s) for this field.');
|
|
if($this->hasFieldtype !== false) {
|
|
$field->description .= ' ' . $this->_('Select one that is consistent with your “Value type” selection on the “Details” tab for single or multiple-page selection.');
|
|
}
|
|
$field->notes = $this->_('After selecting an input field type and saving changes, please note that additional configuration options specific to your selection may appear directly below this.');
|
|
$field->required = true;
|
|
$field->icon = 'plug';
|
|
$inputfieldSelection = $field;
|
|
|
|
$options = $this->getInputfieldOptions();
|
|
$pageListTypes = $options['pageListTypes'];
|
|
|
|
$multiLabel = $this->_('Multiple page selection');
|
|
$field->addOption($this->_('Single page selection'), $options['singles']);
|
|
$field->addOption($multiLabel, $options['multiples']);
|
|
$field->addOption($multiLabel . ' (' . $this->_('sortable') . ')', $options['sortables']);
|
|
$inputfields->insertBefore($field, $selectablePagesFieldset);
|
|
|
|
if($this->hasFieldtype === false) {
|
|
/** @var InputfieldRadios $field */
|
|
/*
|
|
$field = $modules->get('InputfieldRadios');
|
|
$field->attr('name', 'derefAsPage');
|
|
$field->label = $this->_('Value type');
|
|
$field->addOption(FieldtypePage::derefAsPageArray,
|
|
$this->_('PageArray') . ' ' .
|
|
'[span.detail] ' . $this->_('(works for all cases but required for multiple selection)') . ' [/span]'
|
|
);
|
|
$field->addOption(FieldtypePage::derefAsPageOrNullPage,
|
|
$this->_('Page') . ' ' .
|
|
'[span.detail] ' . $this->_('(optional for single page selection)') . ' [/span]'
|
|
);
|
|
$field->attr('value', (int) $this->derefAsPage);
|
|
$inputfields->add($field);
|
|
*/
|
|
|
|
} else {
|
|
|
|
/** @var InputfieldMarkup $f */
|
|
$f = $modules->get('InputfieldMarkup');
|
|
$f->label = $this->_('Regarding “Page List” input types');
|
|
$f->icon = 'warning';
|
|
$f->showIf = 'inputfield=' . implode('|', $pageListTypes);
|
|
$f->value = '<p>' .
|
|
$this->_('You have selected an input type that has specific requirements.') . ' ' .
|
|
$this->_('Specify only the “Parent” option below when configuring “Selectable pages”.') . ' ' .
|
|
$this->_('Note that the parent you specify implies the root of the tree of selectable pages.') . ' ' .
|
|
$this->_('If you want to make everything selectable, then specify nothing.') .
|
|
'</p>';
|
|
$inputfields->insertAfter($f, $field);
|
|
|
|
/** @var InputfieldCheckbox $field */
|
|
$field = $modules->get('InputfieldCheckbox');
|
|
$field->attr('name', 'addable');
|
|
$field->attr('value', 1);
|
|
$field->icon = 'lightbulb-o';
|
|
$field->label = $this->_('Allow new pages to be created from field?');
|
|
$field->description = $this->_('If checked, an option to add new page(s) will also be present if the indicated requirements are met.');
|
|
$field->notes =
|
|
$this->_('1. Both a parent and template must be specified in the “Selectable pages” section above.') . "\n" .
|
|
$this->_('2. The editing user must have access to create/publish these pages.') . "\n" .
|
|
$this->_('3. The “label field” must be set to “title (default)”.');
|
|
|
|
if($this->addable) {
|
|
$field->attr('checked', 'checked');
|
|
} else {
|
|
$field->collapsed = Inputfield::collapsedYes;
|
|
}
|
|
$inputfields->append($field);
|
|
}
|
|
|
|
foreach(parent::___getConfigInputfields() as $inputfield) {
|
|
$inputfields->add($inputfield);
|
|
}
|
|
|
|
$inputfield = $this->getInputfield();
|
|
if($inputfield) {
|
|
// tell it it's under control of a parent, regardless of whether this one is hasFieldtype true or not.
|
|
$info = $modules->getModuleInfo($inputfield);
|
|
$inputfield->hasFieldtype = $this->hasFieldtype ? $this->hasFieldtype : true;
|
|
$inputfield->hasInputfield = $this;
|
|
if($inputfield instanceof InputfieldSupportsPageSelector) {
|
|
$exampleSelector = $this->createFindPagesSelector();
|
|
$inputfield->setPageSelector($exampleSelector);
|
|
}
|
|
/** @var InputfieldFieldset $fieldset */
|
|
$fieldset = $modules->get('InputfieldFieldset');
|
|
$n = 0;
|
|
foreach($inputfield->___getConfigInputfields() as $f) {
|
|
if(in_array($f->name, array('required', 'requiredIf', 'showIf', 'collapsed', 'columnWidth'))) continue;
|
|
if(array_key_exists($f->name, self::$defaultConfig)) continue;
|
|
// if we already have the given field, skip over it to avoid duplication
|
|
if($f->name && $inputfields->getChildByName($f->name)) continue;
|
|
$fieldset->add($f);
|
|
$n++;
|
|
}
|
|
if($n) {
|
|
$fieldset->label = sprintf($this->_('Settings specific to “%s”'), $info['title']);
|
|
$fieldset->icon = 'gear';
|
|
$fieldset->collapsed = Inputfield::collapsedYes;
|
|
$inClass = $inputfield->className();
|
|
$fieldset->showIf = 'inputfield=' . $inClass;
|
|
if($inClass == 'InputfieldPageAutocomplete') $fieldset->showIf .= "|_$inClass";
|
|
$inputfields->insertAfter($fieldset, $inputfieldSelection);
|
|
}
|
|
}
|
|
|
|
$this->configMode = false; // reverse what was set at the top of this function
|
|
|
|
return $inputfields;
|
|
}
|
|
|
|
/**
|
|
* Get options available for page selection Inputfields
|
|
*
|
|
* @return array
|
|
* @since 3.0.213
|
|
*
|
|
*/
|
|
public function getInputfieldOptions() {
|
|
$modules = $this->wire()->modules;
|
|
|
|
$singles = array();
|
|
$multiples = array();
|
|
$sortables = array();
|
|
$pageListTypes = array();
|
|
|
|
$inputfieldClasses = $this->inputfieldClasses;
|
|
if($this->hasFieldtype) $inputfieldClasses = array_merge(self::$defaultInputfieldClasses, $inputfieldClasses);
|
|
|
|
foreach($inputfieldClasses as $class) {
|
|
$module = $modules->getModule($class, array('noInit' => true));
|
|
$info = $modules->getModuleInfo($module);
|
|
$label = ucfirst((string) $info['title']);
|
|
if($module instanceof InputfieldPageListSelection) {
|
|
$pageListTypes[] = $class;
|
|
}
|
|
if($module instanceof InputfieldHasSortableValue) {
|
|
$sortables[$class] = $label;
|
|
} else if($module instanceof InputfieldHasArrayValue || $module instanceof InputfieldSupportsArrayValue) {
|
|
$multiples[$class] = $label;
|
|
} else {
|
|
$singles[$class] = $label;
|
|
}
|
|
if($class == 'InputfieldPageAutocomplete') $singles["_$class"] = $label;
|
|
}
|
|
|
|
return array(
|
|
'singles' => $singles,
|
|
'multiples' => $multiples,
|
|
'sortables' => $sortables,
|
|
'pageListTypes' => $pageListTypes,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get recommended setups for FieldtypePage/InputfieldPage
|
|
*
|
|
* @return array
|
|
* @since 3.0.213
|
|
*
|
|
*/
|
|
public function getFieldSetups() {
|
|
$setups = array();
|
|
$options = $this->getInputfieldOptions();
|
|
$singleLabel = $this->_('Single page:');
|
|
$multiLabel = $this->_('Multiple pages:');
|
|
$sortLabel = $this->_('Multiple sortable pages:');
|
|
|
|
foreach($options['singles'] as $class => $label) {
|
|
$name = str_replace('Inputfield', '', $class);
|
|
$setups[$name] = array(
|
|
'title' => "$singleLabel $label",
|
|
'derefAsPage' => FieldtypePage::derefAsPageOrNullPage,
|
|
'inputfield' => $class,
|
|
);
|
|
}
|
|
|
|
foreach(array_merge($options['multiples'], $options['sortables']) as $class => $label) {
|
|
$name = str_replace('Inputfield', '', $class);
|
|
$label = isset($options['sortables'][$class]) ? "$sortLabel $label" : "$multiLabel $label";
|
|
$setups[$name] = array(
|
|
'title' => $label,
|
|
'derefAsPage' => FieldtypePage::derefAsPageArray,
|
|
'inputfield' => $class,
|
|
);
|
|
}
|
|
|
|
return $setups;
|
|
}
|
|
|
|
/**
|
|
* Get module configuration Inputfields
|
|
*
|
|
* @param array $data
|
|
* @return InputfieldWrapper
|
|
*
|
|
*/
|
|
public function getModuleConfigInputfields(array $data) {
|
|
|
|
$name = 'inputfieldClasses';
|
|
|
|
if(!isset($data[$name]) || !is_array($data[$name])) $data[$name] = self::$defaultInputfieldClasses;
|
|
$fields = $this->wire(new InputfieldWrapper());
|
|
$this->wire($fields);
|
|
$modules = $this->wire()->modules;
|
|
/** @var InputfieldAsmSelect $field */
|
|
$field = $modules->get("InputfieldAsmSelect");
|
|
$field->attr('name', $name);
|
|
foreach($modules->findByPrefix('Inputfield') as $className) {
|
|
$field->addOption($className, str_replace('Inputfield', '', $className));
|
|
}
|
|
$field->attr('value', $data[$name]);
|
|
$field->label = $this->_('Inputfield modules available for page selection');
|
|
$field->description = $this->_('Select the Inputfield modules that may be used for page selection. These should generally be Inputfields that allow you to select one or more options.'); // Description
|
|
$fields->append($field);
|
|
|
|
return $fields;
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|