Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download
<?php namespace ProcessWire;/*** ProcessWire FieldtypeFieldsetOpen and InputfieldFieldsetOpen** Provides a Fieldtype and Inputfield for opening a Fieldset.** For documentation about the fields used in this class, please see:* /wire/core/Fieldtype.php** ProcessWire 3.x, Copyright 2016 by Ryan Cramer* https://processwire.com***/class InputfieldFieldsetOpen extends InputfieldWrapper { }class FieldtypeFieldsetOpen extends Fieldtype {/*** Appended to the name of a 'Close' version of a FieldsetOpen**/const fieldsetCloseIdentifier = '_END';public static function getModuleInfo() {return array('title' => 'Fieldset (Open)','version' => 101,'summary' => 'Open a fieldset to group fields. Should be followed by a Fieldset (Close) after one or more fields.','permanent' => true,);}/*** Setup hooks to minotor when Fields are added and deleted** But only do it once so that we don't have the same hooks setup when there are multiple Fieldsets**/public function init() {if($this->process == 'ProcessField' || $this->process = 'ProcessTemplate') {$this->addProcessHooks();}}/*** Add hooks to ProcessField or ProcessTemplate, called only when applicable**/protected function addProcessHooks() {$this->addHook('ProcessField::fieldAdded', $this, 'hookFieldAdded');$this->addHook('ProcessField::fieldDeleted', $this, 'hookFieldDeleted');$this->addHook('ProcessField::allowFieldInTemplate', $this, 'hookAllowFieldInTemplate');$this->addHook('ProcessTemplate::fieldAdded', $this, 'hookTemplateFieldAdded');$this->addHook('ProcessTemplate::fieldRemoved', $this, 'hookTemplateFieldRemoved');}public function sanitizeValue(Page $page, Field $field, $value) {return null;}public function getInputfield(Page $page, Field $field) {$inputfield = $this->wire(new InputfieldFieldsetOpen());$inputfield->class = $this->className() . ' InputfieldFieldset';return $inputfield;}public function savePageField(Page $page, Field $field) {return true;}public function ___getConfigInputfields(Field $field) {$inputfields = parent::___getConfigInputfields($field);return $inputfields;}public function ___getConfigAdvancedInputfields(Field $field) {$inputfields = parent::___getConfigAdvancedInputfields($field);// these really aren't applicable with fieldsets, so remove them$inputfields->remove($inputfields->get('autojoin'));$inputfields->remove($inputfields->get('global'));return $inputfields;}public function ___getCompatibleFieldtypes(Field $field) {$fieldtypes = $this->wire(new Fieldtypes());foreach($this->wire('fieldtypes') as $fieldtype) {if($fieldtype instanceof FieldtypeFieldsetOpen) $fieldtypes->add($fieldtype);}return $fieldtypes;}public function loadPageField(Page $page, Field $field) {return '';}public function getLoadQuery(Field $field, DatabaseQuerySelect $query) {return $query;}/*** Is given field a fieldset opener?** For hooks to share in determining if this is a field they want to operate on** @param Field $field* @return bool**/protected function isFieldset(Field $field) {return $field->type instanceof FieldtypeFieldsetOpen && !($field->type instanceof FieldtypeFieldsetClose);}/*** Check that Fieldgroup has matching open/close fieldsets and in correct order** @param Fieldgroup $fieldgroup* @return bool* @since 3.0.164**/public function checkFieldgroupFieldsets(Fieldgroup $fieldgroup) {list($openers, $closers, $isChanged) = array(array(), array(), false);foreach($fieldgroup as $field) {/** @var Fieldtype $fieldtype */if(!$field->type instanceof FieldtypeFieldsetOpen) continue;if($field->type instanceof FieldtypeFieldsetClose) {if(!strpos($field->name, self::fieldsetCloseIdentifier)) continue;$name = substr($field->name, 0, -1 * strlen(self::fieldsetCloseIdentifier));$closers[$name] = $field;} else if(!isset($closers[$field->name])) {// ensure opener comes before closer, otherwise only closer remains$openers[$field->name] = $field;}}if(count($openers) === count($closers)) return true;foreach($openers as $name => $opener) {/** @var Field $opener */if(isset($closers[$name])) continue;$closer = $this->getFieldsetCloseField($opener);if($closer) {$fieldgroup->insertAfter($closer, $opener);} else {$fieldgroup->remove($opener);}$isChanged = true;}foreach($closers as $name => $closer) {/** @var Field $closer */if(isset($openers[$name])) continue;$opener = $this->getFieldsetOpenField($closer);if($opener) {$fieldgroup->insertBefore($opener, $closer);} else {$fieldgroup->remove($closer);}$isChanged = true;}return $isChanged;}/*** Get the Field that closes/terminates the given Fieldset field** @param Field $field* @param bool $createIfNotExists Create it if it doesn't already exist?* @return null|Field of type FieldtypeFieldsetClose**/public function getFieldsetCloseField(Field $field, $createIfNotExists = false) {if(!$this->isFieldset($field)) return null;$name = $field->name . self::fieldsetCloseIdentifier;$closer = $this->wire('fields')->get($name);if(!$closer) {$closeFieldID = (int) $field->get('closeFieldID');if($closeFieldID) {$closer = $this->wire('fields')->get($closeFieldID);}}if(!$closer && $createIfNotExists) {$closer = $this->wire(new Field());$closer->type = $this->wire(new FieldtypeFieldsetClose());$closer->name = $name;$closer->label = "Close an open fieldset";$closer->description ="This field was added automatically to accompany fieldset '$field'. " ."It should be placed in your template/fieldgroup wherever you want the fieldset to end.";$closer->set('openFieldID', $field->id);$closer->save();}if($closer && !$field->get('closeFieldID')) {$field->set('closeFieldID', $closer->id);$field->save();}return $closer;}/*** Get the Field that opens the given FieldsetClose field** @param Field $field* @return null|Field of type FieldtypeFieldsetOpen, if found* @since 3.0.164**/public function getFieldsetOpenField(Field $field) {if(!$field->type instanceof FieldtypeFieldsetClose) return null;if(!strpos($field->name, self::fieldsetCloseIdentifier)) return null;$name = substr($field->name, 0, -1 * strlen(self::fieldsetCloseIdentifier));$opener = $this->wire()->fields->get($name);if($opener) return $opener;foreach($this->wire()->fields as $f) {if(!$f->type instanceof FieldtypeFieldsetOpen) continue;$closeFieldID = (int) $f->get('closeFieldID');if($closeFieldID != $field->id) continue;$opener = $f;break;}return $opener;}/*** Hook executed when field is added via ProcessField** @param HookEvent $event**/public function hookFieldAdded($event) {$field = $event->arguments[0];if(!$this->isFieldset($field)) return;$closer = $this->getFieldsetCloseField($field, true);$this->message("Also added field '$closer', to accompany '$field'");}/*** Hook executed when field is deleted via ProcessField** @param HookEvent $event**/public function hookFieldDeleted($event) {$field = $event->arguments[0];if(!$this->isFieldset($field)) return;$closer = $this->getFieldsetCloseField($field);if($closer) {$this->wire('fields')->delete($closer);$this->message("Delete also issued for field '$closer'");}}/*** Hook executed when field is added to a template via ProcessTemplate** @param HookEvent $event**/public function hookTemplateFieldAdded($event) {/** @var Field $field *//** @var Template $template */list($field, $template) = $event->arguments;if(!$this->isFieldset($field)) return;$closer = $this->getFieldsetCloseField($field);if(!$closer) return;if(!$template->fieldgroup->hasField($closer)) {$template->fieldgroup->add($closer);$this->message("Also added field '$closer', which should be placed where you want to close fieldset '$field'");}}/*** Hook executed when field is removed from a template via ProcessTemplate** @param HookEvent $event**/public function hookTemplateFieldRemoved($event) {list($field, $template) = $event->arguments;if(!$this->isFieldset($field)) return;$closer = $this->getFieldsetCloseField($field);if(!$closer) return;$template->fieldgroup->remove($closer);$this->message("Also removed '$closer', which accompanies '$field'");}/*** Hook to ProcessField::allowFieldInTemplate** @param HookEvent $event**/public function hookAllowFieldInTemplate(HookEvent $event) {/** @var Field $field */// $field = $event->arguments(0);/** @var Template $template */// $template = $event->arguments(1);}/*** Hook called by Fields::save() after a field using this type has been renamed** Note that PW already takes care of renaming the field_[name] table.* Most Fieldtypes don't need to do anything here, but this exists just in case.** #pw-internal** @param Field $field* @param string $prevName Previous name (current name can be found in $field->name)**/public function ___renamedField(Field $field, $prevName) {// avoid ending up in infinite loop, since FieldtypeFieldsetClose extends thisif($this instanceof FieldtypeFieldsetClose) return;// rename the _END field to match this one$fields = $this->wire()->fields;$oldName = $prevName . self::fieldsetCloseIdentifier;$newName = $field->name . self::fieldsetCloseIdentifier;$closer = $this->getFieldsetCloseField($field);if(!$closer) $closer = $fields->get($oldName);if($closer && $closer->name != $newName) {$closer->name = $newName;$fields->save($closer);}parent::___renamedField($field, $prevName);}}