ProcessWire - Module schreiben: Unterschied zwischen den Versionen

Aus Wikizone
Wechseln zu: Navigation, Suche
Zeile 660: Zeile 660:
 
=== Inputfield / Fieldtype ===
 
=== Inputfield / Fieldtype ===
 
[[ProcessWire - Fieldtype erstellen (Module)]]
 
[[ProcessWire - Fieldtype erstellen (Module)]]
==== FieldtypePhone ====
 
InputfieldPhone.module
 
<syntaxhighlight lang="php">
 
<?php
 
 
/**
 
* ProcessWire Phone Inputfieldtype
 
* by Adrian Jones with code from "Soma" Philipp Urlich's Dimensions Fieldtype module and Ryan's core FieldtypeDatetime module
 
*
 
* ProcessWire 3.x
 
* Copyright (C) 2010 by Ryan Cramer
 
* Licensed under GNU/GPL v2, see LICENSE.TXT
 
*
 
* http://www.processwire.com
 
* http://www.ryancramer.com
 
*
 
*/
 
 
class InputfieldPhone extends Inputfield {
 
 
    public static function getModuleInfo() {
 
        return array(
 
            'title' => __('Phone Inputfield', __FILE__),
 
            'summary' => __('Multi part phone field, with custom output formatting options.', __FILE__),
 
            'version' => '3.1.0',
 
            'author' => 'Adrian Jones',
 
            'href' => 'http://modules.processwire.com/modules/fieldtype-phone/',
 
            'icon' => 'phone',
 
            'requires' => array("FieldtypePhone")
 
      );
 
    }
 
 
  /**
 
    * Default configuration for module
 
    *
 
    */
 
    static public function getDefaultData() {
 
        return array(
 
            "hide_input_labels" => 0,
 
            "placeholder_input_labels" => 0,
 
            "input_class" => '',
 
            "country_input" => 0,
 
            "country_input_label" => 'Ctry',
 
            "country_input_width" => 60,
 
            "area_code_input" => 0,
 
            "area_code_input_label" => 'Area',
 
            "area_code_input_width" => 80,
 
            "number_input_label" => 'Num',
 
            "number_input_width" => 140,
 
            "extension_input" => 0,
 
            "extension_input_label" => 'Ext',
 
            "extension_input_width" => 80,
 
            "output_format_override_input" => 0,
 
            "allow_letters_input" => 0
 
        );
 
    }
 
 
    /**
 
    * Construct the Inputfield, setting defaults for all properties
 
    *
 
    */
 
    public function __construct() {
 
        $this->fieldtypePhone = $this->wire('modules')->get('FieldtypePhone')->getArray();
 
        $this->set('output_format',$this->fieldtypePhone["output_format"]);
 
        $this->set('output_format_options',$this->fieldtypePhone["output_format_options"]);
 
        foreach(self::getDefaultData() as $key => $value) {
 
            $this->$key = $value;
 
        }
 
        parent::__construct();
 
    }
 
 
    /**
 
    * Per the Module interface, init() is called when the system is ready for API usage
 
    *
 
    */
 
    public function init() {
 
        return parent::init();
 
    }
 
 
    /**
 
    * Return the completed output of this Inputfield, ready for insertion in an XHTML form
 
    *
 
    * @return string
 
    *
 
    */
 
    public function ___render() {
 
 
        if($this->wire('languages')) {
 
            $language = $this->wire('user')->language;
 
            $lang = !$language->isDefault() ? $language : '';
 
        }
 
        else {
 
            $lang = '';
 
        }
 
 
        $out = '';
 
 
        $value = $this->attr('value') ? $this->attr('value') : new \Phone();
 
 
        $pattern = $this->allow_letters_input ? '[0-9A-Za-z]*' : '[0-9]*';
 
 
        if($this->country_input) {
 
            $out .= "<div class='phone_col'>";
 
            $out .= "<label>".($this->hide_input_labels ? '' : $this->{"country_input_label$lang"} . ' ')."<input type='text' pattern='".$pattern."' class='".$this->input_class."' ".($this->placeholder_input_labels ? ' placeholder="'.$this->{"country_input_label$lang"}.'"' : '') . ($this->country_input_width !== 0 ? ' style="width:'.$this->country_input_width.'px"' : '')." name='{$this->name}_country' id='Inputfield_{$this->name}_country' value='{$value->country}'/></label>";
 
            $out .= "</div>";
 
        }
 
 
        if($this->area_code_input) {
 
            $out .= "<div class='phone_col'>";
 
            $out .= "<label>".($this->hide_input_labels ? '' : $this->{"area_code_input_label$lang"} . ' ')."<input type='text' pattern='".$pattern."' class='".$this->input_class."' ".($this->placeholder_input_labels ? ' placeholder="'.$this->{"area_code_input_label$lang"}.'"' : '') . ($this->area_code_input_width !== 0 ? ' style="width:'.$this->area_code_input_width.'px"' : '')." name='{$this->name}_area_code' id='Inputfield_{$this->name}_area_code' value='{$value->area_code}'/></label>";
 
            $out .= "</div>";
 
        }
 
 
        $out .= "<div class='phone_col'>";
 
        $out .= "<label>".($this->hide_input_labels ? '' : $this->{"number_input_label$lang"} . ' ')."<input type='text' pattern='".$pattern."' class='".$this->input_class."' ".($this->placeholder_input_labels ? ' placeholder="'.$this->{"number_input_label$lang"}.'"' : '') . ($this->number_input_width !== 0 ? ' style="width:'.$this->number_input_width.'px"' : '')."  name='{$this->name}_number' id='Inputfield_{$this->name}_number' value='{$value->number}'/></label>";
 
        $out .= "</div>";
 
 
        if($this->extension_input) {
 
            $out .= "<div class='phone_col'>";
 
            $out .= "<label>".($this->hide_input_labels ? '' : $this->{"extension_input_label$lang"} . ' ')."<input type='text' pattern='".$pattern."' class='".$this->input_class."' ".($this->placeholder_input_labels ? ' placeholder="'.$this->{"extension_input_label$lang"}.'"' : '') . ($this->extension_input_width !== 0 ? ' style="width:'.$this->extension_input_width.'px"' : '')."  name='{$this->name}_extension' id='Inputfield_{$this->name}_extension' value='{$value->extension}'/></label>";
 
            $out .= "</div>";
 
        }
 
 
        if($this->output_format_override_input) {
 
            $out .= "<div class='phone_col'>";
 
            $out .= "<label>".($this->hide_input_labels ? '' : "{$this->_('Format')} ")."<select name='{$this->name}_output_format' id='Inputfield_{$this->name}_output_format'>";
 
            $out .= '<option value="" ' . ($this->output_format == '' ? 'selected' : '') . '>No Override</option>';
 
            $this->fieldtypePhone = $this->wire('modules')->get('FieldtypePhone');
 
            foreach($this->fieldtypePhone->buildOptions(explode("\n",$this->output_format_options), $this->data) as $option) {
 
                $out .= '<option value="'.$option[0].'" ' . (($option[0] == $value->output_format) ? 'selected' : '') . '>'.$option[1].'</option>';
 
            }
 
            $out .= "</select></label>";
 
            $out .= "</div>";
 
        }
 
 
        $out .= '<div style="clear:both; height:0">&nbsp;</div>';
 
 
        return $out;
 
    }
 
 
    /**
 
    * Process the input from the given WireInputData (usually $input->get or $input->post), load and clean the value for use in this Inputfield.
 
    *
 
    * @param WireInputData $input
 
    * @return $this
 
    *
 
    */
 
    public function ___processInput(WireInputData $input) {
 
 
        $this->fieldtypePhone = $this->wire('modules')->get('FieldtypePhone');
 
 
        $name = $this->attr('name');
 
        $value = $this->attr('value');
 
 
        if(is_null($value)) $value = new \Phone;
 
 
        $pn_names = array(
 
            'country' => $name . "_country",
 
            'area_code' => $name . "_area_code",
 
            'number' => $name . "_number",
 
            'extension' => $name . "_extension",
 
            'output_format' => $name . "_output_format"
 
      );
 
 
        // loop all inputs and set them if changed
 
        foreach($pn_names as $key => $name) {
 
            if(isset($input->$name)) {
 
                if($value->$key !== $input->$name) {
 
                    if(!$this->allow_letters_input && !is_numeric($input->$name) && !empty($input->$name) && $key != 'output_format') {
 
                        // in case the input isn't numeric show an error
 
                        $this->wire()->error($this->_("Field only accepts numeric values"));
 
                    }
 
                    elseif($key == 'output_format' || $this->allow_letters_input) {
 
                        $value->set($key, $this->wire('sanitizer')->text($input->$name));
 
                    }
 
                    else {
 
                        $value->set($key, $this->wire('sanitizer')->digits($input->$name));
 
                    }
 
                }
 
            }
 
        }
 
 
        if($value != $this->attr('value')) {
 
            $this->trackChange('value');
 
            // sets formatted value which is needed for Form Builder entries table
 
            $this->setAttribute('value', $this->fieldtypePhone->formatPhone($value->country, $value->area_code, $value->number, $value->extension, $this->fieldtypePhone->getFormatFromName($value->output_format ?: $this->output_format)));
 
        }
 
        return $this;
 
    }
 
 
    /**
 
    * Get any custom configuration fields for this Inputfield
 
    *
 
    * @return InputfieldWrapper
 
    *
 
    */
 
