From f573473066592b587f4d30ed2652a7132f11e014 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Fri, 22 Dec 2017 11:11:02 -0500 Subject: [PATCH] Add support for custom configuration of what Fieldtype/Inputfield settings may be overridden for field/template context. Appears only in $config->advanced mode. You can see it when editing a field (ProcessField) on the "Overrides" tab. Related to processwire/processwire-requests#145 --- wire/core/Field.php | 49 ++++-- .../Process/ProcessField/ProcessField.module | 141 ++++++++++++++---- 2 files changed, 148 insertions(+), 42 deletions(-) diff --git a/wire/core/Field.php b/wire/core/Field.php index d2281e81..1ab0e7ba 100644 --- a/wire/core/Field.php +++ b/wire/core/Field.php @@ -31,6 +31,7 @@ * @property array $viewRoles Role IDs with view access, applicable only if access control is enabled. #pw-group-access * @property array|null $orderByCols Columns that WireArray values are sorted by (default=null), Example: "sort" or "-created". #pw-internal * @property int|null $paginationLimit Used by paginated WireArray values to indicate limit to use during load. #pw-internal + * @property array $allowContexts Names of settings that are custom configured to be allowed for context. #pw-group-properties * * Common Inputfield properties that Field objects store: * @property int|bool|null $required Whether or not this field is required during input #pw-group-properties @@ -341,16 +342,17 @@ class Field extends WireData implements Saveable, Exportable { */ public function get($key) { if($key == 'viewRoles') return $this->viewRoles; - else if($key == 'editRoles') return $this->editRoles; - else if($key == 'table') return $this->getTable(); - else if($key == 'prevTable') return $this->prevTable; - else if($key == 'prevFieldtype') return $this->prevFieldtype; - else if(isset($this->settings[$key])) return $this->settings[$key]; - else if($key == 'icon') return $this->getIcon(true); - else if($key == 'useRoles') return ($this->settings['flags'] & self::flagAccess) ? true : false; - else if($key == 'flags') return $this->settings['flags']; + else if($key == 'editRoles') return $this->editRoles; + else if($key == 'table') return $this->getTable(); + else if($key == 'prevTable') return $this->prevTable; + else if($key == 'prevFieldtype') return $this->prevFieldtype; + else if(isset($this->settings[$key])) return $this->settings[$key]; + else if($key == 'icon') return $this->getIcon(true); + else if($key == 'useRoles') return ($this->settings['flags'] & self::flagAccess) ? true : false; + else if($key == 'flags') return $this->settings['flags']; $value = parent::get($key); + if($key === 'allowContexts' && !is_array($value)) $value = array(); if(is_array($this->trackGets)) $this->trackGets($key); return $value; } @@ -916,6 +918,7 @@ class Field extends WireData implements Saveable, Exportable { if($fieldgroupContext) { $allowContext = $this->type->getConfigAllowContext($this); if(!is_array($allowContext)) $allowContext = array(); + $allowContext = array_merge($allowContext, $this->allowContexts); } else { $allowContext = array(); } @@ -926,6 +929,8 @@ class Field extends WireData implements Saveable, Exportable { if(!$fieldgroupContext) $inputfields->head = $this->_('Field type details'); $inputfields->attr('title', $this->_('Details')); $inputfields->attr('id+name', 'fieldtypeConfig'); + $remainingNames = array(); + foreach($allowContext as $name) $remainingNames[$name] = $name; try { $fieldtypeInputfields = $this->type->getConfigInputfields($this); @@ -940,7 +945,19 @@ class Field extends WireData implements Saveable, Exportable { foreach($fieldtypeInputfields as $inputfield) { if($fieldgroupContext && !in_array($inputfield->name, $allowContext)) continue; $inputfields->append($inputfield); + unset($remainingNames[$inputfield->name]); } + // now capture those that may have been stuck in a fieldset + if($fieldgroupContext) { + foreach($remainingNames as $name) { + if($inputfields->getChildByName($name)) continue; + $inputfield = $fieldtypeInputfields->getChildByName($name); + if(!$inputfield) continue; + $inputfields->append($inputfield); + unset($remainingNames[$inputfield->name]); + } + } + } catch(\Exception $e) { $this->trackException($e, false, true); } @@ -955,11 +972,15 @@ class Field extends WireData implements Saveable, Exportable { if($inputfield) { if($fieldgroupContext) { $allowContext = array('visibility', 'collapsed', 'columnWidth', 'required', 'requiredIf', 'showIf'); - $allowContext = array_merge($allowContext, $inputfield->getConfigAllowContext($this)); + $allowContext = array_merge($allowContext, $this->allowContexts, $inputfield->getConfigAllowContext($this)); } else { $allowContext = array(); $inputfields->head = $this->_('Input field settings'); } + $remainingNames = array(); + foreach($allowContext as $name) { + $remainingNames[$name] = $name; + } $inputfields->attr('title', $this->_('Input')); $inputfields->attr('id+name', 'inputfieldConfig'); $inputfieldInputfields = $inputfield->getConfigInputfields(); @@ -974,6 +995,16 @@ class Field extends WireData implements Saveable, Exportable { foreach($inputfieldInputfields as $i) { if($fieldgroupContext && !in_array($i->name, $allowContext)) continue; $inputfields->append($i); + unset($remainingNames[$i->name]); + } + if($fieldgroupContext) { + foreach($remainingNames as $name) { + if($inputfields->getChildByName($name)) continue; + $inputfield = $inputfieldInputfields->getChildByName($name); + if(!$inputfield) continue; + $inputfields->append($inputfield); + unset($remainingNames[$inputfield->name]); + } } } diff --git a/wire/modules/Process/ProcessField/ProcessField.module b/wire/modules/Process/ProcessField/ProcessField.module index a5aff4bf..90c1530f 100644 --- a/wire/modules/Process/ProcessField/ProcessField.module +++ b/wire/modules/Process/ProcessField/ProcessField.module @@ -911,7 +911,8 @@ class ProcessField extends Process implements ConfigurableModule { if(!$fieldgroup->hasFieldContext($field->name, $this->contextNamespace)) continue; $allChanges[$fieldgroup->name] = $this->getContextChanges($form, $field, $fieldgroup); } - if(!count($allChanges)) return; + + if(!count($allChanges) && !$this->wire('config')->advanced) return; $tab = $this->wire(new InputfieldWrapper()); $tab->attr('title', $this->_('Overrides')); @@ -932,43 +933,117 @@ class ProcessField extends Process implements ConfigurableModule { $f->notes = $this->_('To edit an override setting or override other settings, edit any template and click the field name in the fields list.'); } $tab->add($f); + + if(count($allChanges)) { + /** @var MarkupAdminDataTable $table */ + $table = $this->wire('modules')->get('MarkupAdminDataTable'); + $table->setEncodeEntities(false); + $table->setSortable(false); + $header = array(); + if(!$this->fieldgroup) $header[] = $this->_x('Template', 'context-thead'); + $header[] = $this->_x('Setting', 'context-thead'); + $header[] = $this->_x('Original', 'context-thead'); + $header[] = $this->_x('Override', 'context-thead'); + $header[] = ""; + $table->headerRow($header); + + $sanitizer = $this->wire('sanitizer'); + $lastFieldgroupName = ''; + $this->wire('modules')->get('JqueryUI')->use('modal'); + + foreach($allChanges as $fieldgroupName => $changes) { + $fieldgroup = $this->wire('fieldgroups')->get($fieldgroupName); + foreach($changes as $key => $change) { + $a = $fieldgroupName; + // $a = "$fieldgroupName"; + $fieldgroupLabel = $fieldgroupName == $lastFieldgroupName ? '' : $a; + $row = array(); + if(!$this->fieldgroup) $row[] = $fieldgroupLabel; + $row[] = $sanitizer->entities($change['label']); + $row[] = "" . $sanitizer->entities($change['originalValue']) . ""; + $row[] = $sanitizer->entities($change['value']); + $row[] = ""; + $options = $fieldgroupLabel ? array('separator' => true) : array(); + $table->row($row, $options); + $lastFieldgroupName = $fieldgroupName; + } + } + + $f->value = $table->render(); + } else { + $f->value = '

