Location: PHPKode > projects > SolarPHP > solar-system-1.1.1/solar/source/solar/Solar/Form/Load/Model.php
<?php
/**
 * 
 * Class for loading form definitions from Solar_Sql_Model columns.
 * 
 * @category Solar
 * 
 * @package Solar_Form
 * 
 * @author Paul M. Jones <hide@address.com>
 * 
 * @author Jeff Moore <hide@address.com>
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 * @version $Id: Model.php 4287 2009-12-31 16:47:54Z pmjones $
 * 
 */
class Solar_Form_Load_Model extends Solar_Base
{
    /**
     * 
     * Default configuration values.
     * 
     * @config int default_text_size The default 'size' attribute for text
     * elements.
     * 
     * @config int default_textarea_rows The default 'rows' attribute for
     * textarea elements.
     * 
     * @config int default_textarea_cols The default 'cols' attribute for
     * textarea elements.
     * 
     * @var array
     * 
     */
    protected $_Solar_Form_Load_Model = array(
        'default_text_size'     => 60,
        'default_textarea_rows' => 18,
        'default_textarea_cols' => 60,
    );
    
    /**
     * 
     * The model we use for getting information about columns for elements.
     * 
     * @var Solar_Sql_Model
     * 
     */
    protected $_model;
    
    /**
     * 
     * Create elements as part of this array name in the form.
     * 
     * @var string
     * 
     */
    protected $_array_name;
    
    /**
     * 
     * Column definitions from the model.
     * 
     * @var array
     * 
     */
    protected $_cols;
    
    /**
     * 
     * Filter definitions from the model.
     * 
     * @var array
     * 
     */
    protected $_filters;
    
    /**
     * 
     * The column names and element options to load into the form.
     * 
     * @var array
     * 
     */
    protected $_load;
    
    /**
     * 
     * Default element values from the model.
     * 
     * @var array
     * 
     */
    protected $_default;
    
    /**
     * 
     * Loads Solar_Form elements based on Solar_Sql_Model columns.
     * 
     * @param Solar_Sql_Model $model Load form elements from this model object.
     * 
     * @param array $load Which model columns to load as form elements. If
     * empty or '*', uses all fetch and calculate columns.
     * 
     * @param string $array_name Load the model columns as elements of this
     * array-name within the form.
     * 
     * @return array An array of form attributes and elements.
     * 
     */
    public function fetch($model, $load = null, $array_name = null)
    {
        if (! $load) {
            $load = '*';
        }
        
        $this->_setModel($model);
        $this->_setLoad($load);
        $this->_setArrayName($array_name);
        
        // loop through the list of requested columns and collect elements
        $elements = array();
        foreach ($this->_load as $name => $spec) {
            
            // if $name is integer, $spec is just a column name,
            // and there are no added element specifications.
            if (is_int($name)) {
                $name = $spec;
                $spec = array();
            } else {
                settype($spec, 'array');
            }
            
            // get the column description
            $col = $this->_getCol($name);
            
            // get the base element
            $elem = $this->_newElement($spec);
            
            // fix each part of the element
            $this->_fixElement($elem, $name, $col);
            
            // keep the element
            $elements[$elem['name']] = $elem;
        }
        
        // done!
        $result = array(
            'attribs'  => array(),
            'elements' => $elements
        );
        
        return $result;
    }
    
    /**
     * 
     * Sets the model to use for loading.
     * 
     * @param Solar_Sql_Model $model The model to use for loading.
     * 
     * @return void
     * 
     */
    protected function _setModel($model)
    {
        // make sure it's a model
        if (! $model instanceof Solar_Sql_Model) {
            throw $this->_exception('ERR_NOT_MODEL_OBJECT');
        }
        
        $this->_model = $model;
        $this->_setCols();
        $this->_setFilters();
        $this->_setDefault();
    }
    
    /**
     * 
     * Sets the array-name for form elements.
     * 
     * @param string $array_name The array name to use.
     * 
     * @return void
     * 
     */
    protected function _setArrayName($array_name)
    {
        // if not specified, set the array_name to the model name
        if (empty($array_name)) {
            $this->_array_name = $this->_model->array_name;
        } else {
            $this->_array_name = $array_name;
        }
    }
    
    /**
     * 
     * Sets the column definitions from the model.
     * 
     * @return void
     * 
     */
    protected function _setCols()
    {
        // all table and calculate column descriptions in the model
        $this->_cols = array_merge(
            $this->_model->table_cols,
            $this->_model->calculate_cols
        );
    }
    