    public function ___getConfigInputfields() {
 
 
        $inputfields = parent::___getConfigInputfields();
 
        $this->fieldtypePhone = $this->wire('modules')->get('FieldtypePhone');
 
        $value = $this->hasField ?: $this;
 
 
        $f = $this->wire('modules')->get('InputfieldCheckbox');
 
        $f->attr('name', 'hide_input_labels');
 
        $f->label = __('Hide input labels', __FILE__);
 
        $f->description = __('Check to hide the component input labels', __FILE__);
 
        $f->columnWidth = 33;
 
        $f->attr('checked', $value->hide_input_labels ? 'checked' : '');
 
        $inputfields->append($f);
 
 
        $f = $this->wire('modules')->get('InputfieldCheckbox');
 
        $f->attr('name', 'placeholder_input_labels');
 
        $f->label = __('Placeholder input labels', __FILE__);
 
        $f->description = __('Check to show the component input labels as the placeholder', __FILE__);
 
        $f->columnWidth = 34;
 
        $f->attr('checked', $value->placeholder_input_labels ? 'checked' : '');
 
        $inputfields->append($f);
 
 
        $f = $this->wire('modules')->get('InputfieldText');
 
        $f->attr('name', 'input_class');
 
        $f->label = __('Input Class', __FILE__);
 
        $f->description = __('Class to add to component inputs.', __FILE__);
 
        $f->notes = __('eg. uk-input', __FILE__);
 
        $f->columnWidth = 33;
 
        $f->value = $value->input_class;
 
        $inputfields->append($f);
 
 
        // country
 
        $f = $this->wire('modules')->get('InputfieldCheckbox');
 
        $f->attr('name', 'country_input');
 
        $f->label = __('Country Code', __FILE__);
 
        $f->attr('checked', $value->country_input ? 'checked' : '');
 
        $f->description = __('Whether to ask for country code when entering phone numbers.', __FILE__);
 
        $f->columnWidth = 33;
 
        $inputfields->append($f);
 
 
        $f = $this->wire('modules')->get('InputfieldText');
 
        $f->attr('name', 'country_input_label');
 
        $f->label = __('Country input label', __FILE__);
 
        $f->attr('size', 100);
 
        $f->description = __('Name of Country input', __FILE__);
 
        $f->notes = __('Default: Ctry', __FILE__);
 
        $f->columnWidth = 34;
 
        $f->value = $value->country_input_label;
 
        if($this->wire('languages')) {
 
            $f->useLanguages = true;
 
            foreach($this->wire('languages') as $language) {
 
                if(!$language->isDefault() && isset($value->data["country_input_label$language"])) $f->set("value$language", $value->data["country_input_label$language"]);
 
            }
 
        }
 
        $inputfields->append($f);
 
 
        $f = $this->wire('modules')->get('InputfieldText');
 
        $f->attr('name', 'country_input_width');
 
        $f->label = __('Country input width', __FILE__);
 
        $f->attr('size', 10);
 
        $f->description = __('Width of the input in pixels.', __FILE__);
 
        $f->notes = __('Default: 60; 0 to not set width', __FILE__);
 
        $f->columnWidth = 33;
 
        $f->value = $value->country_input_width;
 
        $inputfields->append($f);
 
 
 
        // area code
 
        $f = $this->wire('modules')->get('InputfieldCheckbox');
 
        $f->attr('name', 'area_code_input');
 
        $f->label = __('Area Code', __FILE__);
 
        $f->description = __('Whether to ask for area code when entering phone numbers.', __FILE__);
 
        $f->notes = __('If this is unchecked, then area code and number will be store as one in the number field.', __FILE__);
 
        $f->columnWidth = 33;
 
        $f->attr('checked', $value->area_code_input ? 'checked' : '');
 
        $inputfields->append($f);
 
 
        $f = $this->wire('modules')->get('InputfieldText');
 
        $f->attr('name', 'area_code_input_label');
 
        $f->label = __('Area Code input name', __FILE__);
 
        $f->attr('size', 100);
 
        $f->description = __('Name of Area Code input', __FILE__);
 
        $f->notes = __('Default: Area', __FILE__);
 
        $f->columnWidth = 34;
 
        $f->value = $value->area_code_input_label;
 
        if($this->wire('languages')) {
 
            $f->useLanguages = true;
 
            foreach($this->wire('languages') as $language) {
 
                if(!$language->isDefault() && isset($value->data["area_code_input_label$language"])) $f->set("value$language", $value->data["area_code_input_label$language"]);
 
            }
 
        }
 
        $inputfields->append($f);
 
 
        $f = $this->wire('modules')->get('InputfieldText');
 
        $f->attr('name', 'area_code_input_width');
 
        $f->label = __('Area code input width', __FILE__);
 
        $f->attr('size', 10);
 
        $f->description = __('Width of the input in pixels.', __FILE__);
 
        $f->notes = __('Default: 80; 0 to not set width', __FILE__);
 
        $f->columnWidth = 33;
 
        $f->value = $value->area_code_input_width;
 
        $inputfields->append($f);
 
 
        // number
 
        $f = $this->wire('modules')->get('InputfieldText');
 
        $f->attr('name', 'number_input_label');
 
        $f->label = __('Number input name', __FILE__);
 
        $f->attr('size', 100);
 
        $f->description = __('Name of Number input', __FILE__);
 
        $f->notes = __('Default: Num', __FILE__);
 
        $f->columnWidth = 50;
 
        $f->value = $value->number_input_label;
 
        if($this->wire('languages')) {
 
            $f->useLanguages = true;
 
            foreach($this->wire('languages') as $language) {
 
                if(!$language->isDefault() && isset($value->data["number_input_label$language"])) $f->set("value$language", $value->data["number_input_label$language"]);
 
            }
 
        }
 
        $inputfields->append($f);
 
 
        $f = $this->wire('modules')->get('InputfieldText');
 
        $f->attr('name', 'number_input_width');
 
        $f->label = __('Number input width', __FILE__);
 
        $f->attr('size', 10);
 
        $f->description = __('Width of the input in pixels.', __FILE__);
 
        $f->notes = __('Default: 140; 0 to not set width', __FILE__);
 
        $f->columnWidth = 50;
 
        $f->value = $value->number_input_width;
 
        $inputfields->append($f);
 
 
        // extension
 
        $f = $this->wire('modules')->get('InputfieldCheckbox');
 
        $f->attr('name', 'extension_input');
 
        $f->label = __('Extension', __FILE__);
 
        $f->description = __('Whether to ask for extension when entering phone numbers.', __FILE__);
 
        $f->columnWidth = 33;
 
        $f->attr('checked', $value->extension_input ? 'checked' : '');
 
        $inputfields->append($f);
 
 
        $f = $this->wire('modules')->get('InputfieldText');
 
        $f->attr('name', 'extension_input_label');
 
        $f->label = __('Extension input name', __FILE__);
 
        $f->attr('size', 100);
 
        $f->description = __('Name of Extension input', __FILE__);
 
        $f->notes = __('Default: Ext', __FILE__);
 
        $f->columnWidth = 34;
 
        $f->value = $value->extension_input_label;
 
        if($this->wire('languages')) {
 
            $f->useLanguages = true;
 
            foreach($this->wire('languages') as $language) {
 
                if(!$language->isDefault() && isset($value->data["extension_input_label$language"])) $f->set("value$language", $value->data["extension_input_label$language"]);
 
            }
 
        }
 
        $inputfields->append($f);
 
 
        $f = $this->wire('modules')->get('InputfieldText');
 
        $f->attr('name', 'extension_input_width');
 
        $f->label = __('Extension input width', __FILE__);
 
        $f->attr('size', 10);
 
        $f->description = __('Width of the input in pixels.', __FILE__);
 
        $f->notes = __('Default: 80; 0 to not set width', __FILE__);
 
        $f->columnWidth = 33;
 
        $f->value = $value->extension_input_width;
 
        $inputfields->append($f);
 
 
        $f = $this->wire('modules')->get('InputfieldSelect');
 
        $f->attr('name', 'output_format');
 
        $f->label = __('Phone Output Format', __FILE__);
 
        $f->description = __("Select the format to be used when outputting phone numbers for this field.\n\nYou can define new formats for this dropdown select in the phone fieldtype module config settings.", __FILE__);
 
        $f->columnWidth = 66;
 
        $f->addOption('', __('None', __FILE__));
 
        foreach($this->fieldtypePhone->buildOptions(explode("\n", $this->fieldtypePhone->output_format_options), $this->data) as $option) {
 
            $f->addOption($option[0], $option[1]);
 
            if($value->output_format == $option[0]) $f->attr('value', $option[0]);
 
        }
 
        $inputfields->append($f);
 
 
        $f = $this->wire('modules')->get('InputfieldCheckbox');
 
        $f->attr('name', 'allow_letters_input');
 
        $f->label = __('Allow Letters in Input', __FILE__);
 
        $f->description = __('Whether to allow letters when entering phone numbers.', __FILE__);
 
        $f->notes = __('Some businesses use letters to make it easier to remember a number.', __FILE__);
 
        $f->columnWidth = 34;
 
        $f->attr('checked', $value->allow_letters_input ? 'checked' : '');
 
        $inputfields->append($f);
 
 
        $f = $this->wire('modules')->get('InputfieldCheckbox');
 
        $f->attr('name', 'output_format_override_input');
 
        $f->label = __('Output Format Override', __FILE__);
 
        $f->description = __('Whether to give option to override selected output format when entering phone numbers.', __FILE__);
 
        $f->attr('checked', $value->output_format_override_input ? 'checked' : '');
 
        $inputfields->append($f);
 
 
        return $inputfields;
 
    }
 
 
}
 
