Rev 22 | Blame | Compare with Previous | Last modification | View Log | Download
<?php namespace ProcessWire;/*** ProcessWire Module Fieldtype** Field that stores reference to another Module.** For documentation about the fields used in this class, please see:* /wire/core/Fieldtype.php** ProcessWire 3.x, Copyright 2022 by Ryan Cramer* https://processwire.com**/class FieldtypeModule extends Fieldtype {/*** Get module info** @return array**/public static function getModuleInfo() {return array('title' => 'Module Reference','version' => 102,'summary' => 'Field that stores a reference to another module','permanent' => true,);}/*** Get blank value** @param Page $page* @param Field $field* @return bool|int|null|ModulePlaceholder* @throws WireException**/public function getBlankValue(Page $page, Field $field) {$blankType = $field->get('blankType');if($blankType === 'zero') {$value = 0;} else if($blankType === 'false') {$value = false;} else if($blankType === 'placeholder') {$value = $this->wire(new ModulePlaceholder());} else {$value = null;}return $value;}/*** Should this Fieldtype only be allowed for new fields in advanced mode?** @return bool**/public function isAdvanced() {return true;}/*** Sanitize value** @param Page $page* @param Field $field* @param int|object|WireArray|string $value* @return int|bool|null|Module**/public function sanitizeValue(Page $page, Field $field, $value) {if(!$value) return $this->getBlankValue($page, $field);$modules = $this->wire()->modules;if($field->get('instantiateModule')) return $value instanceof Module ? $value : $modules->get($value);if(ctype_digit("$value")) return $modules->getModuleClass((int) $value);return $modules->getModuleID($value) ? $value : $this->getBlankValue($page, $field);}/*** Wakeup value** @param Page $page* @param Field $field* @param array|int|string $value* @return string**/public function ___wakeupValue(Page $page, Field $field, $value) {if(empty($value)) return $this->getBlankValue($page, $field);$modules = $this->wire()->modules;if($field->get('instantiateModule')) return $modules->get($value);return $modules->getModuleClass((int) $value);}/*** Sleep value** @param Page $page* @param Field $field* @param array|float|int|object|string $value* @return int**/public function ___sleepValue(Page $page, Field $field, $value) {$blankValue = $this->getBlankValue($page, $field);if(!$value || "$blankValue" == "$value") {return 0;} else {return $this->wire()->modules->getModuleID($value);}}/*** Get all selectable modules for given field** @param Field $field* @return array Returns array of associative arrays with class, title, label, summary.**/public function getSelectableModules(Field $field) {$modules = $this->wire()->modules;$options = array();$moduleTypes = $field->get('moduleTypes');$inputfieldClass = $field->get('inputfieldClass');$labelField = $field->get('labelField');$matchType = $field->get('matchType'); // verbose or prefixif($matchType !== 'prefix' || empty($moduleTypes)) {$checkModules = $modules;} else {$checkModules = array();foreach($moduleTypes as $prefix) {foreach($modules->findByPrefix($prefix, false) as $moduleName) {$checkModules[] = $moduleName;}}}foreach($checkModules as $module) {if($matchType != 'prefix' && !empty($moduleTypes)) {$ns = $modules->getModuleNamespace($module);$parents = wireClassParents($ns ? "$ns$module" : "$module");$found = false;foreach($moduleTypes as $moduleType) {if(!in_array($moduleType, $parents)) continue;$found = true;break;}if(!$found) continue;}$class = (string) $module;$summary = '';$title = '';if($labelField === 'title') {$info = $modules->getModuleInfo($module);$id = $info['id'];$label = !empty($info['title']) ? $info['title'] : (string) $module;$title = $label;$keyType = 'title';} else if($labelField === 'title-summary') {$info = $modules->getModuleInfoVerbose($module);$id = $info['id'];$title = $info['title'];$label = !empty($title) ? $title : (string) $module;$keyType = 'title';if(!empty($info['summary'])) {$summary = $info['summary'];if($inputfieldClass === 'InputfieldRadios') {$label .= " [span.detail] • $summary [/span]";} else {$label .= " • $summary";}}} else {$info = $modules->getModuleInfo($module);$id = $info['id'];$keyType = 'class';$label = $class;}$option = array('id' => $id,'class' => $class,'label' => $label,'title' => $title,'summary' => $summary,);$key = $option[$keyType];while(isset($options[$key])) $key .= ' '; // just in case$options[$key] = $option;}ksort($options);return array_values($options);}/*** Get Inputfield** @param Page $page* @param Field $field* @return Inputfield**/public function getInputfield(Page $page, Field $field) {$modules = $this->wire()->modules;$inputfieldClass = $field->get('inputfieldClass');$inputfieldClass = $inputfieldClass ? $inputfieldClass : 'InputfieldSelect';/** @var InputfieldSelect $inputfield */$inputfield = $modules->get($inputfieldClass);if(!$inputfield) $inputfield = $modules->get('InputfieldSelect');$inputfield->attr('name', $field->name);$inputfield->class = $this->className();if($inputfieldClass == 'InputfieldRadios' && $field->get('showNoneOption')) {$inputfield->addOption(0, $this->_('None'));}foreach($this->getSelectableModules($field) as $info) {$inputfield->addOption($info['class'], $info['label']);}return $inputfield;}/*** Get database schema** @param Field $field* @return array**/public function getDatabaseSchema(Field $field) {$schema = parent::getDatabaseSchema($field);$schema['data'] = 'int NOT NULL';return $schema;}/*** Configure field** @param Field $field* @return InputfieldWrapper**/public function ___getConfigInputfields(Field $field) {$modules = $this->wire()->modules;$inputfields = parent::___getConfigInputfields($field);$moduleTypes = array();foreach($modules as $module) {if(strpos($module->className(), 'AdminTheme') === 0) {$matches = array('', 'AdminTheme');} else {if(!preg_match('/^([A-Za-z][a-z0-9_]+)/', $module->className(), $matches)) continue;}$moduleType = $matches[1];$moduleTypes[$moduleType] = $moduleType;}ksort($moduleTypes);/** @var InputfieldCheckboxes $f */$f = $modules->get("InputfieldCheckboxes");$f->attr('name', 'moduleTypes');$f->addOptions($moduleTypes);$value = $field->get('moduleTypes');if(!is_array($value)) $value = array();$f->attr('value', $value);$f->label = $this->_('Module Types');$f->description = $this->_('Check all of the module types that may be selectable in this field.');$f->optionWidth = 250;$inputfields->append($f);/** @var InputfieldRadios $f */$f = $modules->get('InputfieldRadios');$f->attr('name', 'matchType');$f->label = $this->_('Module matching type');$f->addOption('prefix', $this->_('Prefix'));$f->addOption('verbose', $this->_('Inheritance'));$f->notes = $this->_('The prefix option is recommended for better performance unless it fails to match a module you intend.');$value = $field->get('matchType');$f->val($value ? $value : 'verbose');$inputfields->add($f);/** @var InputfieldCheckbox $f */$f = $modules->get("InputfieldCheckbox");$f->attr('name', 'instantiateModule');$f->label = $this->_('Make this field an instance of the selected module?');$f->description = $this->_('If checked, the field value will be an actual instance of the selected module. If not checked, the field value will be a string containing the class name of the module.'); // instantiate module descriptionif($field->get('instantiateModule')) $f->attr('checked', 'checked');$inputfields->add($f);/** @var InputfieldRadios $f */$f = $modules->get('InputfieldRadios');$f->label = $this->_('Options Label');$f->attr('name', 'labelField');$f->addOption('', $this->_('Name'));$f->addOption('title', $this->_('Title'));$f->addOption('title-summary', $this->_('Title and summary'));$f->attr('value', $field->get('labelField'));$f->columnWidth = 50;$inputfields->add($f);/** @var InputfieldRadios $f */$f = $modules->get('InputfieldRadios');$f->label = $this->_('Input Type');$f->attr('name', 'inputfieldClass');$f->addOption('', $this->_('Select'));$f->addOption('InputfieldRadios', $this->_('Radios'));$f->columnWidth = 50;$f->attr('value', $field->get('inputfieldClass'));$inputfields->add($f);/** @var InputfieldCheckbox $f */$f = $modules->get('InputfieldCheckbox');$f->label = $this->_('Show a “None” option?');$f->attr('name', 'showNoneOption');if($field->get('showNoneOption')) $f->attr('checked', 'checked');$f->showIf = 'inputfieldClass=InputfieldRadios';$inputfields->add($f);/** @var InputfieldRadios $f */$f = $modules->get('InputfieldRadios');$f->attr('name', 'blankType');$f->label = $this->_('Blank value type');$f->addOption('null', 'Null');$f->addOption('zero', 'Integer 0');$f->addOption('false', 'Boolean false');$f->addOption('placeholder', 'ModulePlaceholder instance');$value = $field->get('blankType');if($value === null) $value = 'null';$f->val($value);$inputfields->add($f);return $inputfields;}/*** Get compatible Fieldtypes** @param Field $field* @return Fieldtypes**/public function ___getCompatibleFieldtypes(Field $field) {$fieldtypes = $this->wire(new Fieldtypes());foreach($this->wire()->fieldtypes as $fieldtype) {if($fieldtype instanceof FieldtypeModule) $fieldtypes->add($fieldtype);}return $fieldtypes;}/*** Render a markup string of the value.** @param Page $page Page that $value comes from* @param Field $field Field that $value comes from* @param mixed $value Optionally specify the value returned by `$page->getFormatted('field')`.* When specified, value must be a formatted value.* If null or not specified (recommended), it will be retrieved automatically.* @param string $property Optionally specify the property or index to render. If omitted, entire value is rendered.* @return string Returns a string or object that can be output as a string, ready for output.* Return a MarkupFieldtype value when suitable so that the caller has potential specify additional* config options before typecasting it to a string.**/public function ___markupValue(Page $page, Field $field, $value = null, $property = '') {if($value === null) $value = $page->getFormatted($field->name);$value = (string) $value;if(empty($value)) $value = '';return $value;}/*** Return array with information about what properties and operators can be used with this field.** @param Field $field* @param array $data Array of extra data, when/if needed* @return array See `FieldSelectorInfo` class for details.**/public function ___getSelectorInfo(Field $field, array $data = array()) {if($data) {}/** @var FieldSelectorInfo $selectorInfo */$selectorInfo = $this->wire(new FieldSelectorInfo());$labelField = $field->get('labelField');$info = $selectorInfo->getSelectorInfo($field);$info['input'] = 'select';$info['operators'] = array('=', '!=');$selectableModules = $this->getSelectableModules($field);foreach($selectableModules as $m) {$id = (int) $m['id'];if($labelField === 'title' || $labelField === 'title-summary') {$label = $m['title'];} else {$label = $m['class'];}$info['options'][$id] = $label;}return $info;}/*** Get the database query that matches a Fieldtype table’s data with a given value.** @param PageFinderDatabaseQuerySelect $query* @param string $table The table name to use* @param string $subfield Name of the subfield (typically 'data', unless selector explicitly specified another)* @param string $operator The comparison operator.* @param mixed $value Value to find.* @return PageFinderDatabaseQuerySelect|DatabaseQuerySelect $query* @throws WireException**/public function getMatchQuery($query, $table, $subfield, $operator, $value) {if(empty($subfield)) $subfield = 'data';if(!empty($value) && !ctype_digit("$value") && $subfield = 'data') {$value = $this->wire()->modules->getModuleID($value); // convert class name to ID}return parent::getMatchQuery($query, $table, $subfield, $operator, $value);}}