<?php

/*
 +--------------------------------------------------------------------+
 | CiviCRM version 1.7                                                |
 +--------------------------------------------------------------------+
 | Copyright CiviCRM LLC (c) 2004-2007                                |
 +--------------------------------------------------------------------+
 | This file is a part of CiviCRM.                                    |
 |                                                                    |
 | CiviCRM is free software; you can copy, modify, and distribute it  |
 | under the terms of the Affero General Public License Version 1,    |
 | March 2002.                                                        |
 |                                                                    |
 | CiviCRM is distributed in the hope that it will be useful, but     |
 | WITHOUT ANY WARRANTY; without even the implied warranty of         |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
 | See the Affero General Public License for more details.            |
 |                                                                    |
 | You should have received a copy of the Affero General Public       |
 | License along with this program; if not, contact CiviCRM LLC       |
 | at info[AT]civicrm[DOT]org.  If you have questions about the       |
 | Affero General Public License or the licensing  of CiviCRM,        |
 | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
 +--------------------------------------------------------------------+
*/

/**
 * This is our base form. It is part of the Form/Controller/StateMachine
 * trifecta. Each form is associated with a specific state in the state
 * machine. Each form can also operate in various modes
 *
 * @package CRM
 * @copyright CiviCRM LLC (c) 2004-2007
 * $Id$
 *
 */

require_once 'HTML/QuickForm/Page.php';

require_once 'CRM/Utils/Rule.php';
require_once 'CRM/Utils/Request.php';
require_once 'CRM/Core/Smarty.php';
require_once 'CRM/Core/Form/Renderer.php';
require_once 'CRM/Core/SelectValues.php';

class CRM_Core_Form extends HTML_QuickForm_Page {

    /**
     * The state object that this form belongs to
     * @var object
     */
    protected $_state;

    /**
     * The name of this form
     * @var string
     */
    protected $_name;

    /**
     * The title of this form
     * @var string
     */
    protected $_title = null;

    /**
     * The options passed into this form
     * @var mixed
     */
    protected $_options = null;

    /**
     * The mode of operation for this form
     * @var int
     */
    protected $_action;

    /**
     * the renderer used for this form
     *
     * @var object
     */
    protected $_renderer;

    /**
     * cache the smarty template for efficiency reasons
     *
     * @var CRM_Core_Smarty
     */
    static protected $_template;

    /**
     * constants for attributes for various form elements
     * attempt to standardize on the number of variations that we 
     * use of the below form elements
     *
     * @var const string
     */
    const
        ATTR_SPACING = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';

    /**
     * All checkboxes are defined with a common prefix. This allows us to
     * have the same javascript to check / clear all the checkboxes etc
     * If u have multiple groups of checkboxes, you will need to give them different
     * ids to avoid potential name collision
     *
     * @var const string / int
     */
    const
        CB_PREFIX     = 'mark_x_',
        CB_PREFIY     = 'mark_y_',
        CB_PREFIZ     = 'mark_z_',
        CB_PREFIX_LEN = 7;

    /**
     * Constructor for the basic form page
     *
     * We should not use QuickForm directly. This class provides a lot
     * of default convenient functions, rules and buttons
     *
     * @param object    $state     State associated with this form
     * @param enum      $action    The mode the form is operating in (None/Create/View/Update/Delete)
     * @param string    $method    The type of http method used (GET/POST)
     * @param string    $name      The name of the form if different from class name
     * 
     * @return object
     * @access public
     */

    function __construct($state = null, $action = CRM_Core_Action::NONE, $method = 'post', $name = null ) {
        if ( $name ) {
            $this->_name  = $name;
        } else {
            $this->_name  = CRM_Utils_String::getClassName(CRM_Utils_System::getClassName($this));
        }

        $this->HTML_QuickForm_Page( $this->_name, $method );
    
        $this->_state   = $state;
        $this->_action  = $action;

        $this->registerRules( );

        // let the constructor initialize this, should happen only once
        if ( ! isset( self::$_template ) ) {
            self::$_template =& CRM_Core_Smarty::singleton( );
        }

    }

    static function generateID( ) {
    }

    /**
     * register all the standard rules that most forms potentially use
     *
     * @return void
     * @access private
     *
     */
    function registerRules( ) {
        static $rules = array( 'title', 'variable', 'qfVariable', 
                               'phone', 'integer', 'query',
                               'url','domain','numberOfDigit',
                               'date', 'qfDate', 'currentDate',
                               'asciiFile', 'htmlFile', 'utf8File',
                               'objectExists', 'optionExists', 'postalCode', 'money','positiveInteger',
                               'xssString' );

        foreach ( $rules as $rule ) {
            $this->registerRule( $rule, 'callback', $rule, 'CRM_Utils_Rule' );
        }
    }