</syntaxhighlight>
 
FieldtypePhone.module
 
<syntaxhighlight lang="php">
 
<?php
 
 
/**
 
* ProcessWire Phone Fieldtype
 
* by Adrian Jones with code from "Soma" Philipp Urlich's Dimensions Fieldtype module and Ryan's core FieldtypeDatetime module
 
*
 
* Field that stores 4 numeric values for country/area code/number/extension and allows for multiple formatting options.
 
*
 
* ProcessWire 3.x
 
* Copyright (C) 2010 by Ryan Cramer
 
* Licensed under GNU/GPL v2, see LICENSE.TXT
 
*
 
* http://www.processwire.com
 
* http://www.ryancramer.com
 
*
 
*/
 
 
class FieldtypePhone extends Fieldtype implements Module, ConfigurableModule {
 
 
 
    public static function getModuleInfo() {
 
        return array(
 
            'title' => __('Phone', __FILE__),
 
            'summary' => __('Multi part phone field, with custom output formatting options.', __FILE__),
 
            'version' => '3.1.0',
 
            'author' => 'Adrian Jones',
 
            'href' => 'http://modules.processwire.com/modules/fieldtype-phone/',
 
            'installs' => 'InputfieldPhone',
 
            'requiredBy' => 'InputfieldPhone',
 
            'icon' => 'phone'
 
      );
 
    }
 
 
  /**
 
    * Default configuration for module
 
    *
 
    */
 
    static public function getDefaultData() {
 
        return array(
 
            "output_format" => "",
 
            "output_format_options" => '
 
 
/*North America without separate area code*/
 
northAmericaStandardNoSeparateAreaCode | {+[phoneCountry]} {([phoneNumber,0,3])} {[phoneNumber,3,3]}-{[phoneNumber,6,4]} {x[phoneExtension]} | 1,,2215673456,123
 
northAmericaStandardNoSeparateAreaCodeNoNumberDashes | {+[phoneCountry]} {([phoneNumber,0,3])} {[phoneNumber,3,7]} {x[phoneExtension]} | 1,,2215673456,123
 
northAmericaStandardNoSeparateAreaAllDashes | {+[phoneCountry]}-{[phoneNumber,0,3]}-{[phoneNumber,3,3]}-{[phoneNumber,6,4]} {x[phoneExtension]} | 1,,2215673456,123
 
northAmericaStandardNoSeparateAreaDashesNoNumberDashes | {+[phoneCountry]}-{[phoneNumber]} {x[phoneExtension]} | 1,,2215673456,123
 
 
/*North America with separate area code*/
 
northAmericaStandard | {+[phoneCountry]} {([phoneAreaCode])} {[phoneNumber,0,3]}-{[phoneNumber,3,4]} {x[phoneExtension]} | 1,221,5673456,123
 
northAmericaNoNumberDashes | {+[phoneCountry]} {([phoneAreaCode])} {[phoneNumber]} {x[phoneExtension]} | 1,221,5673456,123
 
northAmericaAllDashes| {+[phoneCountry]}-{[phoneAreaCode]}-{[phoneNumber,0,3]}-{[phoneNumber,3,4]} {x[phoneExtension]} | 1,221,5673456,123
 
northAmericaDashesNoNumberDashes | {+[phoneCountry]}-{[phoneAreaCode]}-{[phoneNumber]} {x[phoneExtension]} | 1,221,5673456,123
 
 
/*Australia*/
 
australiaNoCountryAreaCodeLeadingZero | {([phoneAreaCode,0,2])} {[phoneNumber,0,4]} {[phoneNumber,4,4]} {x[phoneExtension]} | 61,07,45673456,123
 
australiaWithCountryAreaCodeNoLeadingZero | {+[phoneCountry]} {([phoneAreaCode,1,1])} {[phoneNumber,0,4]} {[phoneNumber,4,4]} {x[phoneExtension]} | 61,07,45673456,123
 
'
 
        );
 
    }
 
 
    /**
 
    * Data as used by the get/set functions
 
    *
 
    */
 
    protected $data = array();
 
 
    /**
 
    * Populate the default config data
 
    *
 
    */
 
    public static $_data;
 
 
    public function __construct() {
 
        foreach(self::getDefaultData() as $key => $value) {
 
            $this->$key = $value;
 
        }
 
    }
 
 
    /**
 
    * Format the value for output, according to selected format and language
 
    *
 
    */
 
    public function ___formatValue(Page $page, Field $field, $value) {
 
 
        $outputCode = $this->getOutputFormat($value, $field);
 
 
        $value->formattedNumber = $this->formatPhone($value->country, $value->area_code, $value->number, $value->extension, $outputCode);
 
        $value->formattedNumberNoCtryNoExt = $this->formatPhone(null, $value->area_code, $value->number, null, $outputCode);
 
        $value->formattedNumberNoCtry = $this->formatPhone(null, $value->area_code, $value->number, $value->extension, $outputCode);
 
        $value->formattedNumberNoExt = $this->formatPhone($value->country, $value->area_code, $value->number, null, $outputCode);
 
 
        $value->unformattedNumberNoCtryNoExt = ($value->area_code ? $value->area_code : null) . ($value->number ? $value->number : null);
 
        $value->unformattedNumberNoCtry = ($value->area_code ? $value->area_code : null) . ($value->number ? $value->number : null) . ($value->extension ? $value->extension : null);
 
        $value->unformattedNumberNoExt = ($value->country ? $value->country : null) . ($value->area_code ? $value->area_code : null) . ($value->number ? $value->number : null);
 
        $value->unformattedNumber = $value->unformattedNumberNoExt . ($value->extension ? $value->extension : null);
 
 
        foreach(explode("\n",$this->data["output_format_options"]) as $format) {
 
            if(trim(preg_replace('!/\*.*?\*/!s', '', $format)) == '') continue;
 
            $formatParts = explode('|', $format);
 
            $formatName = trim($formatParts[0]);
 
            $formatCode = trim($formatParts[1]);
 
            $value->$formatName = $this->formatPhone($value->country, $value->area_code, $value->number, $value->extension, $formatCode);
 
        }
 
 
        return $value;
 
    }
 
 
    /**
 
    * Format the value for string output, eg in a Lister table
 
    *
 
    */
 
    public function ___markupValue(Page $page, Field $field, $value = null, $property = '') {
 
        if(is_null($value)) return;
 
        $outputCode = $this->getOutputFormat($value, $field);
 
        return $this->formatPhone($value->country, $value->area_code, $value->number, $value->extension, $outputCode);
 
    }
 
 
    /**
 
    *
 
    * Add mapping to different name for use in page selectors
 
    * This enables us to use it like "field.country=61, field.area_code=225, field.number=123456, field.extension=123"
 
    */
 
    public function getMatchQuery($query, $table, $subfield, $operator, $value) {
 
        if($subfield == 'raw') $subfield = 'data';
 
        if($subfield == 'country') $subfield = 'data_country';
 
        if($subfield == 'area_code') $subfield = 'data_area_code';
 
        if($subfield == 'number') $subfield = 'data_number';
 
        if($subfield == 'extension') $subfield = 'data_extension';
 
 
if($this->wire('database')->isOperator($operator)) {
 
// if dealing with something other than address, or operator is native to SQL,
 
// then let Fieldtype::getMatchQuery handle it instead
 
return parent::getMatchQuery($query, $table, $subfield, $operator, $value);
 
}
 
// if we get here, then we're performing either %= (LIKE and variations) or *= (FULLTEXT and variations)
 
$ft = new DatabaseQuerySelectFulltext($query);
 
$ft->match($table, $subfield, $operator, $value);
 
return $query;
 
 
    }
 
 
    /**
 
    * get Inputfield for this fieldtype, set config attributes so they can be used in the inputfield
 
    *
 
    */
 
