Blame | Last modification | View Log | Download
<?php namespace ProcessWire;/*** ProcessWire Toggle Fieldtype** #pw-summary Configurable yes/no, on/off toggle alternative to a checkbox, plus optional “other” option.** #pw-body =* Toggle fieldtype for “yes/on”, “no/off” and optional “other” state. Unlike* FieldtypeCheckbox, this Fieldtype can differentiate between a selection* of “no” and no-selection (aka unknown state), and it can also optionally* support a selection for “other” (with custom label).** When using a selector to find pages matching a particular toggle state,* or when setting values to `$page->your_field`, the following:** - `0` or `no` or `FieldtypeToggle::valueNo` for no/off selection* - `1` or `yes` or `FieldtypeToggle::valueYes` for yes/on selection* - `2` or `other` or `FieldtypeToggle::valueOther` for other selection (if enabled for field)* - `''` blank string or `unknown` or `FieldtypeToggle::valueUnknown` for “no selection”** Please note that `0` and “no selection” are different things (unlike with a checkbox) so* be sure to consider this when finding pages or outputting values. The examples below* include a couple that demonstrate this.** Examples (for field named “featured”):* ~~~~~* // find pages with “yes” selected for “featured”* $items = $pages->find("featured=1");* $items = $pages->find("featured=yes");** // find pages with “no” selected for “featured”* $items = $pages->find("featured=0");* $items = $pages->find("featured=no");** // find pages with no selection* $items = $pages->find("featured=''");* $items = $pages->find("featured=unknown");** // find pages with yes or no selection* $items = $pages->find("featured=1|0");* $items = $pages->find("featured=yes|no");** // find pages with “no” selected, or no selection* $items = $pages->find("featured=''|0");* $items = $pages->find("featured=unknown|no");** // output current value (blank, 0 or 1, or 2 if “other” option available)* // unless you’ve configured it to output custom labels when formatted* echo $page->featured;** // determine current setting (assuming labels not overriding values)* if($page->featured === '') {* // unknown aka no-selection* } else if($page->featured === 0) {* // no selected* } else if($page->featured === 1) {* // yes selected* } else if($page->featured === 2) {* // other selected (if enabled)* }** // set value of $page->featured to yes/on* $page->featured = 1;* ~~~~~** #pw-body** For documentation about the fields used in this class, please see:* /wire/core/Fieldtype.php** ProcessWire 3.x, Copyright 2019 by Ryan Cramer* https://processwire.com***/class FieldtypeToggle extends Fieldtype {public static function getModuleInfo() {return array('title' => __('Toggle (Yes/No)', __FILE__),'version' => 1,'summary' => __('Configurable yes/no, on/off toggle alternative to a checkbox, plus optional “other” option.', __FILE__),'requires' => 'InputfieldToggle',);}// value constantsconst valueNo = 0;const valueYes = 1;const valueOther = 2;const valueUnknown = '';// format constantsconst formatNone = 0;const formatBoolean = 1;const formatString = 2;const formatEntities = 3;/*** Get the blank value** @param Page $page* @param Field $field* @return string**/public function getBlankValue(Page $page, Field $field) {return self::valueUnknown;}/*** Get the default value** #pw-internal** @param Page $page* @param Field $field* @return mixed**/public function getDefaultValue(Page $page, Field $field) {return self::valueUnknown;}/*** Return whether the given value is considered empty or not.** This can be anything that might be present in a selector value and thus is* typically a string. However, it may be used outside of that purpose so you* shouldn't count on it being a string.** Example: an integer or text Fieldtype might not consider a "0" to be empty,* whereas a Page reference would.** #pw-group-finding** @param Field $field* @param mixed $value* @return bool**/public function isEmptyValue(Field $field, $value) {if($field) {}// 0 is allowed because it represents "no/off" selectionif($value === 0 || $value === "0") return false;if($value === 'unknown' || "$value" === "-1") return true;$value = trim($value, '"\'');return empty($value);}/*** Sanitize value to 0, 1, 2, '' (blank string), or optionaly given fail value on failure** @param string|int $value* @param string $failValue Value to return if we are unable to map to toggle option* @return int|string**/protected function _sanitizeValue($value, $failValue = self::valueUnknown) {$strValue = strtolower("$value");if($strValue === "0" || $value === false || $strValue === "no" || $strValue === "off") {$value = self::valueNo;} else if($strValue === "1" || $value === true || $strValue === "yes" || $strValue === "on") {$value = self::valueYes;} else if($strValue === "2" || $strValue === "other") {$value = self::valueOther;} else if($value === null || $strValue === '' || $strValue === 'unknown' || $strValue === '-1') {$value = self::valueUnknown;} else {$value = $failValue;}return $value;}/*** Sanitize value for placement on Page object** @param Page $page* @param Field $field* @param int|object|WireArray|string $value* @return int|object|WireArray|string**/public function sanitizeValue(Page $page, Field $field, $value) {$cleanValue = $this->_sanitizeValue($value, 'fail');if($cleanValue === 'fail') {// if we fail to sanitize here, try to sanitize with InputfieldToggle// which can map toggle labels to toggle values/** @var InputfieldToggle $f */$f = $field->getInputfield($page, $field);$cleanValue = $f ? $f->sanitizeValue($value) : '';}return $cleanValue;}/*** Return the markup value** @param Page $page* @param Field $field* @param int|string|null $value* @param string $property** @return MarkupFieldtype|string**/public function ___markupValue(Page $page, Field $field, $value = null, $property = '') {/** @var InputfieldToggle $f */$f = $field->getInputfield($page, $field);if($value !== null) $f->val($value);if(!$f) return '';return $f->renderValue();}/*** Get the InputfieldToggle instance** @param Page $page* @param Field $field* @return InputfieldToggle**/public function getInputfield(Page $page, Field $field) {/** @var InputfieldToggle $f */$f = $this->wire('modules')->get('InputfieldToggle');return $f;}/*** Get database schema** @param Field $field* @return array**/public function getDatabaseSchema(Field $field) {$schema = parent::getDatabaseSchema($field);$schema['data'] = "tinyint NOT NULL";$schema['keys']['data'] = 'KEY data (data)';return $schema;}/*** @param DatabaseQuerySelect $query* @param string $table* @param string $subfield* @param string $operator* @param mixed $value** @return DatabaseQuery|DatabaseQuerySelect* @throws WireException**/public function getMatchQuery($query, $table, $subfield, $operator, $value) {$value = $this->_sanitizeValue($value); // 0, 1, 2 or ''$matchNull = false;if($value === '' && $operator === '=') {// match only no-selection$matchNull = true;} else if($value === '' && $operator === '>') {// match all except no-selection$value = 0;$operator = '>=';} else if($value !== '' && $operator === '!=') {// match value as well as no-selection$matchNull = true;}if($value === '' && ($operator[0] === '<' || $operator[0] === '>')) {throw new WireException("Operator $operator not supported here for non-value");}if($matchNull) {// match non-present (null) rows in selection via left joinstatic $n = 0;$_table = $table . '_tog' . (++$n);$query->leftjoin("$table AS $_table ON $_table.pages_id=pages.id");$where = "$_table.pages_id IS NULL ";if($value !== '') $where .= "OR $_table.data$operator" . ((int) $value);$query->where(trim($where));} else {$query = parent::getMatchQuery($query, $table, $subfield, $operator, $value);}return $query;}/*** Get information used for InputfieldSelector interactive selector builder** @param Field $field* @param array $data* @return array**/public function ___getSelectorInfo(Field $field, array $data = array()) {/** @var InputfieldToggle $inputfield */$inputfield = $field->getInputfield(new NullPage(), $field);$labels = $inputfield->getLabels();$info = parent::___getSelectorInfo($field, $data);$info['input'] = 'select';$info['options'] = array('1' => $labels['yes'], '0' => $labels['no']);if($field->get('useOther')) $info['options']['2'] = $labels['other'];$info['options']['""'] = $labels['unknown'];$info['operators'] = array('=', '!=');return $info;}/*** Get an array of Fieldtypes that are compatible with this one** This represents the list of Fieldtype modules that the user is allowed to change to from this one.** @param Field $field* @return Fieldtypes|null**/public function ___getCompatibleFieldtypes(Field $field) {if($field) {}$fieldtypes = $this->wire(new Fieldtypes());foreach($this->wire('fieldtypes') as $fieldtype) {if($fieldtype instanceof FieldtypeToggle || $fieldtype instanceof FieldtypeCheckbox) {$fieldtypes->add($fieldtype);}}return $fieldtypes;}/*** Given an 'awake' value, as set by wakeupValue(), convert the value back to a basic type for storage in database.** @param Page $page* @param Field $field* @param string|int|float|array|object $value* @return string|int|float|array* @see Fieldtype::wakeupValue()**/public function ___sleepValue(Page $page, Field $field, $value) {if($page && $field) {}return $this->_sanitizeValue($value);}/*** Format the given value for output and return a string of the formatted value** @param Page $page Page that the value lives on* @param Field $field Field that represents the value* @param string|int|object $value The value to format* @return mixed**/public function ___formatValue(Page $page, Field $field, $value) {$value = $this->_sanitizeValue($value);$formatType = (int) $field->get('formatType');if($value === '' || $formatType === self::formatNone) {// no formatting, or blank string which always represents no-selection} else if($formatType === self::formatBoolean) {if($value === 0) $value = false;if($value === 1) $value = true;} else if($formatType === self::formatString || $formatType === self::formatEntities) {/** @var InputfieldToggle $f */$f = $field->getInputfield($page, $field);if($f && $f instanceof InputfieldToggle) {$value = $f->getValueLabel($value);if($formatType == self::formatEntities) $value = $f->formatLabel($value, false);} else if($formatType == self::formatEntities) {$value = $this->wire('sanitizer')->entities1($value);}}return $value;}/*** Get any Inputfields used for configuration of this Fieldtype.** @param Field $field* @return InputfieldWrapper**/public function ___getConfigInputfields(Field $field) {$inputfields = parent::___getConfigInputfields($field);/** @var InputfieldRadios $f */$f = $this->modules->get('InputfieldRadios');$f->attr('name', 'formatType');$f->attr('value', (int) $field->get('formatType'));$f->label = $this->_('What do you want the formatted value of your toggle field to be?');$f->icon = 'toggle-on';$f->description =sprintf($this->_('Select the formatted value returned by %s.'), "`\$page->$field->name`") . ' ' .$this->_('Please also see the “Input” tab for all of the other Toggle field settings.');$f->notes ='¹ ' . $this->_('For all of the above, no-selection is always represented by a blank string.') . "\n" .'² ' . $this->_('If a 3rd/other option is enabled, it is represented by integer 2.');$f->detail =$this->_('When a page’s output formatting is off, the value is always integer 0, 1, 2 (for No, Yes, Other) or a blank string when no selection.');$f->addOption(self::formatNone, $this->_('**Integer:** 0=no, 1=yes (same as no formatting).') . ' ²');$f->addOption(self::formatBoolean, $this->_('**Boolean:** True or false for yes/no states.') . ' ²');$f->addOption(self::formatString, $this->_('**String:** Use text labels configured with “Input” settings.'));$f->addOption(self::formatEntities, $this->_('**Entities:** Same as above (string) but entity encoded for HTML output.'));$f->attr('value', (int) $field->get('formatType'));$inputfields->add($f);// names of fields in the form that are allowed in fieldgroup/template contextreturn $inputfields;}}