' . $this->_('There are currently no settings being overridden by template.') . '

'; + $f->collapsed = Inputfield::collapsedYes; + } - /** @var MarkupAdminDataTable $table */ - $table = $this->wire('modules')->get('MarkupAdminDataTable'); - $table->setEncodeEntities(false); - $table->setSortable(false); - $header = array(); - if(!$this->fieldgroup) $header[] = $this->_x('Template', 'context-thead'); - $header[] = $this->_x('Setting', 'context-thead'); - $header[] = $this->_x('Original', 'context-thead'); - $header[] = $this->_x('Override', 'context-thead'); - $header[] = ""; - $table->headerRow($header); + // allow the following configuration only for advanced mode + if(!$this->wire('config')->advanced) return; - $sanitizer = $this->wire('sanitizer'); - $lastFieldgroupName = ''; - $this->wire('modules')->get('JqueryUI')->use('modal'); - - foreach($allChanges as $fieldgroupName => $changes) { - $fieldgroup = $this->wire('fieldgroups')->get($fieldgroupName); - foreach($changes as $key => $change) { - $a = $fieldgroupName; - // $a = "$fieldgroupName"; - $fieldgroupLabel = $fieldgroupName == $lastFieldgroupName ? '' : $a; - $row = array(); - if(!$this->fieldgroup) $row[] = $fieldgroupLabel; - $row[] = $sanitizer->entities($change['label']); - $row[] = "" . $sanitizer->entities($change['originalValue']) . ""; - $row[] = $sanitizer->entities($change['value']); - $row[] = ""; - $options = $fieldgroupLabel ? array('separator' => true) : array(); - $table->row($row, $options); - $lastFieldgroupName = $fieldgroupName; + /** @var InputfieldCheckboxes $field */ + $field = $this->modules->get('InputfieldCheckboxes'); + $field->attr('name', 'allowContexts'); + $field->label = $this->_('Allowed overrides'); + $field->icon = 'sliders'; + $field->description = + $this->_('Checked settings will appear as configuration options when editing this field within the context of a particular template.') . ' ' . + $this->_('**Warning:** doing this for settings beyond those specified by the module author may not always work, or may cause problems.'); + $field->table = true; + $field->thead = + $this->_('Label') . '|' . + $this->_('Name') . '|' . + $this->_('Type'); + + $tabNames = array( + 'fieldtypeConfig' => $this->_('Details:'), + 'inputfieldConfig' => $this->_('Input:') + ); + $exclusions = array( + 'collapsed', + 'showIf', + 'required', + 'requiredIf', + 'columnWidth', + 'visibility', + 'themeOffset', + 'themeBorder', + 'themeColor', + ); + + $fieldtypeNames = $this->field->type->getConfigAllowContext($this->field); + if(!is_array($fieldtypeNames)) $fieldtypeNames = array(); + $dummyPage = $this->wire('pages')->get("/"); // only using this to satisfy param requirement + $inputfieldNames = $this->field->getInputfield($dummyPage)->getConfigAllowContext($this->field); + if(!is_array($inputfieldNames)) $inputfieldNames = array(); + $alwaysSelected = array_merge($fieldtypeNames, $inputfieldNames); + $allowContexts = $this->field->get('allowContexts'); + $alwaysSelected = array_diff($alwaysSelected, $allowContexts); + $qty = 0; + + foreach($tabNames as $tabName => $tabLabel) { + /** @var InputfieldWrapper $tabInputfield */ + $tabInputfield = $form->getChildByName($tabName); + if(!$tabInputfield) continue; + foreach($tabInputfield->getAll() as $f) { + $name = $f->name; + if(strpos($name, '_') === 0) continue; + if(in_array($name, $exclusions)) continue; + if($f instanceof InputfieldWrapper || $f instanceof InputfieldMarkup || $f instanceof InputfieldHidden) continue; + $typeName = str_replace('Inputfield', '', $f->className()); + $label = str_replace('|', ' ', "$tabLabel $f->label") . + "| [span.detail] $name [/span] " . + "| [span.detail] $typeName [/span]"; + if(in_array($name, $alwaysSelected)) { + $field->addOption($name, $label, array('checked' => 'checked', 'disabled' => 'disabled')); + $allowContexts[] = $name; + } else { + $field->addOption($name, $label); + $qty++; + } } } - $f->value = $table->render(); + $field->attr('value', $allowContexts); + if($qty) $tab->append($field); } /*