    public function getInputfield(Page $page, Field $field) {
 
        $pn = $this->wire('modules')->get('InputfieldPhone');
 
        return $pn;
 
    }
 
 
    /**
 
    * there's none compatible
 
    *
 
    */
 
    public function ___getCompatibleFieldtypes(Field $field) {
 
        return null;
 
    }
 
 
    /**
 
    * blank value is an WireData object Phone
 
    *
 
    */
 
    public function getBlankValue(Page $page, Field $field) {
 
        return new Phone($field);
 
    }
 
 
    /**
 
    * Any value will get sanitized before setting it to a page object
 
    * and before saving the data
 
    *
 
    * If value not of instance Phone return empty instance
 
    */
 
    public function sanitizeValue(Page $page, Field $field, $value) {
 
 
        if(!$value instanceof Phone) $value = $this->getBlankValue($page, $field);
 
 
        // report any changes to the field values
 
        if($value->isChanged('country')
 
            || $value->isChanged('area_code')
 
            || $value->isChanged('number')
 
            || $value->isChanged('extension')
 
            || $value->isChanged('output_format')) {
 
                $page->trackChange($field->name);
 
        }
 
        return $value;
 
    }
 
 
    /**
 
    * get values converted when fetched from db
 
    *
 
    */
 
    public function ___wakeupValue(Page $page, Field $field, $value) {
 
 
        // get blank phone number (pn)
 
        $pn = $this->getBlankValue($page, $field);
 
 
        $sanitizerType = $field->allow_letters_input ? 'text' : 'digits';
 
 
        // populate the pn
 
        if(isset($value['data'])) $pn->raw = $this->wire('sanitizer')->$sanitizerType($value['data']);
 
        if(isset($value['data_country'])) $pn->country = $this->wire('sanitizer')->$sanitizerType($value['data_country']);
 
        if(isset($value['data_area_code'])) $pn->area_code = $this->wire('sanitizer')->$sanitizerType($value['data_area_code']);
 
        if(isset($value['data_number'])) $pn->number = $this->wire('sanitizer')->$sanitizerType($value['data_number']);
 
        if(isset($value['data_extension'])) $pn->extension = $this->wire('sanitizer')->$sanitizerType($value['data_extension']);
 
        if(isset($value['data_output_format'])) $pn->output_format = $this->wire('sanitizer')->text($value['data_output_format']);
 
 
        return $pn;
 
    }
 
 
    /**
 
    * return converted from object to array for storing in database
 
    *
 
    */
 
    public function ___sleepValue(Page $page, Field $field, $value) {
 
 
        // throw error if value is not of the right type
 
        if(!$value instanceof Phone)
 
            throw new WireException("Expecting an instance of Phone");
 
 
        $sanitizerType = $field->allow_letters_input ? 'text' : 'digits';
 
 
        $sleepValue = array(
 
            'data' => $this->wire('sanitizer')->$sanitizerType($value->country . $value->area_code . $value->number),
 
            'data_country' => $this->wire('sanitizer')->$sanitizerType($value->country),
 
            'data_area_code' => $this->wire('sanitizer')->$sanitizerType($value->area_code),
 
            'data_number' => $this->wire('sanitizer')->$sanitizerType($value->number),
 
            'data_extension' => $this->wire('sanitizer')->$sanitizerType($value->extension),
 
            'data_output_format' => $this->wire('sanitizer')->text($value->output_format)
 
      );
 
 
        return $sleepValue;
 
    }
 
 
    /**
 
    * Get the database schema for this field
 
    *
 
    * @param Field $field In case it's needed for the schema, but usually should not.
 
    * @return array
 
    */
 
    public function getDatabaseSchema(Field $field) {
 
 
        $schema = parent::getDatabaseSchema($field);
 
        $schema['data'] = 'varchar(15) NOT NULL';
 
        $schema['data_country'] = 'varchar(15) NOT NULL';
 
        $schema['data_area_code'] = 'varchar(15) NOT NULL';
 
        $schema['data_number'] = 'varchar(15) NOT NULL';
 
        $schema['data_extension'] = 'varchar(15) NOT NULL';
 
        $schema['data_output_format'] = 'varchar(255) NOT NULL';
 
        // key for data will already be added from the parent
 
        $schema['keys']['data_country'] = 'KEY data_country(data_country)';
 
        $schema['keys']['data_area_code'] = 'KEY data_area_code(data_area_code)';
 
        $schema['keys']['data_number'] = 'KEY data_number(data_number)';
 
        $schema['keys']['data_extension'] = 'KEY data_extension(data_extension)';
 
        $schema['keys']['data_output_format'] = 'KEY data_output_format(data_output_format)';
 
        return $schema;
 
    }
 
 
    /**
 
    * Get any inputfields used for configuration of this Fieldtype.
 
    *
 
    * This is in addition to any configuration fields supplied by the parent Inputfield.
 
    *
 
    * @param Field $field
 
    * @return InputfieldWrapper
 
    *
 
    */
 
    public function getModuleConfigInputfields(array $data) {
 
 
        foreach(self::getDefaultData() as $key => $value) {
 
            if(!isset($data[$key]) || $data[$key]=='') $data[$key] = $value;
 
        }
 
 
        $inputfields = new InputfieldWrapper();
 
 
        $f = $this->wire('modules')->get('InputfieldSelect');
 
        $f->attr('name', 'output_format');
 
        $f->label = __('Phone Output Format', __FILE__);
 
        $f->description = __("Select the default format to be used when outputting phone numbers.\n\nYou can define new formats for this dropdown select in the 'Phone Output Format Options' field below.", __FILE__);
 
        $f->notes = __("This can be overridden on the Input tab of each 'phone' field.", __FILE__);
 
        $f->addOption('', __('None', __FILE__));
 
        foreach($this->buildOptions(explode("\n",$this->data["output_format_options"]), $this->data) as $option) {
 
            $f->addOption($option[0], $option[1]);
 
            if($this->data["output_format"] == $option[0]) $f->attr('value', $option[0]);
 
        }
 
        $inputfields->add($f);
 
 
        $f = $this->wire('modules')->get("InputfieldTextarea");
 
        $f->attr('name', 'output_format_options');
 
        $f->attr('value', $this->data["output_format_options"]);
 
        $f->attr('rows', 10);
 
        $f->label = __('Phone Output Format Options', __FILE__);
 
        $f->description = __("Any formats listed here will be available from the Phone Output Format selector above, as well as the Format Override selector when entering data for phone number fields.\n\nOne format per line: `name | format | example numbers`\n\nEach component of the phone number is surrounded by { }\nThe names of the component parts are surrounded by [ ]\nTwo optional comma separated numbers after the component name are used to get certain parts of the number using the [PHP substr() function](http://php.net/manual/function.substr.php), allowing for complete flexibility.\nAnything outside the [ ] or { } is used directly: +,-,(,),x, spaces, etc - whatever you want to use.\n\nPlease send me a PR on Github, or post to the support forum any new formats you create that you think others would find useful.", __FILE__);
 
        $inputfields->add($f);
 
 
        return $inputfields;
 
    }
 
 
 
    /**
 
    * Format a phone number with the given number format
 
    *
 
    * @param text $phoneCountry country code
 
    * @param text $phoneAreaCode area code
 
    * @param text $phoneNumber number
 
    * @param text $phoneExtension phone extension
 
    * @param string $format to use for formatting
 
    * @return string Formatted phone string
 
    *
 
    */
 