    /**
     * Simple easy to use wrapper around addElement. Deal with
     * simple validation rules
     *
     * @param string type of html element to be added
     * @param string name of the html element
     * @param string display label for the html element
     * @param string attributes used for this element.
     *               These are not default values
     * @param bool   is this a required field
     *
     * @return object    html element, could be an error object
     * @access public
     *
     */
    function &add($type, $name, $label,
                  $attributes = '', $required = false, $javascript = null ) {
        $element =& $this->addElement($type, $name, $label, $attributes, $javascript );
        if (HTML_QuickForm::isError($element)) {
            CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($element));
        }
        
        if ( $required ) {
            $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)) , 'required');
            if (HTML_QuickForm::isError($error)) {
                CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($element));
            }
        }
    
        return $element;
    }
  
    /**
     * This function is called before buildForm. Any pre-processing that
     * needs to be done for buildForm should be done here
     *
     * This is a virtual function and should be redefined if needed
     *
     * @access public
     * @return void
     *
     */
    function preProcess() {
    }

    /**
     * This function is called after the form is validated. Any
     * processing of form state etc should be done in this function.
     * Typically all processing associated with a form should be done
     * here and relevant state should be stored in the session
     *
     * This is a virtual function and should be redefined if needed
     * 
     * @access public
     * @return void
     *
     */
    function postProcess() {
    }

    /**
     * This virtual function is used to build the form. It replaces the
     * buildForm associated with QuickForm_Page. This allows us to put 
     * preProcess in front of the actual form building routine
     *
     * @access public
     * @return void
     *
     */
    function buildQuickForm() {
    }

    /**
     * This virtual function is used to set the default values of
     * various form elements
     *
     * access        public
     * @return array reference to the array of default values
     *
     */
    function setDefaultValues( ) {
    }

    /**
     * This is a virtual function that adds group and global rules to
     * the form. Keeping it distinct from the form to keep code small
     * and localized in the form building code
     *
     * @access public
     * @return void
     *
     */
    function addRules() {
    }

    /**
     * Core function that builds the form. We redefine this function
     * here and expect all CRM forms to build their form in the function
     * buildQuickForm.
     *
     */
    function buildForm() {
        $this->_formBuilt = true;

        $this->preProcess();

        // add the drupal form token hidden value to allow things to work
        $config =& CRM_Core_Config::singleton( );
        if ( $config->userFramework == 'Drupal' &&
             $config->userFrameworkVersion <= 4.6      &&
             function_exists( 'drupal_get_token' ) ) {
            $this->addElement( 'hidden', 'edit[token]', drupal_get_token( ) );
        }

        if ( $this->controller->_key ) {
            $this->addElement( 'hidden', 'qfKey', $this->controller->_key );
        }

        $this->buildQuickForm();

        $defaults =& $this->setDefaultValues( );
        if ( ! empty( $defaults ) ) {
            $this->setDefaults( $defaults );
        }

        $this->addRules();
    }

    /**
     * Add default Next / Back buttons 
     *
     * @param array   array of associative arrays in the order in which the buttons should be
     *                displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
     *                The base form class will define a bunch of static arrays for commonly used
     *                formats
     *
     * @return void
     *
     * @access public
     *
     */
    function addButtons( $params ) {
        $prevnext = array( );
        $spacing = array( );
        foreach ( $params as $button ) {
            $js = CRM_Utils_Array::value( 'js', $button );
            $isDefault = CRM_Utils_Array::value( 'isDefault', $button, false );
            if ( $isDefault ) {
                $attrs = array( 'class' => 'form-submit default' );
            } else {
                $attrs = array( 'class' => 'form-submit' );
            }

            if ( $js ) {
                $attrs = array_merge( $js, $attrs );
            }

            if ( $button['type'] === 'reset' ) {
                $prevnext[] =& $this->createElement( $button['type'], 'reset', $button['name'], $attrs );
            } else {
                if ( CRM_Utils_Array::value( 'subName', $button ) ) {
                    $buttonName = $this->getButtonName( $button['type'], $button['subName'] );
                } else {
                    $buttonName = $this->getButtonName( $button['type'] );
                }
                $prevnext[] =& $this->createElement( 'submit', $buttonName, $button['name'], $attrs );
            }
            if ( CRM_Utils_Array::value( 'isDefault', $button ) ) {
                $this->setDefaultAction( $button['type'] );
            }
            
            // hack - addGroup uses an array to express variable spacing, read from the last element
            $spacing[] = CRM_Utils_Array::value('spacing', $button, self::ATTR_SPACING);
        }
        $this->addGroup( $prevnext, 'buttons', '', $spacing, false );
    }

    /**
     * getter function for Name
     *
     * @return string
     * @access public
     */     
    function getName() {
        return $this->_name;
    }
   
    /**
     * getter function for State
     *
     * @return object
     * @access public
     */     
    function &getState() {
        return $this->_state;
    }

    /**
     * getter function for StateType
     *
     * @return int
     * @access public
     */     
    function getStateType( ) {
        return $this->_state->getType( );
    }

    /**
     * getter function for title. Should be over-ridden by derived class
     *
     * @return string
     * @access public
     */     
    function getTitle( ) {
        return $this->_title ? $this->_title : ts( 'ERROR: Title is not Set' );
    }
	
    /**
     * setter function for title.
     *
     * @param string $title the title of the form
     *
     * @return void
     * @access public
     */     
    function setTitle( $title ) {
        $this->_title = $title;
    }

    /**
     * Setter function for options
     * @param mixed 
     *
     * @return void
     * @access public
     */
    function setOptions( $options ) {
        $this->_options = $options;
    }

    /**
     * getter function for link.
     *
     * @return string
     * @access public
     */     
    function getLink( ) {
        $config =& CRM_Core_Config::singleton( );
        return CRM_Utils_System::url( $_GET[$config->userFrameworkURLVar],
                                      '_qf_' . $this->_name . '_display=true' );
    }
	
    /**
     * boolean function to determine if this is a one form page
     *
     * @return boolean
     * @access public
     */     
    function isSimpleForm() {
        return $this->_state->getType( ) & ( CRM_Core_State::START | CRM_Core_State::FINISH );
    }

    /**
     * getter function for Form Action
     *
     * @return string
     * @access public
     */     
    function getFormAction() {
        return $this->_attributes['action'];
    }
	
    /**
     * setter function for Form Action
     *
     * @param string
     * @return void
     * @access public
     */     
    function setFormAction($action) {
        $this->_attributes['action'] = $action;
    }

    /**
     * render form and return contents
     * 
     * @return string
     * @access public
     */  
    function toSmarty() {
        $renderer =& $this->getRenderer();
        $this->accept($renderer);
        $content = $renderer->toArray();
        $content['formName'] = $this->getName();
        return $content;
    }

    /** 
     * getter function for renderer. If renderer is not set
     * create one and initialize it  
     *
     * @return object
     * @access public
     */
    function &getRenderer() {
        if (! isset($this->_renderer)) {
            $this->_renderer =& CRM_Core_Form_Renderer::singleton( );
        }
        return $this->_renderer;
    }
  
    /**
     * Use the form name to create the tpl file name
     *
     * @return string
     * @access public
     */
    function getTemplateFileName() {
        return (str_replace('_', DIRECTORY_SEPARATOR, CRM_Utils_System::getClassName($this)) . ".tpl");
    }

    /**
     * Error reporting mechanism
     *
     * @param string  $message Error Message
     * @param int     $code    Error Code
     * @param CRM_Core_DAO $dao     A data access object on which we perform a rollback if non - empty
     * @return void
     * @access public
     */
    function error( $message, $code = null, $dao = null ) {
        if ( $dao ) {
            $dao->query( 'ROLLBACK' );
        }

        $error =& CRM_Core_Error::singleton();
        
        $error->push( $code, $message );
    }
  
    /**
     * Store the variable with the value in the form scope
     *
     * @param  string name  : name  of the variable
     * @param  mixed  value : value of the variable
     *
     * @access public
     * @return void
     *
     */
    function set( $name, $value ) {
        $this->controller->set( $name, $value );
    }

    /**
     * Get the variable from the form scope
     *
     * @param  string name  : name  of the variable
     *
     * @access public
     * @return mixed
     *
     */
    function get( $name ) {
        return $this->controller->get( $name );
    }

    /**
     * getter for action
     *
     * @return int
     * @access public
     */
    function getAction( ) {
        return $this->_action;
    }

    /**
     * setter for action
     *
     * @param  int  $action the mode we want to set the form
     *
     * @return void
     * @access public
     */
    function setAction( $action ) {
        $this->_action = $action;
    }

    /**
     * assign value to name in template
     *
     * @param array|string $name  name  of variable
     * @param mixed $value value of varaible
     *
     * @return void
     * @access public
     */
    function assign( $var, $value = null) {
        self::$_template->assign($var, $value);
    }

    /**
     * assign value to name in template by reference
     *
     * @param array|string $name  name  of variable
     * @param mixed $value value of varaible
     *
     * @return void
     * @access public
     */
    function assign_by_ref( $var, &$value ) {
        self::$_template->assign_by_ref($var, $value);
    }

    function addRadio( $name, $title, &$values, $attributes = null, $separator = null, $required = false ) {
        $options = array( );
        foreach ( $values as $key => $var ) {
            $options[] =& HTML_QuickForm::createElement('radio', null, null, $var, $key, $attributes);
        }
        $this->addGroup($options, $name, $title, $separator);
        if ($required) {
            $this->addRule($name, $title, 'required');
        }           
    }

    function addYesNo( $id, $title, $dontKnow = null ,$required = null, $attribute = null) {
        $choice   = array( );
        $choice[] =& $this->createElement( 'radio', null, '11', ts( 'Yes' ), '1', $attribute );
        $choice[] =& $this->createElement( 'radio', null, '11', ts( 'No' ) , '0', $attribute );
        if ( $dontKnow ) {
            $choice[] =& $this->createElement( 'radio', null, '22', ts( "Don't Know" ), '2', $attribute );
        }
        $group =& $this->addGroup( $choice, $id, $title );

        if ( $required ) {
            $this->addRule($id, $title,'required');
        }
    }

    function addCheckBox( $id, $title, $values, $other = null, $attributes = null ,$required = null, $javascriptMethod = null) {
        $options = array( );

        if ($javascriptMethod) {
            foreach ( $values as $key => $var ) {
                $options[] =& HTML_QuickForm::createElement( 'checkbox', $var, null, $key, $javascriptMethod );
            }
        } else {
            foreach ( $values as $key => $var ) {
                $options[] =& HTML_QuickForm::createElement( 'checkbox', $var, null, $key );
            }
        }

        $this->addGroup($options, $id, $title, '<br/>' );

        if ( $other ) {
            $this->addElement( 'text', $id . '_other', ts( 'Other' ), $attributes[$id . '_other'] );
        }
        if ( $required ) {
            $this->addRule($id, $title, 'required');
        }
    }
                          
    function resetValues( ) {
        $data =& $this->controller->container( );
        $data['values'][$this->_name] = array( );
    }


    /**
     * simple shell that derived classes can call to add buttons to
     * the form with a customized title for the main Submit
     *
     * @param string $title title of the main button
     * @param string $type  button type for the form after processing
     * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
     * @return void
     * @access public
     */
    function addDefaultButtons( $title, $nextType = 'next', $backType = 'back', $submitOnce = false ) {
        $buttons = array();
        if ( $backType != null ) {
            $buttons[] = array ( 'type'      => $backType,
                                 'name'      => ts('Previous'));
        }
        if ( $nextType != null ) {
            $nextButton = array ( 'type'      => $nextType,
                                 'name'      => $title,
                                 'isDefault' => true   );
            if ( $submitOnce ) {
	      $nextButton['js'] = array( 'onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') ."');" );
            }
            $buttons[] = $nextButton;
        }
        $this->addButtons( $buttons );
    }

    function addSelect( $name, $label, $prefix = null, $required = null, $extra = null, $select = '- select -' ) {
        require_once "CRM/Core/OptionGroup.php";
        if ($prefix) {
            $this->addElement('select', $name . '_id' . $prefix , $label,
                              array('' => $select ) + CRM_Core_OptionGroup::values($name), $extra );
            if ( $required) {
                $this->addRule($name . '_id' . $prefix, ts('Please select %1', array(1 => $label)), 'required');
            }
        } else {
            require_once 'CRM/Core/OptionGroup.php';
            $this->addElement('select', $name. '_id' , $label,
                              array('' => $select ) + CRM_Core_OptionGroup::values($name), $extra );
            if ( $required) {
                $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
            }

        }
        
    }

    function addCountry( $id, $title ,$required = null, $extra = null ) {
        $this->addElement('select', $id, $title,
                          array('' => ts('- select -')) + CRM_Core_PseudoConstant::country( ), $extra );
        if( $required ) {
            $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
        }

    }

    function addSelectOther( $name, $label, $options, $attributes ,$required = null, $javascriptMethod = null) {
        
        $this->addElement('select', $name . '_id' , $label, $options, $javascriptMethod);
        
        if( $required ) {
            $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
        }

        $this->addElement( 'text', $name . '_other', $label, $attributes[$name . '_other'] );
    }

    function buildAddressBlock( $locationId, $title, $phone, $alternatePhone  = null, $addressRequired = null, $phoneRequired = null, $altPhoneRequired = null ,$locationName = null ) {
        if ( ! $locationName ) {
            $locationName = "location";
        }
        
        $config =& CRM_Core_Config::singleton( );
        $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_Address');

        $location[$locationId]['address']['street_address']         =
            $this->addElement('text', "{$locationName}[$locationId][address][street_address]", $title,
                              $attributes['street_address']);
        if( $addressRequired ){
            $this->addRule("{$locationName}[$locationId][address][street_address]" , ts("Please enter the Street Address for %1." , array( 1 => $title)),'required');
        }

        $location[$locationId]['address']['supplemental_address_1'] =
            $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_1]", ts('Additional Address 1'),
                              $attributes['supplemental_address_1']);
        $location[$locationId]['address']['supplemental_address_2'] =
            $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_2]", ts('Additional Address 2'),
                              $attributes['supplemental_address_2']);

        $location[$locationId]['address']['city']                   =
            $this->addElement('text', "{$locationName}[$locationId][address][city]", ts('City'),
                              $attributes['city']);
        if( $addressRequired ){
            $this->addRule("{$locationName}[$locationId][address][city]" , ts("Please enter the City for %1." , array( 1 => $title)),'required');
        }
        
        $location[$locationId]['address']['postal_code']            =
            $this->addElement('text', "{$locationName}[$locationId][address][postal_code]", ts('Zip / Postal Code'),
                              $attributes['postal_code']);
        if( $addressRequired ){
            $this->addRule("{$locationName}[$locationId][address][postal_code]" , ts("Please enter the Zip/Postal Code for %1." , array( 1 => $title)),'required');
        }
        $this->addRule( "{$locationName}[$locationId][address][postal_code]", ts("Zip/Postal Code not valid for %1.", array( 1 => $title)), 'positiveInteger' );
        
        $location[$locationId]['address']['postal_code_suffix']            =
            $this->addElement('text', "{$locationName}[$locationId][address][postal_code_suffix]", ts('Add-on Code'),
                              array( 'size' => 4, 'maxlength' => 12 ));
        $this->addRule( "{$locationName}[$locationId][address][postal_code_suffix]", ts('Zip-Plus not valid.'), 'positiveInteger' );

        if ( $config->includeCounty ) {
            $location[$locationId]['address']['county_id']             =
                $this->addElement('select', "{$locationName}[$locationId][address][county_id]", ts('County'),
                               array('' => ts('- select -')) + CRM_Core_PseudoConstant::county());                    
        }        
        
        $location[$locationId]['address']['state_province_id']      =
             $this->addElement('select', "{$locationName}[$locationId][address][state_province_id]", ts('State / Province'),
                               array('' => ts('- select -')) + CRM_Core_PseudoConstant::stateProvince());

        $location[$locationId]['address']['country_id']             =
             $this->addElement('select', "{$locationName}[$locationId][address][country_id]", ts('Country'),
                               array('' => ts('- select -')) + CRM_Core_PseudoConstant::country());
        if( $addressRequired ){
            $this->addRule("{$locationName}[$locationId][address][country_id]" , ts("Please select the Country for %1." , array( 1 => $title)),'required');
        }
        

         if ( $phone ) {
             $location[$locationId]['phone'][1]['phone']      = $this->addElement('text',
                                                                                  "{$locationName}[$locationId][phone][1][phone]", 
                                                                                  $phone,
                                                                                  CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone',
                                                                                                             'phone'));
             if($phoneRequired) {
                 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a value for %1', array(1 => $phone)), 'required');
             }
             $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a valid number for %1', array(1 => $phone)), 'phone');
         }

         if ( $alternatePhone ) {
             $location[$locationId]['phone'][2]['phone']      = $this->addElement('text',
                                                                                  "{$locationName}[$locationId][phone][2][phone]", 
                                                                                  $alternatePhone,
                                                                                  CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone',
                                                                                                             
                                                                                                   'phone'));
             if ($alternatePhoneRequired) {
                 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a value for %1', array(1 => $alternatePhone)), 'required');
             }
             $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a valid number for %1', array(1 => $alternatePhone)), 'phone');
         }
    }

    public function getRootTitle( ) {
        return null;
    }

    public function getCompleteTitle( ) {
        return $this->getRootTitle( ) . $this->getTitle( );
    }

    static function &getTemplate( ) {
        return self::$_template;
    }
    
}

?>