    /**
     * 
     * Gets a column definition from the model; if the column does not exist
     * at the model, gets a "fake" column definition instead.
     * 
     * @param string $name The column name to retrieve.
     * 
     * @return array The column definition.
     * 
     */
    protected function _getCol($name)
    {
        if (! empty($this->_cols[$name])) {
            return $this->_cols[$name];
        } else {
            return $this->_getFakeCol($name);
        }
    }
    
    /**
     * 
     * Gets a "fake" column definition.
     * 
     * @param string $name The fake column name.
     * 
     * @return array The fake column definition.
     * 
     */
    protected function _getFakeCol($name)
    {
        return array(
            'name'    => $name,
            'type'    => 'text',
            'size'    => null,
            'scope'   => null,
            'default' => null,
            'require' => false,
            'primary' => false,
            'autoinc' => false,
        );
    }
    
    /**
     * 
     * Sets the list of column names and element options to load as elements.
     * 
     * @param string|array $load The column names and element options.  If a
     * '*' is used, loads all columns from the model, minus special columns
     * (e.g. primary, created, xmlstruct, etc).
     * 
     * @return void
     * 
     */
    protected function _setLoad($load)
    {
        // if not '*', we have a list of element names and element hints
        if ($load != '*') {
            $this->_load = (array) $load;
            return;
        }
        
        // looking for '*' columns; set the list to all the model columns.
        if ($this->_model->fetch_cols) {
            // use the fetch and calculate cols
            $load = array_merge(
                $this->_model->fetch_cols,
                array_keys($this->_model->calculate_cols)
            );
        } else {
            // use all columns
            $load = array_keys($this->_cols);
        }
        
        // flip around so we can unset easier
        $load = array_flip($load);
        
        // remove special columns
        unset($load[$this->_model->primary_col]);
        unset($load[$this->_model->created_col]);
        unset($load[$this->_model->updated_col]);
        unset($load[$this->_model->inherit_col]);
        
        // remove sequence columns
        foreach ($this->_model->sequence_cols as $key => $val) {
            unset($load[$key]);
        }
        
        // remove xmlstruct columns
        foreach ($this->_model->xmlstruct_cols as $key => $val) {
            unset($load[$key]);
        }
            
        // done!
        $this->_load = array_keys($load);
    }
    
    /**
     * 
     * Sets the default values for elements, using the model.
     * 
     * @return void
     * 
     */
    protected function _setDefault()
    {
        $this->_default = $this->_model->fetchNew();
    }
    
    /**
     * 
     * Gets the default value for a column, or null if the columns does not
     * exist at the model.
     * 
     * @param string $name The column name to get default value for.
     * 
     * @return mixed
     * 
     */
    protected function _getDefault($name)
    {
        if (isset($this->_default[$name])) {
            return $this->_default[$name];
        } else {
            return null;
        }
    }
    
    /**
     * 
     * Sets the filters from the model.
     * 
     * @return void
     * 
     */
    protected function _setFilters()
    {
        $this->_filters = $this->_model->filters;
    }
    
    /**
     * 
     * Gets the filters for a column.
     * 
     * @param string $name The column name.
     * 
     * @return array
     * 
     */
    protected function _getFilters($name)
    {
        if (! empty($this->_filters[$name])) {
            return $this->_filters[$name];
        } else {
            return array();
        }
    }
    
    /**
     * 
     * Returns a new (baseline) form element array.
     * 
     * @param array $spec Element specification hints.
     * 
     * @return array The baseline element array.
     * 
     */
    protected function _newElement($spec)
    {
        // initial set of element keys
        $elem = array(
            'name'    => null,
            'type'    => null,
            'label'   => null,
            'descr'   => null,
            'value'   => null,
            'require' => null,
            'disable' => null,
            'options' => null,
            'attribs' => null,
            'filters' => null,
            'invalid' => null,
        );
        
        // set up the base element with the element info hints
        $elem = array_merge($elem, $spec);
        
        // done
        return $elem;
    }
    
    /**
     * 
     * Fixes each part of the element in-place.
     * 
     * @param array &$elem The element to work with.
     * 
     * @param string $name The original element name (column name).
     * 
     * @param string $col The column definition used to inform the element.
     * 
     * @return void
     * 
     */
    protected function _fixElement(&$elem, $name, $col)
    {
        foreach ($elem as $key => $val) {
            $method = "_fixElement" . ucfirst($key);
            if (method_exists($this, $method)) {
                $this->$method($elem, $name, $col);
            }
        }
    }
    