    public function formatPhone($phoneCountry, $phoneAreaCode, $phoneNumber, $phoneExtension, $format) {
 
 
        if(!$phoneNumber) return '';
 
        if(!strlen($format) || $format == '%s') return ($phoneCountry ? $phoneCountry : null) . ($phoneAreaCode ? $phoneAreaCode : null) . ($phoneNumber ? $phoneNumber : null) . ($phoneExtension ? $phoneExtension : null); // no formatting
 
 
        $pattern = preg_match_all("/{(.*?)}[^{]*/", $format, $components);
 
 
        $finalValue = '';
 
        $lastSuffix = '';
 
        foreach ($components[0] as $component) {
 
 
            $prefix = strstr($component, '[', true);
 
            $suffix = str_replace(']','',strstr($component, ']'));
 
            $component = str_replace(array($prefix, $suffix, '[', ']'), null, $component);
 
 
            if(strcspn($component, '0123456789') != strlen($component)) {
 
                $component_name = strstr($component, ',', true);
 
                $char_cutoffs = explode(',',ltrim(str_replace($component_name, '', $component),','));
 
                $value = trim(substr($$component_name, $char_cutoffs[0], $char_cutoffs[1]));
 
            }
 
            else {
 
                $component_name = $component;
 
                $value = $$component_name;
 
            }
 
            $finalValue .= ($value != '' ? $prefix . $value . $suffix : null);
 
            // if this component has no value, or is not numeric, remove the last suffix
 
            if($value == '' || !is_numeric($value)) $finalValue = rtrim($finalValue, $lastSuffix);
 
            $lastSuffix = str_replace('}', '', $suffix);
 
        }
 
        $finalValue = trim(str_replace(array('{', '}'), null, $finalValue));
 
        return $finalValue;
 
    }
 
 
    public function buildOptions($options, $data) {
 
        $optionsArr = array();
 
        foreach($options as $format) {
 
            if(trim(preg_replace('!/\*.*?\*/!s', '', $format)) == '') continue;
 
            $formatParts = explode('|', $format);
 
            $formatName = trim($formatParts[0]);
 
            $formatCode = trim($formatParts[1]);
 
            $defaultExampleNumbers = array(1,221,5673456,123);
 
            $exampleNumbers = isset($formatParts[2]) ? array_map('trim', explode(',', trim($formatParts[2]))) : $defaultExampleNumbers;
 
            $phoneNumberFormatted = $this->formatPhone(
 
                isset($exampleNumbers[0]) ? $exampleNumbers[0] : $defaultExampleNumbers[0],
 
                isset($exampleNumbers[1]) ? $exampleNumbers[1] : $defaultExampleNumbers[1],
 
                isset($exampleNumbers[2]) ? $exampleNumbers[2] : $defaultExampleNumbers[2],
 
                isset($exampleNumbers[3]) ? $exampleNumbers[3] : $defaultExampleNumbers[3],
 
                $formatCode
 
            );
 
            $optionsArr[] = array($formatName, $formatName . ' | ' . $phoneNumberFormatted);
 
        }
 
        return $optionsArr;
 
    }
 
 
    public function getFormatFromName($formatName) {
 
        foreach(explode("\n",$this->data['output_format_options']) as $format) {
 
            if(trim(preg_replace('!/\*.*?\*/!s', '', $format)) == '') continue;
 
            $formatParts = explode('|', $format);
 
            if(trim($formatParts[0]) == $formatName) {
 
                return trim($formatParts[1]);
 
            }
 
        }
 
    }
 
 
    public function getOutputFormat($value, $field) {
 
        if($value->output_format) {
 
            $output_format = $value->output_format;
 
        }
 
        elseif($field->output_format) {
 
            $output_format = $field->output_format;
 
        }
 
        else {
 
            $output_format = $this->data["output_format"];
 
        }
 
 
        return $this->getFormatFromName($output_format);
 
    }
 
 
}
 
 
 
/**
 
* Helper WireData Class to hold a Phone object
 
*
 
*/
 
class Phone extends WireData {
 
 
    public function __construct($field = null) {
 
        $this->field = $field;
 
        $this->set('country', null);
 
        $this->set('area_code', null);
 
        $this->set('number', null);
 
        $this->set('extension', null);
 
        $this->set('output_format', null);
 
    }
 
 
    public function set($key, $value) {
 
 
        if($key == 'country' || $key == 'area_code' || $key == 'number' || $key == 'extension') {
 
            // if value isn't numeric set it to blank and throw an exception so it can be seen on API usage
 
            if($this->field && !$this->field->allow_letters_input && !is_numeric($value) && !is_null($value) && $value != '') {
 
                $value = $this->$key ? $this->$key : '';
 
                throw new WireException("Phone Object only accepts numbers");
 
            }
 
        }
 
        return parent::set($key, $value);
 
    }
 
 
    public function get($key) {
 
        return parent::get($key);
 
    }
 
 
    public function __toString() {
 
        $number = (string)$this->formattedNumber ? (string)$this->formattedNumber : $this->data['number'];
 
        if(!$number) $number = '';
 
        return $number;
 
    }
 
 
 
}
 
</syntaxhighlight>
 
==== SelectFile Modul von Martijn Geerts ====
 
Besteht i.d.R. aus zwei Teilen:
 
* Fieldtype (FieldtypeSelectFile.module) Ist für das Backend zuständig
 
* Inputfield (InputfieldSelectFile.module) Ist für die Ausgabe im Frontend zuständig (render Methode)
 
FieldtypeSelectFile.module
 
<syntaxhighlight lang="php">
 
<?php
 
 
/**
 
* Fieldtype 'select file' stores a file/folder name selected in the associated
 
* Inputfield.
 
*
 
* ©2019 Martijn Geerts
 
*
 
* ProcessWire 3.x
 
* Copyright (C) 2010 by Ryan Cramer
 
* Licensed under GNU/GPL v2, see LICENSE.TXT
 
*
 
* http://www.processwire.com
 
* http://www.ryancramer.com
 
*
 
*/
 
 
class FieldtypeSelectFile extends FieldtypeText {
 
 
/**
 
* Return an array of module information
 
*
 
* @return array
 
*/
 
public static function getModuleInfo() {
 
return array(
 
'title' => __('Select File'),
 
'version' => 105,
 
'summary' => __('Fieldtype that stores a file or folder.'),
 
'author' => 'Martijn Geerts',
 
'href' => 'https://processwire.com/talk/topic/6377-fieldtypeselectfile-inputfieldselectfile/',
 
'installs' => 'InputfieldSelectFile',
 
);
 
}
 
 
/**
 
* This method is called when all system classes are loaded and ready for API usage
 
*
 
*/
 
public function init() {
 
parent::init();
 
$this->allowTextFormatters(false);
 
$this->addHookBefore('Page::loaded', $this, 'changePageTemplate');
 
}
 
 
/**
 
* Change the page template to render
 
*
 
*/
 
public function changePageTemplate(HookEvent $event) {
 
// Page before loaded
 
$page = $event->object;
 
// Inputfield object
 
$inputfield = $page->fields->get('type=FieldtypeSelectFile');
 
// Is not of type FieldtypeSelectFile
 
if ($inputfield === NULL) return;
 
// If change page template is not set in the Inputfield config
 
if (!$inputfield->template) return;
 
$value = trim($page->get($inputfield->name));
 
// If no value return
 
if (!$value) return;
 
$path = $this->config->paths->templates;
 
$folder = ltrim($inputfield->folderPath, '/') . '/';
 
$filename = $path . $folder . $value;
 
if (!is_file($filename)) return;
 
$page->template->set('filename', $filename);
 
}
 
 
/**
 
* Sanitize value for storage
 
*
 
*/
 
public function sanitizeValue(Page $page, Field $field, $value) {
 
$file = $this->config->paths->templates . trim(trim($field->folderPath, '/')) . '/' . $value;
 
if(is_file($file) || is_dir($file)) return $value;
 
return '';
 
}
 
 
/**
 
* Return new instance of the Inputfield associated with this Fieldtype
 
*
 
* @param Page $page
 
* @param Field $field
 
* @return Inputfield
 
*
 
*/
 
public function getInputfield(Page $page, Field $field) {
 
$inputfield = $this->modules->get('InputfieldSelectFile');
 
$inputfield->set('folderPath', $field->folderPath);
 
$inputfield->set('fileExt', $field->fileExt);
 
$inputfield->set('fileDesc', $field->fileDesc);
 
$inputfield->set('hideFiles', $field->hideFiles);
 
$inputfield->set('hideFolders', $field->hideFolders);
 
$inputfield->set('sort', $field->sort);
 
$inputfield->set('template', $field->template);
 
return $inputfield;
 
}
 
 
/**
 
* Get the inputfield used for configuration of this Fieldtype.
 
*
 
* @param Field $field
 
* @return InputfieldWrapper
 
*
 
*/
 
public function ___getConfigInputfields(Field $field) {
 
$error = false;
 
if ($field->folderPath) {
 
$folder = $this->config->paths->templates . ltrim($field->folderPath, '/');
 
if (!is_dir($folder)) {
 
$path = $this->config->urls->templates . ltrim($field->folderPath, '/');
 
$error = sprintf($this->_("Folder %s doesn't exist."), $path);
 
}
 
}
 
 
$inputfields = parent::___getConfigInputfields($field);
 
 
$f = $this->modules->get('InputfieldText');
 
$f->attr('name', 'folderPath');
 
$f->label = $this->_("The folder containing the files and/or folders.");
 
$f->attr('value', $field->folderPath);
 
$f->description =
 
sprintf($this->_('A relative path relative to the **%s** folder.'), $this->config->urls->templates) .
 
' ' .
 
sprintf($this->_('Leave blank for the **%s** folder.'), $this->config->urls->templates);
 
$f->notes =
 
$this->_("When the files are located in /site/templates/scripts/, type: scripts/");
 
$f->getErrors(true);
 
if ($error) $f->error($error);
 
$inputfields->add($f);
 
 
$f = $this->modules->get('InputfieldCheckbox');
 
$f->attr('name', 'fileExt');
 
$f->label = $this->_("Hide File Extension");
 
$f->attr('autocheck', 1);
 
$f->attr('uncheckedValue', 0);
 
$f->attr('checkedValue', 1);
 
$f->columnWidth = 20;
 
$f->attr('value', $field->fileExt);
 
$inputfields->add($f);
 
 
$f = $this->modules->get('InputfieldCheckbox');
 
$f->attr('name', 'fileDesc');
 
$f->label = $this->_("Hide PHP File Description");
 
$f->attr('autocheck', 1);
 
$f->attr('uncheckedValue', 0);
 
$f->attr('checkedValue', 1);
 
$f->columnWidth = 20;
 
$f->attr('value', $field->fileDesc);
 
$inputfields->add($f);
 
 
$f = $this->modules->get('InputfieldCheckbox');
 
$f->attr('name', 'hideFiles');
 
$f->label = $this->_("Hide Files");
 
$f->attr('autocheck', 1);
 
$f->attr('uncheckedValue', 0);
 
$f->attr('checkedValue', 1);
 
$f->columnWidth = 20;
 
$f->attr('value', $field->hideFiles);
 
$inputfields->add($f);
 
 
$f = $this->modules->get('InputfieldCheckbox');
 
$f->attr('name', 'hideFolders');
 
$f->label = $this->_("Hide Folders");
 
$f->attr('autocheck', 1);
 
$f->attr('uncheckedValue', 0);
 
$f->attr('checkedValue', 1);
 
$f->columnWidth = 20;
 
$f->attr('value', $field->hideFolders);
 
$inputfields->add($f);
 
 
$f = $this->modules->get('InputfieldCheckbox');
 
$f->attr('name', 'sort');
 
$f->label = $this->_("Natural Sort (Select options)");
 
$f->attr('autocheck', 1);
 
$f->attr('uncheckedValue', 0);
 
$f->attr('checkedValue', 1);
 
$f->columnWidth = 20;
 
$f->attr('value', $field->sort);
 
$inputfields->add($f);
 
 
$f = $this->modules->get('InputfieldCheckbox');
 
$f->attr('name', 'template');
 
$f->label = $this->_('Change Page Template');
 
$f->label2 = $this->_('Use selected file as template');
 
$f->description =
 
$this->_('Just before the **Page::loaded** event the selected file is set as template file for the page.') .
 
' ' .
 
$this->_('This setting can only be applied once per a page and folders are exluded from the select inputfield.');
 
$f->attr('autocheck', 1);
 
$f->attr('uncheckedValue', 0);
 
$f->attr('checkedValue', 1);
 
$f->attr('value', $field->template);
 
$inputfields->add($f);
 
 
return $inputfields;
 
}
 
}
 
