Skip to content
Snippets Groups Projects
class.forms.php 131 KiB
Newer Older
Jared Hancock's avatar
Jared Hancock committed
<?php
/*********************************************************************
    class.forms.php

    osTicket forms framework

    Jared Hancock <jared@osticket.com>
    Copyright (c)  2006-2013 osTicket
    http://www.osticket.com

    Released under the GNU General Public License WITHOUT ANY WARRANTY.
    See LICENSE.TXT for details.

    vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/

/**
 * Form template, used for designing the custom form and for entering custom
 * data for a ticket
 */
class Form {
    static $renderer = 'GridFluidLayout';
    static $id = 0;

    var $options = array();
Jared Hancock's avatar
Jared Hancock committed
    var $fields = array();
    var $title = '';
Jared Hancock's avatar
Jared Hancock committed
    var $instructions = '';

    var $validators = array();

    var $_errors = null;
    var $_source = false;
Peter Rotich's avatar
Peter Rotich committed
    function __construct($source=null, $options=array()) {

        $this->options = $options;
        if (isset($options['title']))
            $this->title = $options['title'];
        if (isset($options['instructions']))
            $this->instructions = $options['instructions'];
        if (isset($options['id']))
            $this->id = $options['id'];

        // Use POST data if source was not specified
        $this->_source = ($source) ? $source : $_POST;

    function getId() {
        return static::$id;
    }

    function data($source) {
        foreach ($this->fields as $name=>$f)
            if (isset($source[$name]))
                $f->value = $source[$name];
    }
    function setFields($fields) {

        if (!is_array($fields) && !$fields instanceof Traversable)
            return;

        $this->fields = $fields;
        foreach ($fields as $k=>$f) {
            $f->setForm($this);
Peter Rotich's avatar
Peter Rotich committed
            if (!$f->get('name') && $k && !is_numeric($k))
                $f->set('name', $k);
        }
    }

Jared Hancock's avatar
Jared Hancock committed
    function getFields() {
        return $this->fields;
    }

    function getField($name) {
        $fields = $this->getFields();
        foreach($fields as $f)
            if(!strcasecmp($f->get('name'), $name))
                return $f;
        if (isset($fields[$name]))
            return $fields[$name];
    function hasField($name) {
        return $this->getField($name);
    }

Jared Hancock's avatar
Jared Hancock committed
    function getTitle() { return $this->title; }
    function getInstructions() { return $this->instructions; }
    function getSource() { return $this->_source; }
    function setSource($source) { $this->_source = $source; }
    /**
     * Validate the form and indicate if there no errors.
     *
     * Parameters:
     * $filter - (callback) function to receive each field and return
     *      boolean true if the field's errors are significant
     */
    function isValid($include=false) {
        if (!isset($this->_errors)) {
            $this->_errors = array();
            $this->validate($this->getClean());
            foreach ($this->getFields() as $field)
                if ($field->errors() && (!$include || $include($field)))
                    $this->_errors[$field->get('id')] = $field->errors();
        }
        return !$this->_errors;
    }

    function validate($clean_data) {
        // Validate the whole form so that errors can be added to the
        // individual fields and collected below.
        foreach ($this->validators as $V) {
            $V($this);
        }
    }

    function getClean() {
        if (!$this->_clean) {
            $this->_clean = array();
            foreach ($this->getFields() as $key=>$field) {
                if (!$field->hasData())
Jared Hancock's avatar
Jared Hancock committed
                    continue;
                // Prefer indexing by field.id if indexing numerically
                if (is_int($key) && $field->get('id'))
                    $key = $field->get('id');
                $this->_clean[$key] = $this->_clean[$field->get('name')]
                    = $field->getClean();
            }
            unset($this->_clean[""]);
    function errors($formOnly=false) {
        return ($formOnly) ? $this->_errors['form'] : $this->_errors;
    }

    function addError($message, $index=false) {

        if ($index)
            $this->_errors[$index] = $message;
        else
            $this->_errors['form'][] = $message;
    }

    function addErrors($errors=array()) {
        foreach ($errors as $k => $v) {
            if (($f=$this->getField($k)))
                $f->addError($v);
            else
                $this->addError($v, $k);
        }
    }

    function addValidator($function) {
        if (!is_callable($function))
            throw new Exception('Form validator must be callable');
        $this->validators[] = $function;
    function render($staff=true, $title=false, $options=array()) {
        if ($title)
            $this->title = $title;
        if (isset($options['instructions']))
            $this->instructions = $options['instructions'];
        $template = $options['template'] ?: 'dynamic-form.tmpl.php';
            include(STAFFINC_DIR . 'templates/' . $template);
            include(CLIENTINC_DIR . 'templates/' . $template);
        echo $this->getMedia();
Peter Rotich's avatar
Peter Rotich committed
    function getLayout($title=false, $options=array()) {
        $rc = @$options['renderer'] ?: static::$renderer;
        return new $rc($title, $options);
    }

Peter Rotich's avatar
Peter Rotich committed
    function asTable($title=false, $options=array()) {
        return $this->getLayout($title, $options)->asTable($this);
        // XXX: Media can't go in a table
        echo $this->getMedia();
    }

    function getMedia() {
        static $dedup = array();

        foreach ($this->getFields() as $f) {
            if (($M = $f->getMedia()) && is_array($M)) {
                foreach ($M as $type=>$files) {
                    foreach ($files as $url) {
                        $key = strtolower($type.$url);
                        if (isset($dedup[$key]))
                            continue;

                        self::emitMedia($url, $type);
    function emitJavascript($options=array()) {

        // Check if we need to emit javascript
        if (!($fid=$this->getId()))
            return;
        ?>
        <script type="text/javascript">
          $(function() {
            <?php
            //XXX: We ONLY want to watch field on this form. We'll only
            // watch form inputs if form_id is specified. Current FORM API
            // doesn't generate the entire form  (just fields)
            if ($fid) {
                ?>
                $(document).off('change.<?php echo $fid; ?>');
                $(document).on('change.<?php echo $fid; ?>',
                    'form#<?php echo $fid; ?> :input',
                    function() {
                        //Clear any current errors...
                        var errors = $('#field'+$(this).attr('id')+'_error');
                        if (errors.length)
                            errors.slideUp('fast', function (){
                                $(this).remove();
                                });
                        //TODO: Validation input inplace or via ajax call
                        // and set any new errors AND visibilty changes
                    }
                   );
            <?php
            }
            ?>
            });
        </script>
        <?php
    }

    static function emitMedia($url, $type) {
        if ($url[0] == '/')
            $url = ROOT_PATH . substr($url, 1);

        switch (strtolower($type)) {
        case 'css': ?>
        <link rel="stylesheet" type="text/css" href="<?php echo $url; ?>"/><?php
            break;
        case 'js': ?>
        <script type="text/javascript" src="<?php echo $url; ?>"></script><?php
            break;
        }
    }

    /**
     * getState
     *
     * Retrieves an array of information which can be passed to the
     * ::loadState method later to recreate the current state of the form
     * fields and values.
     */
    function getState() {
        $info = array();
        foreach ($this->getFields() as $f) {
            // Skip invisible fields
            if (!$f->isVisible())
                continue;

            // Skip fields set to default values
            $v = $f->getClean();
            $d = $f->get('default');
            if ($v == $d)
                continue;

            // Skip empty values
            if (!$v)
                continue;

            $info[$f->get('name') ?: $f->get('id')] = $f->to_database($v);
        }
        return $info;
    }

    /**
     * loadState
     *
     * Reset this form to the state previously recorded by the ::getState()
     * method
     */
    function loadState($state) {
        foreach ($this->getFields() as $f) {
            $name = $f->get('name');
            $f->reset();
            if (isset($state[$name])) {
                $f->value = $f->to_php($state[$name]);
            }
        }
    }

    /*
     * Initialize a generic static form
     */
    static function instantiate() {
        $r = new ReflectionClass(get_called_class());
        return $r->newInstanceArgs(func_get_args());
    }
}

/**
 * SimpleForm
 * Wrapper for inline/static forms.
 *
 */
class SimpleForm extends Form {
    function __construct($fields=array(), $source=null, $options=array()) {
        parent::__construct($source, $options);
        $this->setFields($fields);
    }
class CustomForm extends SimpleForm {

    function getFields() {
Peter Rotich's avatar
Peter Rotich committed
        global $thisstaff, $thisclient;

        $options = $this->options;
Peter Rotich's avatar
Peter Rotich committed
        $user = $options['user'] ?: $thisstaff ?: $thisclient;
        $isedit = ($options['mode'] == 'edit');
        $fields = array();
        foreach (parent::getFields() as $field) {
            if ($isedit && !$field->isEditable($user))
                continue;

            $fields[] = $field;
        }

        return $fields;
    }
}

Jared Hancock's avatar
Jared Hancock committed
abstract class AbstractForm extends Form {
    function __construct($source=null, $options=array()) {
        parent::__construct($source, $options);
        $this->setFields($this->buildFields());
    }
    /**
     * Fetch the fields defined for this form. This method is only called
     * once.
     */
    abstract function buildFields();
/**
 * Container class to represent the connection between the form fields and the
 * rendered state of the form.
 */
interface FormRenderer {
    // Render the form fields into a table
    function asTable($form);
    // Render the form fields into divs
    function asBlock($form);
}

abstract class FormLayout {
    static $default_cell_layout = 'Cell';

Peter Rotich's avatar
Peter Rotich committed
    var $title;
    var $options;

    function __construct($title=false, $options=array()) {
        $this->title = $title;
        $this->options = $options;
    }

    function getLayout($field) {
        $layout = $field->get('layout') ?: static::$default_cell_layout;
        if (is_string($layout))
            $layout = new $layout();
        return $layout;
    }
}

class GridFluidLayout
extends FormLayout
implements FormRenderer {
    function asTable($form) {
      ob_start();
?>
      <table class="<?php echo 'grid form' ?>">
Peter Rotich's avatar
Peter Rotich committed
          <caption><?php echo Format::htmlchars($this->title ?: $form->getTitle()); ?>
                  <div><small><?php echo Format::viewableImages($form->getInstructions()); ?></small></div>
          </caption>
          <tbody><tr><?php for ($i=0; $i<12; $i++) echo '<td style="width:8.3333%"/>'; ?></tr></tbody>
<?php
      $row_size = 12;
      $cols = $row = 0;
Peter Rotich's avatar
Peter Rotich committed

      //Layout and rendering options
      $options = $this->options;

      foreach ($form->getFields() as $f) {
          $layout = $this->getLayout($f);
          $size = $layout->getWidth() ?: 12;
          if ($offs = $layout->getOffset()) {
              $size += $offs;
          }
          if ($cols < $size || $layout->isBreakForced()) {
              if ($row) echo '</tr>';
              echo '<tr>';
              $cols = $row_size;
              $row++;
          }
          // Render the cell
          $cols -= $size;
          $attrs = array('colspan' => $size, 'rowspan' => $layout->getHeight(),
              'style' => '"'.$layout->getOption('style').'"');
          if ($offs) { ?>
Peter Rotich's avatar
Peter Rotich committed
              <td colspan="<?php echo $offs; ?>"></td> <?php
          }
          ?>
          <td class="cell" <?php echo Format::array_implode('=', ' ', array_filter($attrs)); ?>
              data-field-id="<?php echo $f->get('id'); ?>">
              <fieldset class="field <?php if (!$f->isVisible()) echo 'hidden'; ?>"
                id="field<?php echo $f->getWidget()->id; ?>"
                data-field-id="<?php echo $f->get('id'); ?>">
<?php         if ($label = $f->get('label')) { ?>
              <label class="<?php if ($f->isRequired()) echo 'required'; ?>"
                  for="<?php echo $f->getWidget()->id; ?>">
                  <?php echo Format::htmlchars($label); ?>:
Peter Rotich's avatar
Peter Rotich committed
                <?php if ($f->isRequired()) { ?>
                <span class="error">*</span>
                <?php
                }?>
              </label>
<?php         }
              if ($f->get('hint')) { ?>
                  <div class="field-hint-text">
                      <?php echo Format::htmlchars($f->get('hint')); ?>
                  </div>
<?php         }
Peter Rotich's avatar
Peter Rotich committed
              $f->render($options);
              if ($f->errors())
                  foreach ($f->errors() as $e)
                      echo sprintf('<div class="error">%s</div>', Format::htmlchars($e));
?>
              </fieldset>
          </td>
      <?php
      }
      if ($row)
        echo  '</tr>';

      echo '</tbody></table>';

      return ob_get_clean();
    }

    function asBlock($form) {}
}

/**
 * Basic container for field and form layouts. By default every cell takes
 * a whole output row and does not imply any sort of width.
 */
class Cell {
    function isBreakForced()  { return true; }
    function getWidth()       { return false; }
    function getHeight()      { return 1; }
    function getOffset()      { return 0; }
    function getOption($prop) { return false; }
}

/**
 * Fluid grid layout, meaning each cell renders to the right of the previous
 * cell (for left-to-right layouts). A width in columns can be specified for
 * each cell along with an offset from the previous cell. A height of columns
 * along with an optional break is supported.
 */
class GridFluidCell
extends Cell {
    var $span;
    var $options;

    function __construct($span, $options=array()) {
        $this->span = $span;
        $this->options = $options + array(
            'rows' => 1,        # rowspan
            'offset' => 0,      # skip some columns
            'break' => false,   # start on a new row
        );
    }

    function isBreakForced()  { return $this->options['break']; }
    function getWidth()       { return $this->span; }
    function getHeight()      { return $this->options['rows']; }
    function getOffset()      { return $this->options['offset']; }
    function getOption($prop) { return $this->options[$prop]; }
}

Jared Hancock's avatar
Jared Hancock committed
require_once(INCLUDE_DIR . "class.json.php");

class FormField {
Jared Hancock's avatar
Jared Hancock committed
    var $ht = array(
        'label' => false,
Jared Hancock's avatar
Jared Hancock committed
        'required' => false,
        'default' => false,
        'configuration' => array(),
    );

Jared Hancock's avatar
Jared Hancock committed
    var $_cform;
    var $_clean;
    var $_errors = array();
    var $answer;
    var $parent;
    var $presentation_only = false;
Jared Hancock's avatar
Jared Hancock committed

    static $types = array(
        /* @trans */ 'Basic Fields' => array(
            'text'  => array(   /* @trans */ 'Short Answer', 'TextboxField'),
            'memo' => array(    /* @trans */ 'Long Answer', 'TextareaField'),
            'thread' => array(  /* @trans */ 'Thread Entry', 'ThreadEntryField', false),
            'datetime' => array(/* @trans */ 'Date and Time', 'DatetimeField'),
            'phone' => array(   /* @trans */ 'Phone Number', 'PhoneField'),
            'bool' => array(    /* @trans */ 'Checkbox', 'BooleanField'),
            'choices' => array( /* @trans */ 'Choices', 'ChoiceField'),
            'files' => array(   /* @trans */ 'File Upload', 'FileUploadField'),
            'break' => array(   /* @trans */ 'Section Break', 'SectionBreakField'),
            'info' => array(    /* @trans */ 'Information', 'FreeTextField'),
Jared Hancock's avatar
Jared Hancock committed
    );
    static $more_types = array();
    static $uid = null;
Jared Hancock's avatar
Jared Hancock committed

    function __construct($options=array()) {
        $this->ht = array_merge($this->ht, $options);
        if (!isset($this->ht['id']))
            $this->ht['id'] = self::$uid++;
    }

    function __clone() {
        $this->_widget = null;
        $this->ht['id'] = self::$uid++;
    static function addFieldTypes($group, $callable) {
        static::$more_types[$group][] = $callable;
Jared Hancock's avatar
Jared Hancock committed
    }

    static function allTypes() {
        if (static::$more_types) {
            foreach (static::$more_types as $group => $entries)
                foreach ($entries as $c)
                    static::$types[$group] = array_merge(
                            static::$types[$group] ?: array(), call_user_func($c));

Jared Hancock's avatar
Jared Hancock committed
            static::$more_types = array();
        }
        return static::$types;
    }

    static function getFieldType($type) {
        foreach (static::allTypes() as $group=>$types)
            if (isset($types[$type]))
                return $types[$type];
    }

    function get($what, $default=null) {
        return array_key_exists($what, $this->ht)
            ? $this->ht[$what]
            : $default;
    function set($field, $value) {
        $this->ht[$field] = $value;
    }
    function getId() {
        return $this->ht['id'];
    }

Jared Hancock's avatar
Jared Hancock committed
    /**
     * getClean
     *
     * Validates and cleans inputs from POST request. This is performed on a
     * field instance, after a DynamicFormSet / DynamicFormSection is
     * submitted via POST, in order to kick off parsing and validation of
     * user-entered data.
     */
    function getClean() {
        if (!isset($this->_clean)) {
            $this->_clean = (isset($this->value))
                // XXX: The widget value may be parsed already if this is
                //      linked to dynamic data via ::getAnswer()
                ? $this->value : $this->parse($this->getWidget()->value);
            if ($vs = $this->get('cleaners')) {
                if (is_array($vs)) {
                    foreach ($vs as $cleaner)
                        if (is_callable($cleaner))
                            $this->_clean = call_user_func_array(
                                    $cleaner, array($this, $this->_clean));
                }
                elseif (is_callable($vs))
                    $this->_clean = call_user_func_array(
                            $vs, array($this, $this->_clean));
            if (!isset($this->_clean) && ($d = $this->get('default')))
                $this->_clean = $d;

            if ($this->isVisible())
                $this->validateEntry($this->_clean);
        }
        return $this->_clean;
    function reset() {
        $this->value = $this->_clean = $this->_widget = null;
    function getValue() {
        return $this->getWidget()->getValue();
    }

Jared Hancock's avatar
Jared Hancock committed
    function errors() {
        return $this->_errors;
    function addError($message, $index=false) {
            $this->_errors[$index] = $message;
        else
            $this->_errors[] = $message;

        // Update parent form errors for the field
        if ($this->_form)
            $this->_form->addError($this->errors(), $this->get('id'));
Jared Hancock's avatar
Jared Hancock committed

    function isValidEntry() {
        $this->validateEntry();
        return count($this->_errors) == 0;
    }

    /**
     * validateEntry
     *
     * Validates user entry on an instance of the field on a dynamic form.
     * This is called when an instance of this field (like a TextboxField)
     * receives data from the user and that value should be validated.
     *
     * Parameters:
     * $value - (string) input from the user
     */
    function validateEntry($value) {
        if (!$value && count($this->_errors))
            return;

Jared Hancock's avatar
Jared Hancock committed
        # Validates a user-input into an instance of this field on a dynamic
        # form
        if ($this->get('required') && !$value && $this->hasData())
            $this->_errors[] = $this->getLabel()
                ? sprintf(__('%s is a required field'), $this->getLabel())
                : __('This is a required field');

        # Perform declared validators for the field
        if ($vs = $this->get('validators')) {
            if (is_array($vs)) {
                foreach ($vs as $validator)
                    if (is_callable($validator))
                        $validator($this, $value);
            }
            elseif (is_callable($vs))
                $vs($this, $value);
        }
    /**
     * isVisible
     *
     * If this field has visibility configuration, then it will parse the
     * constraints with the visibility configuration to determine if the
     * field is visible and should be considered for validation
     */
    function isVisible() {
        if ($this->get('visibility') instanceof VisibilityConstraint) {
            return $this->get('visibility')->isVisible($this);
        }
        return true;
    }

     * Check if the user has edit rights
    function isEditable($user=null) {

Peter Rotich's avatar
Peter Rotich committed
        if ($user instanceof Staff)
            $flag = DynamicFormField::FLAG_AGENT_EDIT;
Peter Rotich's avatar
Peter Rotich committed
        else
            $flag = DynamicFormField::FLAG_CLIENT_EDIT;

        return (($this->get('flags') & $flag) != 0);
    /**
     * isStorable
     *
     * Indicate if this field data is storable locally (default).Some field's data
     * might beed to be stored elsewhere for optimization reasons at the
     * application level.
     *
     */

    function isStorable() {
        return (($this->get('flags') & DynamicFormField::FLAG_EXT_STORED) == 0);
    function isRequired() {
        return $this->get('required');
    }

Jared Hancock's avatar
Jared Hancock committed
    /**
     * parse
     *
     * Used to transform user-submitted data to a PHP value. This value is
     * not yet considered valid. The ::validateEntry() method will be called
     * on the value to determine if the entry is valid. Therefore, if the
     * data is clearly invalid, return something like NULL that can easily
     * be deemed invalid in ::validateEntry(), however, can still produce a
     * useful error message indicating what is wrong with the input.
     */
    function parse($value) {
        return is_string($value) ? trim($value) : $value;
Jared Hancock's avatar
Jared Hancock committed
    }

    /**
     * to_php
     *
     * Transforms the data from the value stored in the database to a PHP
     * value. The ::to_database() method is used to produce the database
     * valse, so this method is the compliment to ::to_database().
     *
     * Parameters:
     * $value - (string or null) database representation of the field's
     *      content
     */
    function to_php($value) {
        return $value;
    }

    /**
     * to_config
     *
     * Transform the data from the value to config form (as determined by
     * field). to_php is used for each field returned from
     * ::getConfigurationOptions(), and when the whole configuration is
     * built, to_config() is called and receives the config array. The array
     * should be returned, perhaps with modifications, and will be JSON
     * encoded and stashed in the database.
     */
    function to_config($value) {
Jared Hancock's avatar
Jared Hancock committed
    /**
     * to_database
     *
     * Determines the value to be stored in the database. The database
     * backend for all fields is a text field, so this method should return
     * a text value or NULL to represent the value of the field. The
     * ::to_php() method will convert this value back to PHP.
     *
     * Paremeters:
     * $value - PHP value of the field's content
     */
    function to_database($value) {
        return $value;
    }

    /**
     * toString
     *
     * Converts the PHP value created in ::parse() or ::to_php() to a
     * pretty-printed value to show to the user. This is especially useful
     * for something like dates which are stored considerably different in
     * the database from their respective human-friendly versions.
     * Furthermore, this method allows for internationalization and
     * localization.
     *
     * Parametes:
     * $value - PHP value of the field's content
     */
    function toString($value) {
        return (string) $value;
    function __toString() {
        return $this->toString($this->value);
    }

    /**
     * When data for this field is deleted permanently from some storage
     * backend (like a database), other associated data may need to be
     * cleaned as well. This hook allows fields to participate when the data
     * for a field is cleaned up.
     */
    function db_cleanup($field=false) {
    /**
     * Returns an HTML friendly value for the data in the field.
     */
    function display($value) {
        return Format::htmlchars($this->toString($value));
    }

    /**
     * Returns a value suitable for exporting to a foreign system. Mostly
     * useful for things like dates and phone numbers which should be
     * formatted using a standard when exported
     */
    function export($value) {
        return $this->toString($value);
    }

    /**
     * Fetch a value suitable for embedding the value of this field in an
     * email template. Reference implementation uses ::to_php();
     */
    function asVar($value, $id=false) {
        return $this->to_php($value, $id);
    }

    /**
     * Fetch the var type used with the email templating system's typeahead
     * feature. This helps with variable expansion if supported by this
     * field's ::asVar() method. This method should return a valid classname
     * which implements the `TemplateVariable` interface.
     */
    function asVarType() {
        return false;
    }

    /**
     * Describe the difference between the to two values. Note that the
     * values should be passed through ::parse() or to_php() before
     * utilizing this method.
     */
    function whatChanged($before, $after) {
        if ($before)
            $desc = __('changed from <strong>%2$s</strong> to <strong>%1$s</strong>');
        else
            $desc = __('set to <strong>%1$s</strong>');
        return sprintf($desc, $this->display($after), $this->display($before));
    }

    /**
     * Convert the field data to something matchable by filtering. The
     * primary use of this is for ticket filtering.
     */
    function getFilterData() {
        return $this->toString($this->getClean());
    }

    /**
     * Fetches a value that represents this content in a consistent,
     * searchable format. This is used by the search engine system and
     * backend.
     */
    function searchable($value) {
        return Format::searchable($this->toString($value));
    }

    /**
     * Fetches a list of options for searching. The values returned from
     * this method are passed to the widget's `::render()` method so that
     * the widget can be affected by this setting. For instance, date fields
     * might have a 'between' search option which should trigger rendering
     * of two date widgets for search results.
     */
    function getSearchMethods() {
        return array(
            'set' =>        __('has a value'),
            'nset' =>       __('does not have a value'),
            'equal' =>      __('is'),
            'nequal' =>     __('is not'),
            'contains' =>   __('contains'),
            'match' =>      __('matches'),
        );
    }

    function getSearchMethodWidgets() {
        return array(
            'set' => null,
            'equal' => array('TextboxField', array()),
            'nequal' => array('TextboxField', array()),
            'contains' => array('TextboxField', array()),
            'match' => array('TextboxField', array(
                'placeholder' => __('Valid regular expression'),
                'configuration' => array('size'=>30),
                'validators' => function($self, $v) {
                    if (false === @preg_match($v, ' ')
                        && false === @preg_match("/$v/", ' '))
                        $self->addError(__('Cannot compile this regular expression'));
                })),
        );
    }

    /**
     * This is used by the searching system to build a query for the search
     * engine. The function should return a criteria listing to match
     * content saved by the field by the `::to_database()` function.
     */
    function getSearchQ($method, $value, $name=false) {
        $criteria = array();
        $Q = new Q();
        $name = $name ?: $this->get('name');
        switch ($method) {
                $Q->negate();
            case 'set':
                $criteria[$name . '__isnull'] = false;
                break;

                $Q->negate();
            case 'equal':
                $criteria[$name] = $value;
                break;

            case 'contains':
                $criteria[$name . '__contains'] = $value;
                break;

            case 'match':
                $criteria[$name . '__regex'] = $value;
                break;
        }
        return $Q->add($criteria);
    }

    function getSearchWidget($method) {
        $methods = $this->getSearchMethodWidgets();
        $info = $methods[$method];
        if (is_array($info)) {
            $class = $info[0];
            return new $class($info[1]);
        }
        return $info;
    }

    function describeSearchMethod($method) {
        switch ($method) {
        case 'set':
            return __('%s has a value');
        case 'nset':
            return __('%s does not have a value');
        case 'equal':
            return __('%s is %s' /* describes an equality */);
        case 'nequal':
            return __('%s is not %s' /* describes an inequality */);
        case 'contains':
            return __('%s contains "%s"');
        case 'match':
            return __('%s matches pattern %s');
        case 'includes':
            return __('%s in (%s)');
        case '!includes':
            return __('%s not in (%s)');
        }
    }
    function describeSearch($method, $value, $name=false) {
        $desc = $this->describeSearchMethod($method);
        $value = $this->toString($value);
        return sprintf($desc, $name, $value);
    }

Jared Hancock's avatar
Jared Hancock committed
    function getLabel() { return $this->get('label'); }

    /**
     * getImpl
     *
     * Magic method that will return an implementation instance of this
     * field based on the simple text value of the 'type' value of this
     * field instance. The list of registered fields is determined by the
     * global get_dynamic_field_types() function. The data from this model
     * will be used to initialize the returned instance.
     *
     * For instance, if the value of this field is 'text', a TextField
     * instance will be returned.