    /**
     * 
     * Fixes the element name in-place.
     * 
     * @param array &$elem The element to work with.
     * 
     * @param string $name The original element name (column name).
     * 
     * @param string $col The column definition used to inform the element.
     * 
     * @return void
     * 
     */
    protected function _fixElementName(&$elem, $name, $col)
    {
        if ($elem['name'] !== null) {
            return;
        }
        
        $elem['name'] = $this->_array_name . '[' . $name . ']';
    }
    
    /**
     * 
     * Fixes the element type in-place.
     * 
     * @param array &$elem The element to work with.
     * 
     * @param string $name The original element name (column name).
     * 
     * @param string $col The column definition used to inform the element.
     * 
     * @return void
     * 
     */
    protected function _fixElementType(&$elem, $name, $col)
    {
        if ($elem['type'] !== null) {
            return;
        }
        
        // hide primary keys
        if ($col['primary']) {
            $elem['type'] = 'hidden';
            return;
        }
        
        // pick an element type based on the column type
        switch ($col['type']) {
        
        case 'bool':
            $elem['type'] = 'checkbox';
            break;
            
        case 'clob':
            $elem['type'] = 'textarea';
            break;
            
        case 'date':
        case 'time':
        case 'timestamp':
            $elem['type'] = $col['type'];
            break;
            
        default:
            // look for 'select' and 'file' candidates
            $filters = $this->_getFilters($name);
            foreach ($filters as $filter) {
                // if there is a filter to 'validateInList' or
                // 'validateInKeys', make this a select element.
                if ($filter[0] == 'validateInKeys' || $filter[0] == 'validateInList') {
                    $elem['type'] = 'select';
                    break;
                }
                // if there is a filter to 'validateUpload', make this
                // a file element
                if ($filter[0] == 'validateUpload') {
                    $elem['type'] = 'file';
                    break;
                }
            }
            break;
        }
        
        // if type is still empty, make it text.
        if (! $elem['type']) {
            $elem['type'] = 'text';
        }
    }
    
    /**
     * 
     * Fixes the element label in-place.
     * 
     * @param array &$elem The element to work with.
     * 
     * @param string $name The original element name (column name).
     * 
     * @param string $col The column definition used to inform the element.
     * 
     * @return void
     * 
     */
    protected function _fixElementLabel(&$elem, $name, $col)
    {
        if ($elem['label'] !== null) {
            return;
        }
        
        // if no label specified, set up based on element name
        $elem['label'] = $this->_model->locale(strtoupper("LABEL_$name"));
    }
    
    /**
     * 
     * Fixes the element description in-place.
     * 
     * @param array &$elem The element to work with.
     * 
     * @param string $name The original element name (column name).
     * 
     * @param string $col The column definition used to inform the element.
     * 
     * @return void
     * 
     */
    protected function _fixElementDescr(&$elem, $name, $col)
    {
        if ($elem['descr'] !== null) {
            return;
        }
        
        // if no label specified, set up based on element name
        $elem['descr'] = $this->_model->locale(strtoupper("DESCR_$name"));
    }
    
    /**
     * 
     * Fixes the element value in-place.
     * 
     * @param array &$elem The element to work with.
     * 
     * @param string $name The original element name (column name).
     * 
     * @param string $col The column definition used to inform the element.
     * 
     * @return void
     * 
     */
    protected function _fixElementValue(&$elem, $name, $col)
    {
        if ($elem['value'] !== null) {
            return;
        }
        
        $elem['value'] = $this->_getDefault($name);
    }
    
    /**
     * 
     * Fixes the element require-flag in-place.
     * 
     * @param array &$elem The element to work with.
     * 
     * @param string $name The original element name (column name).
     * 
     * @param string $col The column definition used to inform the element.
     * 
     * @return void
     * 
     */
    protected function _fixElementRequire(&$elem, $name, $col)
    {
        if ($elem['require'] !== null) {
            return;
        }
        
        // require if the table says so
        if ($col['require']) {
            $elem['require'] = true;
            return;
        }
        
        // if there is a validateNotBlank filter, mark to require
        $filters = $this->_getFilters($name);
        foreach ($filters as $filter) {
            if ($filter[0] == 'validateNotBlank') {
                // mark as required, and done
                $elem['require'] = true;
                return;
            }
        }
    }
    
    /**
     * 
     * Fixes the element disable-flag in-place.
     * 
     * @param array &$elem The element to work with.
     * 
     * @param string $name The original element name (column name).
     * 
     * @param string $col The column definition used to inform the element.
     * 
     * @return void
     * 
     */
    protected function _fixElementDisable(&$elem, $name, $col)
    {
        if ($elem['disable'] !== null) {
            return;
        }
        
        if ($col['primary']) {
            $elem['disable'] = true;
        }
    }
    