</syntaxhighlight>
 
 
InputfieldSelectFile.module
 
<syntaxhighlight lang="php">
 
<?php
 
 
/**
 
* Inputfield 'select file' provides a HTML select to select a file or a folder
 
* from disk. Per Inputfield you can set a folder to list from.
 
*
 
* ©2019 Martijn Geerts
 
*
 
* ProcessWire 3.x
 
* Copyright (C) 2010 by Ryan Cramer
 
* Licensed under GNU/GPL v2, see LICENSE.TXT
 
*
 
* http://www.processwire.com
 
* http://www.ryancramer.com
 
*
 
*/
 
 
class InputfieldSelectFile extends InputfieldText {
 
 
/**
 
* Return an array of module information
 
*
 
* @return array
 
*/
 
public static function getModuleInfo() {
 
return array(
 
'title' => 'Select File',
 
'version' => 105,
 
'summary' => __('Inputfield to select a file or a folder.'),
 
'author' => 'Martijn Geerts',
 
'href' => 'https://processwire.com/talk/topic/6377-fieldtypeselectfile-inputfieldselectfile/',
 
'requires' => array(
 
'FieldtypeSelectFile',
 
),
 
);
 
}
 
 
/**
 
* Return the completed output of Inputfield select file
 
*
 
* @return string
 
*
 
*/
 
public function ___render() {
 
 
$array = array();
 
$folder = $this->config->paths->templates . trim(trim($this->folderPath), '/') . "/";
 
$phpFileDescription = false;
 
 
if(!is_dir($folder)) {
 
$this->error($this->_("Path to files is invalid"));
 
} else {
 
$array[] = "<option value=''></option>";
 
$handle = opendir($folder);
 
while (false !== ($entry = readdir($handle))) {
 
 
if (strpos($entry, '.') === 0) continue;
 
if (is_file($folder . $entry) && $this->hideFiles) continue;
 
if (is_dir($folder . $entry) && $this->hideFolders) continue;
 
if (is_dir($folder . $entry) && $this->template) continue;
 
if (is_file($folder . $entry) && $this->fileExt) {
 
$exploded = explode('.', $entry);
 
array_pop($exploded);
 
$label = implode('.', $exploded);
 
} else {
 
$label = $entry;
 
}
 
// pull "Description" comment from php files if it exists (inspired by WordPress "Template Name" comment)
 
if(!$this->fileDesc && pathinfo($entry)['extension'] === 'php') {
 
$phpFileData = implode('', file($folder . $entry));
 
if (preg_match('|Description:(.*)$|mi', $phpFileData, $desc)) {
 
$phpFileDescription = trim(preg_replace('/\s*(?:\*\/|\?>).*/', '', $desc[1]));
 
$label .= " (" . $phpFileDescription . ")";
 
}
 
}
 
 
$selected = $entry == $this->value ? " selected" : '';
 
$array[] = "<option value='" . $entry . "'" . $selected . ">" . $label . "</option>";
 
}
 
closedir($handle);
 
}
 
 
if ($this->sort) natcasesort($array);
 
 
return "<select name='" . $this->name . "'>" . implode('', $array) . "</select>";
 
}
 
 
/**
 
* Get any custom configuration fields for Inputfield select file
 
*
 
* @return InputfieldWrapper
 
*
 
*/
 
public function ___getConfigInputfields() {
 
$inputfields = parent::___getConfigInputfields();
 
 
$f = $inputfields->get('stripTags');
 
if($f) $inputfields->remove($f);
 
 
$f = $inputfields->get('size');
 
if($f) $inputfields->remove($f);
 
 
$f = $inputfields->get('maxlength');
 
if($f) $inputfields->remove($f);
 
 
$f = $inputfields->get('placeholder');
 
if($f) $inputfields->remove($f);
 
 
$f = $inputfields->get('pattern');
 
if($f) $inputfields->remove($f);
 
 
return $inputfields;
 
}
 
}
 
</syntaxhighlight>
 

Version vom 16. Dezember 2019, 20:41 Uhr

Links

https://webdesign.tutsplus.com/tutorials/a-beginners-introduction-to-writing-modules-in-processwire--cms-26862
https://processwire.com/api/ref/module/
https://processwire.com/blog/posts/new-module-configuration-options/
https://processwire.com/talk/topic/778-module-dependencies/
http://somatonic.github.io/Captain-Hook/ // Hook Cheatsheet

Beispiele

Frontend Rendering Module

A bit old but working

<?php

/**
 * FrontEndRender
 *
 * Author: Ben Byford
 *
 * ProcessWire 2.x
 * Copyright (C) 2010 by Ryan Cramer
 * Licensed under GNU/GPL v2, see LICENSE.TXT
 *
 * http://www.processwire.com
 * http://www.ryancramer.com
 *
 */

class FrontEndRender extends WireData implements Module {

	public static function getModuleInfo() {
		return array(
			'title' => 'FrontEndRender',
			'version' => 0.1,
			'summary' => "Outputs html and static variables to frontend",
			'author' => 'Ben Byford',
			'singular' => true,
			'href' => 'https://github.com/benbyford/PW-starter-modules'
		);
	}

	// protected variable only accessable within module
	protected $name = 'Ben';

	/*
	* render function to be called in PW template like this:
	* $FrontEndRender = $modules->getModule('FrontEndRender');
	* echo '<h1>' . $FrontEndRender->render() . '</h1>';
	*
	*/
	public function render(){
		return "Hello " . $this->name;
	}
}
?>

ProcessWire Hello World Modul

Beispiel Modul mit Hooks (liegt immer in der Standardinstallation)

<?php namespace ProcessWire;

/**
 * ProcessWire 'Hello world' demonstration module
 *
 * Demonstrates the Module interface and how to add hooks.
 * 
 * See README file for further links regarding module development.
 * 
 * This file is licensed under the MIT license
 * https://processwire.com/about/license/mit/
 * 
 * ProcessWire 3.x, Copyright 2016 by Ryan Cramer
 * https://processwire.com
 *
 */

class Helloworld extends WireData implements Module {

	/**
	 * getModuleInfo is a module required by all modules to tell ProcessWire about them
	 *
	 * @return array
	 *
	 */
	public static function getModuleInfo() {

		return array(

			// The module's title, typically a little more descriptive than the class name
			'title' => 'Hello World', 

			// version number 
			'version' => 3, 

			// summary is brief description of what this module is
			'summary' => 'An example module used for demonstration purposes.',
			
			// Optional URL to more information about the module
			'href' => 'https://processwire.com',

			// singular=true: indicates that only one instance of the module is allowed.
			// This is usually what you want for modules that attach hooks. 
			'singular' => true, 

			// autoload=true: indicates the module should be started with ProcessWire.
			// This is necessary for any modules that attach runtime hooks, otherwise those
			// hooks won't get attached unless some other code calls the module on it's own.
			// Note that autoload modules are almost always also 'singular' (seen above).
			'autoload' => true, 
		
			// Optional font-awesome icon name, minus the 'fa-' part
			'icon' => 'smile-o', 
			);
	}

	/**
	 * Initialize the module
	 *
	 * ProcessWire calls this when the module is loaded. For 'autoload' modules, this will be called
	 * when ProcessWire's API is ready. As a result, this is a good place to attach hooks. 
	 *
	 */
	public function init() {

		// add a hook after the $pages->save, to issue a notice every time a page is saved
		$this->pages->addHookAfter('save', $this, 'example1'); 

		// add a hook after each page is rendered and modify the output
		$this->addHookAfter('Page::render', $this, 'example2'); 

		// add a 'hello' method to every page that returns "Hello World"
		// use "echo $page->hello();" in your template file to display output
		$this->addHook('Page::hello', $this, 'example3'); 

		// add a 'hello_world' property to every page that returns "Hello [user]"
		// use "echo $page->hello_world;" in your template file to display output
		$this->addHookProperty('Page::hello_world', $this, 'example4'); 
	}

	/**
	 * Example1 hooks into the pages->save method and displays a notice every time a page is saved
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function example1($event) {
		/** @var Page $page */
		$page = $event->arguments[0]; 
		$this->message("Hello World! You saved {$page->path}."); 
	}


	/**
	 * Example2 hooks into every page after it's rendered and adds "Hello World" text at the bottom
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function example2($event) {

		/** @var Page $page */
		$page = $event->object; 

		// don't add this to the admin pages
		if($page->template == 'admin') return;

		// add a "Hello World" paragraph right before the closing body tag
		$event->return = str_replace("</body>", "<p>Hello World!</p></body>", $event->return); 
	}

	/**
	 * Example3 adds a 'hello' method (not property) to every page that simply returns "Hello World"
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function example3($event) {
		$event->return = "Hello World";
	}

	/**
	 * Example 4 adds a 'hello_world' property (not method) to every page that returns "Hello [user]"
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function example4($event) {
		$event->return = "Hello " . $this->user->name; 
	}
	
}

ProcessWire Admin Bereich mit eigener Funktionalität erweitern (Modul)

https://webdesign.tutsplus.com/tutorials/extending-the-processwire-admin-using-custom-modules--cms-26863
<?php
/**
* ProcessSimpleAdminPage
*
* @author Ben Byford
* http://www.benbyford.com
*
* @see http://www.processwire.com
*/

class ProcessSimpleAdminPage extends Process {

    public static function getModuleInfo() {
        return array(
            'title' => 'Process Simple Admin Page',
            'summary' => 'Simple Process module that adds new admin page with',
            'version' => 001,

            // Modules that extend Process may specify a 'page' attribute in the
            // getModuleInfo(), this page will automatically be given the module
            // process when added to teh pagetree.

            // I have exampled but commented out the 'page' settings below
            // so that I can show how one might add a page to install() and
            // uninstall() in this and other modules (that might not extend
            // Process)


        // 	'page' => array(
        // 		'name' => 'site-config',
        // 		'parent' => 'admin',
        // 		'title' => 'Site Config'
        // 	   )
        );
    }

    public function execute() {
        return '
            <h2>Edit the text here in the module</h2>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mattis eros vitae metus sodales eget suscipit purus rhoncus. Proin ultrices gravida dolor, non porttitor enim interdum vitae. Integer feugiat lacinia tincidunt. Nulla laoreet tristique tristique. Sed elementum justo a nisl elementum sit amet accumsan nisi tempor. Nulla quis eros et massa dignissim imperdiet a vitae purus.</p>

            <p>Donec scelerisque pulvinar sem eu lobortis. Maecenas turpis ipsum, tempus dictum pharetra eu, consectetur vitae arcu. Fusce orci mauris, semper at tempus quis, volutpat molestie tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed quam tortor, tincidunt sed semper lacinia, scelerisque dapibus quam. Morbi at nisi luctus lacus auctor ultrices eu eu leo.</p>

            <p>Praesent faucibus purus id felis tincidunt dignissim. Sed sit amet ligula mi, eget semper dui. Proin consectetur gravida massa, nec luctus purus hendrerit in. Etiam volutpat, elit non venenatis suscipit, libero neque consectetur diam, id rutrum magna odio ac ligula. Maecenas sollicitudin congue neque fermentum vestibulum. Morbi nec leo nisi. Donec at nisl odio, et porta ligula.</p>

            <p>Sed quis arcu nisi, ac tempor augue. Praesent non elit libero, a ullamcorper lorem. Curabitur porta odio eu nunc ultricies interdum id nec risus. Donec nibh nibh, porta eget vehicula ac, aliquet eget ante. Phasellus eget lorem eu eros eleifend ultrices. Cras sit amet neque sit amet nibh fringilla cursus ut id mauris. Praesent quis nunc justo, sed suscipit lectus. Phasellus eget ultrices risus. Curabitur eu semper est. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut suscipit, nisl ut imperdiet eleifend, turpis arcu placerat tortor, nec laoreet lacus neque ac tellus. Aenean ac lacus justo, quis ultricies nisi.</p>
            ';
    }
    public function install(){

        // create new page to add to CMS
		$page = new Page();

        // add page attributes
        $page->template = "admin";
        $page->name = "cms-faq";
        $page->title = "CMS FAQ";
        $page->save();

        // set this module as the page process, this allows us to display the above
        $page->process = 'ProcessSimpleAdminPage';

        // get admin page and set as page parent
        $admin = $this->pages->get("id=2");
        $page->parent = $admin;

        // save page
        $page->save();
	}

	public function uninstall(){

        // delete created page
        $page = $this->pages->get("name=cms-faq");
        if(count($page)) $this->pages->delete($page, true);
	}
}

ProcessWire Textformatter Modul

https://webdesign.tutsplus.com/tutorials/extending-the-processwire-admin-using-custom-modules--cms-26863
<?php

/**
 * TextformatterFindReplace
 *
 * Author: Ben Byford
 *
 * ProcessWire 2.x
 * Copyright (C) 2010 by Ryan Cramer
 * Licensed under GNU/GPL v2, see LICENSE.TXT
 *
 * http://www.processwire.com
 * http://www.ryancramer.com
 *
 */

class TextformatterFindReplace extends Textformatter implements Module {

	public static function getModuleInfo() {
		return array(
			'title' => 'TextformatterFindReplace',
			'version' => 0.1,
			'summary' => "Finds and replaces any instance of config input to config output",
			'author' => 'Ben Byford',
			'singular' => true,
			'href' => 'https://github.com/benbyford/PW-starter-modules'
		);
	}


	/**
     * Find and Replace the input string
     *
     * @param string $str The block of text to parse
     *
     * The incoming string is replaced with the formatted version of itself.
	 **/

	public function format(&$str) {
		$find = $this->findStr;
		$str = preg_replace_callback($find, array($this,"replace"), $str);

	}

	// adding three underscores to a function allows other modules to hook it
	public function ___replace($match) {
		return $this->replaceStr;
	}
}
?>

Admin Funktionalität: Countdown zum Seitenveröffentlichen

https://webdesign.tutsplus.com/tutorials/extending-the-processwire-admin-using-custom-modules--cms-26863

The module PageDeferredPublish, on clicking one of the Publish Later buttons, sets LazyCron to check that page’s countdown every minute and publishes the page when its countdown reaches 0. This means I can publish a page approximately 24 hours in advance (obviously the checking interval and delay time can be changed to your requirements).

I did this by:

Creating two fields within my install() function: a checkbox field to set to true when a button is checked to indicate the page should countdown, and a countdown field to store the count in seconds for that specific page. Adding hooks to both ProcessPageEdit::buildForm and ProcessPageListActions::getExtraActions enabling me to add the two buttons. Checking to see if one of the buttons was clicked with my ready() function then setting the corresponding page’s checkbox to true. Using a LazyCron hook function to check all pages that are unpublished for true checkboxes and then comparing the seconds field to see if the page needs publishing. If not then deduct the elapsed time in seconds. The result is a module that has settings (the time interval to check and publish at a later time), hooks into the admin using buttons and fields, and enables us to use other modules installed in PW (i.e. LazyCron).