    /**
     * 
     * Fixes the element options in-place.
     * 
     * @param array &$elem The element to work with.
     * 
     * @param string $name The original element name (column name).
     * 
     * @param string $col The column definition used to inform the element.
     * 
     * @return void
     * 
     */
    protected function _fixElementOptions(&$elem, $name, $col)
    {
        if ($elem['options'] !== null) {
            return;
        }
        
        // only fix options for certain element types
        $types = array('checkbox', 'select', 'radio');
        if (! in_array($elem['type'], $types)) {
            return;
        }
        
        // loop through the filters for the element to find a 'keys' or 'list'
        // validation
        $filters = $this->_getFilters($name);
        foreach ($filters as $filter) {
            $ok = $filter[0] == 'validateInKeys'
               || $filter[0] == 'validateInList';
               
            if ($ok) {
                $elem['options'] = $this->_autoOptions($filter[0], $filter[1]);
                break;
            }
        }
        
        // if still no options for checkboxes, set to (1,0)
        if (! $elem['options'] && $elem['type'] == 'checkbox') {
            $elem['options'] = array(1,0);
        }
    }
    
    /**
     * 
     * Fixes the element attribs in-place.
     * 
     * @param array &$elem The element to work with.
     * 
     * @param string $name The original element name (column name).
     * 
     * @param string $col The column definition used to inform the element.
     * 
     * @return void
     * 
     */
    protected function _fixElementAttribs(&$elem, $name, $col)
    {
        // for text elements, set maxlength if none specified
        $fix_maxlength = $elem['type'] == 'text'
                      && empty($elem['attribs']['maxlength'])
                      && $col['size'] > 0;
        
        if ($fix_maxlength) {
            /** @todo Add +1 or +2 to 'size' for numeric types? */
            $elem['attribs']['maxlength'] = $col['size'];
        }
        
        // for text elements, set size
        $fix_size = $elem['type'] == 'text'
                 && empty($elem['attribs']['size']);
        
        if ($fix_size) {
            $elem['attribs']['size'] = $this->_config['default_text_size'];
        }
        
        // for textarea elements, fix rows
        $fix_rows = $elem['type'] == 'textarea'
                 && empty($elem['attribs']['rows']);
        
        if ($fix_rows) {
            $elem['attribs']['rows'] = $this->_config['default_textarea_rows'];
        }
        
        // for textarea elements, fix cols
        $fix_cols = $elem['type'] == 'textarea'
                 && empty($elem['attribs']['cols']);
        
        if ($fix_cols) {
            $elem['attribs']['cols'] = $this->_config['default_textarea_cols'];
        }
    }
    
    /**
     * 
     * Fixes the element filters in-place.
     * 
     * @param array &$elem The element to work with.
     * 
     * @param string $name The original element name (column name).
     * 
     * @param string $col The column definition used to inform the element.
     * 
     * @return void
     * 
     */
    protected function _fixElementFilters(&$elem, $name, $col)
    {
        if (! $elem['filters'] === null) {
            return;
        }
        
        $elem['filters'] = array();
    }
    
    /**
     * 
     * Fixes the element invalid-messages in-place.
     * 
     * @param array &$elem The element to work with.
     * 
     * @param string $name The original element name (column name).
     * 
     * @param string $col The column definition used to inform the element.
     * 
     * @return void
     * 
     */
    protected function _fixElementInvalid(&$elem, $name, $col)
    {
        if (! $elem['invalid'] === null) {
            return;
        }
        
        $elem['invalid'] = array();
    }
    
    /**
     * 
     * Builds an option list from validateInKeys and validateInList values.
     * 
     * The 'validateInKeys' options are not changed.
     * 
     * The 'validateInList' options are generally sequential, so the label
     * and the value are made to be identical (based on the label).
     * 
     * @param string $type The validation type, 'validateInKeys' or 'validateInList'.
     * 
     * @param array $opts The options provided by the validation.
     * 
     * @return array
     * 
     */
    protected function _autoOptions($type, $opts)
    {
        // leave the labels and values alone
        if ($type == 'validateInKeys') {
            return $opts;
        }
        
        // make the form display the labels as both labels and values
        if ($type == 'validateInList') {
            $vals = array_values($opts);
            return array_combine($vals, $vals);
        }
    }
}
Return current item: SolarPHP