<?php
/**
 * DeferredPublish (0.0.1)
 * DeferredPublish publishes a page after a defined amount of time has elapsed using lazycron.
 *
 * @author Ben Byford
 * http://www.benbyford.com
 *
 * ProcessWire 2.x
 * Copyright (C) 2011 by Ryan Cramer
 * Licensed under GNU/GPL v2, see LICENSE.TXT
 *
 * http://www.processwire.com
 * http://www.ryancramer.com
 *
 */

class PageDeferredPublish extends WireData implements Module {

	public static function getModuleInfo() {
		return array(
			'title' => "PageDeferredPublish",
			'version' => "0.0.1",
			'summary' => "PageDeferredPublish",
			'author' => "Ben Byford",
			'href' => "https://github.com/benbyford/PW-intermediate-modules",
			'icon' => "clock-o",
			'autoload' => true,
			'singular' => true,
			'requires' => "LazyCron",
		);
	}

	private $postPubLater 	= null;
	private $pageID 		= null;
	private $pagePubLater 	= null;
	private $currentPage	= null;

	private $submitName 	= 'pdp_pub_later_submit';
	private $listPageName	= 'pdp_pub_later_list';
	private $fieldName 		= 'pdp_pub_later';
	private $checkboxName	= 'pdp_pub_later_check';

	private $defaultInterval = 'everyMinute';
	private $defaultTime 	= 86400;


	public function install(){
		// add new fields needed for module
		$this->installFields();
	}
	public function uninstall(){
		// uninstall fields
		$this->uninstallFields();
	}

	// initialize the hook in your AutoLoad module
	public function init() {

		// get defaults from module setting in CMS
		$this->defaultTime = $this->pub_after;
		$this->defaultInterval = $this->cron_check;

		// get admin URL
		$this->adminUrl = $this->wire('config')->urls->admin;

		// add hooks to CRON, PageList, PageEdit
	    $this->addHookAfter("LazyCron::{$this->defaultInterval}", $this, 'publishDefferedPages');
		$this->addHook("ProcessPageListActions::getExtraActions", $this, 'hookPageListActions');
		$this->addHookAfter("ProcessPageEdit::buildForm", $this, "editForm");
	}

	public function ready() {

		// if list button clicked then grab id
		$this->pagePubLater = $this->input->get($this->listPageName);
		$this->pageID = $this->input->id;

		// get page
		$this->currentPage = $this->pages->get($this->pageID);

		// if pagelist pub later submit button clicked
		if($this->pagePubLater){
			$this->message("Page {$this->currentPage->name} deffered for publish.");
		}

		// if page edit submit button clicked
		$this->postPubLater = $this->input->post($this->submitName);
		if($this->postPubLater){
			$this->message("Page {$this->currentPage->name} deffered for publish.");
		}

		// if either deffered publish sumbit found then publish page later
		if($this->pagePubLater || $this->postPubLater){
			$this->publishLater();
		}
	}

	/*
	*	Hook: ProcessPageEdit::buildForm
	*
	*	add Publish Later button to edit page if not yet published
	*/
	public function ___editForm(HookEvent $form) {

		// get the InputFieldForm object from the event (return value of buildForm())
		$form = $form->return;

		$page = $this->pages->get($this->input->get->id);
		$check = $page->get($this->checkboxName);

		// check if publish button available and therfore unpublished
		$target = $form->get('submit_publish');

		if($target && $check == false){

			// get InputfieldText module
			$submit2 = $this->modules->get('InputfieldSubmit');
			$submit2->attr('name', $this->submitName);
			$submit2->attr('id', 'publish_later');
			$submit2->attr('class', 'ui-button ui-widget ui-corner-all head_button_clone ui-state-default ui-priority-secondary');
			$submit2->attr('value', 'Publish Later'); // Button: save unpublished

			// get form element save and place before
			$target = $form->get('submit_save');
			$form->insertBefore($submit2, $target);

			$form->return = $form;
		}
	}

	public function ___hookPageListActions(HookEvent $event) {

		// get current page
		$page = $event->arguments[0];

		// check to see if page is published
		$pagePub = $page->is(Page::statusUnpublished);

		// check to see if page template has deffered field
		$pageHasDefferField = $page->get($this->fieldName);

		$actions = array();

		// don't get homepage or pages that are already published or are being deffered for publish
		if($page->id > 1 && $pagePub == "published" && !is_null($pageHasDefferField) && $page->get($this->checkboxName) == false) {

			$actions['publish_later'] = array(
				'cn'   => 'PublishLater',
				'name' => 'pub later',
				'url'  => "{$this->adminUrl}?{$this->listPageName}=1&id={$page->id}",
			);
		}
		if(count($actions)) $event->return = $actions + $event->return;
	}

	/*
	* Publish Page on cron job
	*
	* Gets deffered page and publishes
	*/
	public function ___publish($page) {
		$page->removeStatus(Page::statusUnpublished);
		$page->removeStatus(Page::statusHidden);
		$page->Save();
	}

	/*
	* Main publish later function
	*
	* Gets deffered page and sets fields to be checked be lazy cron
	*/
	private function ___publishLater() {

		// get page
		$page = $this->pages->get($this->pageID);

		// set output formatting to false
		$page->of(false);

		//  set field time to settings default time and checkbox to true
		$page->set($this->checkboxName, true);
		$page->set($this->fieldName, $this->defaultTime);

		// save page
		$page->save();
		$page->of(true);
	}

	/*
	* Lazy Cron hook function
	*
	* Triggers every [set time interval] and checks pages
	* Publishes page if time runout
	*
	* Adds publish page log
	*/
	public function ___publishDefferedPages(HookEvent $e){

		// seconds since last lazycron
		$seconds = $e->arguments[0];

		// find all pages with deffered field
		$defferedPages = $this->pages->find("{$this->checkboxName}=1,include=unpublished");

		// for each page decrease time for deffered field
		foreach ($defferedPages as $page) {

			// get current page time
			$timeTillPublish = $page->get($this->fieldName);

			// set time to time minus time past
			$timeLeft = $timeTillPublish - $seconds;

			// if time passed 0 or less then publish page
			$page->of(false);
			if($timeLeft <= 0){
				// remove flags and save
				$this->publish($page);

				$page->set($this->fieldName, 0);
				$page->set($this->checkboxName, 0);

				// log a page has been published
				$log = wire('log');
				$log->message('CRON:'. $seconds .' Pages: '. $page->name .' published');
			}else{
				$page->set($this->fieldName, $timeLeft);
			}
			// save page time
			$page->Save();
			$page->of(true);
		}
	}

	/*
	* Install new module specific fields
	*/
	private function installFields(){

		// install pub later checkbox field
		// find installed fields
		$f = $this->fields->get($this->fieldName);
		if($f){

			// if field already found then don't try and make it
			$this->message($this->fieldName . ' field found');

		}else{

			// create new field object to store crontime
			$f = new Field();
			// get a field type
			$f->type = $this->modules->get("FieldtypeInteger");
			$f->name = $this->fieldName;
			$f->label = 'Publish Time Left';
			$f->defaultValue = $this->defaultTime;
			$f->icon = 'clock-o';
			$f->columnWidth = 50;
			$f->collapsed = 1;
			$f->flags = 4;

			$f->save(); // save the field

			// create new field object to store whether to publish or not
			$f = new Field();
			// get a field type
			$f->type = $this->modules->get("FieldtypeCheckbox");
			$f->name = $this->checkboxName;
			$f->label = 'Publish Page later';
			$f->defaultValue = false;
			$f->columnWidth = 50;
			$f->icon = 'clock-o';
			$f->collapsed = 1;
			$f->flags = 4;

			$f->save(); // save the field
		}
	}

	/*
	* Uninstall module specific fields
	*/
	private function uninstallFields(){

		// find installed fields
		$fInt = $this->fields->get($this->fieldName);
		$fCheck = $this->fields->get($this->checkboxName);

		if($fInt && $fCheck){
			$fieldIntUsed = $fInt->numFieldgroups();
			$fieldCheckUsed = $fCheck->numFieldgroups();

			if($fieldIntUsed){
				// field in use by template
				$this->message('Unable to uninstall field '.$fInt->name);
			}else{
				// delete installed fields
				$this->fields->delete($fInt);
				$this->fields->save;
			}

			if($fieldCheckUsed){
				// field in use by template
				$this->message('Unable to uninstall field '.$fCheck->name);
			}else{
				// delete installed fields
				$this->fields->delete($fCheck);
				$this->fields->save;
			}
		}
	}
}

Inputfield / Fieldtype

ProcessWire - Fieldtype erstellen (Module)