<?php

/*
+---------------------------------------------------------------------------+
| Revive Adserver                                                           |
| http://www.revive-adserver.com                                            |
|                                                                           |
| Copyright: See the COPYRIGHT.txt file.                                    |
| License: GPLv2 or later, see the LICENSE.txt file.                        |
+---------------------------------------------------------------------------+
*/

require_once MAX_PATH . '/lib/pear/HTML/QuickForm.php';
require_once MAX_PATH . '/lib/OA/Admin/UI/component/ArrayRenderer.php';
require_once MAX_PATH . '/lib/OA/Admin/UI/component/rule/RuleAdaptorRegistry.php';
require_once MAX_PATH . '/lib/OA/Admin/UI/component/decorator/DecoratorFactory.php';
require_once MAX_PATH . '/lib/OA/Admin/UI/component/rule/JQueryValidationRuleBuilder.php';


class OA_Admin_UI_Component_Form extends HTML_QuickForm
{
    /**
     * @var mixed[]
     */
    public $decorators;
    private $dispatcher;
    private $id;
    private $forceClientValidation = false;
    private $jQueryValidationBuilder;
    private $hasRequiredFields = false;

    public function __construct($formName = '', $method = 'POST', $action = '', $target = '', $attributes = null, $trackSubmit = true)
    {
        parent::__construct($formName, $method, $action, $target, $attributes, $trackSubmit);
        $this->id = $formName;

        //register custom fields
        parent::registerElementType(
            'html',
            MAX_PATH . '/lib/OA/Admin/UI/component/Html.php',
            'OA_Admin_UI_Component_Html',
        );

        parent::registerElementType(
            'controls',
            MAX_PATH . '/lib/OA/Admin/UI/component/FormControls.php',
            'OA_Admin_UI_Component_FormControls',
        );

        parent::registerElementType(
            'break',
            MAX_PATH . '/lib/OA/Admin/UI/component/FormBreak.php',
            'OA_Admin_UI_Component_FormBreak',
        );

        parent::registerElementType(
            'custom',
            MAX_PATH . '/lib/OA/Admin/UI/component/CustomFormElement.php',
            'OA_Admin_UI_Component_CustomFormElement',
        );

        parent::registerElementType(
            'plugin-custom',
            MAX_PATH . '/lib/OA/Admin/UI/component/CustomPluginFormElement.php',
            'OA_Admin_UI_Component_CustomPluginFormElement',
        );

        parent::registerElementType(
            'script',
            MAX_PATH . '/lib/OA/Admin/UI/component/ScriptFormElement.php',
            'OA_Admin_UI_Component_ScriptFormElement',
        );

        parent::registerElementType(
            'plugin-script',
            MAX_PATH . '/lib/OA/Admin/UI/component/PluginScriptFormElement.php',
            'OA_Admin_UI_Component_PluginScriptFormElement',
        );

        //register additional rules
        $this->registerRule('wholenumber', 'regex', '/^\d+$/');
        $this->registerRule('wholenumber-', 'regex', '/^\d+$|^\-$/');
        $this->registerRule('formattednumber', 'regex', '/^\d+$|^\d(,\d{3})+$/');
        $this->registerRule('decimal', 'regex', '/^([+-])?\d+(\.\d+)?$/');
        $this->registerRule(
            'decimalplaces',
            'rule',
            'OA_Admin_UI_Rule_DecimalPlaces',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/DecimalPlaces.php',
        );
        $this->registerRule(
            'min',
            'rule',
            'OA_Admin_UI_Rule_Min',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/Min.php',
        );
        $this->registerRule(
            'max',
            'rule',
            'OA_Admin_UI_Rule_Max',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/Max.php',
        );

        $this->registerRule(
            'unique',
            'rule',
            'OA_Admin_UI_Rule_Unique',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/Unique.php',
        );

        //register jquery rule adaptors
        $this->registerJQueryRuleAdaptor(
            'required',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/QuickFormRequiredRuleAdaptor.php',
            'OA_Admin_UI_Rule_JQueryRequiredRule',
        );

        $this->registerJQueryRuleAdaptor(
            'minlength',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/QuickFormMinLengthRuleAdaptor.php',
            'OA_Admin_UI_Rule_JQueryMinLengthRule',
        );
        $this->registerJQueryRuleAdaptor(
            'maxlength',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/QuickFormMaxLengthRuleAdaptor.php',
            'OA_Admin_UI_Rule_JQueryMaxLengthRule',
        );

        $this->registerJQueryRuleAdaptor(
            'email',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/QuickFormEmailRuleAdaptor.php',
            'OA_Admin_UI_Rule_JQueryEmailRule',
        );

        $this->registerJQueryRuleAdaptor(
            'numeric',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/QuickFormDigitsRuleAdaptor.php',
            'OA_Admin_UI_Rule_JQueryDigitsRule',
        );
        $this->registerJQueryRuleAdaptor(
            'nonzero',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/QuickFormNonZeroRuleAdaptor.php',
            'OA_Admin_UI_Rule_JQueryNonZeroRule',
        );
        $this->registerJQueryRuleAdaptor(
            'decimal',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/QuickFormNumberRuleAdaptor.php',
            'OA_Admin_UI_Rule_JQueryNumberRule',
        );
        $this->registerJQueryRuleAdaptor(
            'min',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/QuickFormMinRuleAdaptor.php',
            'OA_Admin_UI_Rule_JQueryMinRule',
        );
        $this->registerJQueryRuleAdaptor(
            'max',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/QuickFormMaxRuleAdaptor.php',
            'OA_Admin_UI_Rule_JQueryMaxRule',
        );
        $this->registerJQueryRuleAdaptor(
            'decimalplaces',
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/QuickFormDecimalPlacesAdaptor.php',
            'OA_Admin_UI_Rule_QuickFormDecimalPlacesAdaptor',
        );


        $this->registerJQueryRuleAdaptor(
            "unique",
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/QuickFormUniqueRuleAdaptor.php',
            'OA_Admin_UI_Rule_JQueryUniqueRule',
        );

        $this->registerJQueryRuleAdaptor(
            "compare",
            MAX_PATH . '/lib/OA/Admin/UI/component/rule/QuickFormCompareRuleAdaptor.php',
            'OA_Admin_UI_Rule_JQueryCompareRule',
        );

        //register element decorators
        $this->registerElementDecorator(
            'tag',
            MAX_PATH . '/lib/OA/Admin/UI/component/decorator/HTMLTagDecorator.php',
            'OA_Admin_UI_HTMLTagDecorator',
        );
        $this->registerElementDecorator(
            'process',
            MAX_PATH . '/lib/OA/Admin/UI/component/decorator/ProcessingDecorator.php',
            'OA_Admin_UI_ProcessingDecorator',
        );


        //apply flat class
        $this->setAttribute("class", "flat");

        //trim spaces from all data sent by the user
        $this->applyFilter('__ALL__', 'trim');

        if (!defined('phpAds_installing')) {
            $this->addElement('hidden', 'token', phpAds_SessionGetToken());
            $this->addRule('token', 'Missing request token', 'required');
            $this->addRule('token', 'Invalid request token', 'callback', 'phpAds_SessionValidateToken');
        }
    }

    public function validate()
    {
        $ret = parent::validate();

        if (!$ret) {
            // The form returned an error. We need to generate a new CSRF token, in any.
            $token = $this->getElement('token');
            if (!empty($token) && !PEAR::isError($token)) {
                $token->setValue(phpAds_SessionGetToken());
            }
        }

        return $ret;
    }

    /**
     * Registers new JQuery QuickForm rule adaptor. Registered adaptors should
     * implement OA_Admin_UI_Rule_QuickFormToJQueryRuleAdaptor
     *
     * @param     string    $quickFormRuleName   Name of adapted QuickForm rule
     * @param     string    $include    Include path for adaptor
     * @param     string    $className  Adaptor class name
     * @return unknown
     */
    public function registerJQueryRuleAdaptor($quickFormRuleName, $include, $className)
    {
        $registry = OA_Admin_UI_Rule_JQueryRuleAdaptorRegistry::singleton();
        return $registry->registerJQueryRuleAdaptor($quickFormRuleName, $include, $className);
    }


    /**
     * Registers new element decorator. Registered decorators should implement
     * implement OA_Admin_UI_Decorator interface.
     *
     * @param     string    $decoratorName   Name of decorator
     * @param     string    $include    Include path for decorator
     * @param     string    $className  Decorator class name
     * @return unknown
     */
    public function registerElementDecorator($decoratorName, $include, $className)
    {
        $registry = OA_Admin_UI_Decorator_Factory::singleton();
        return $registry->registerDecorator($decoratorName, $include, $className);
    }


    /**
     * Allows to turn on client validation by default for validation rules that
     * does not explicitly specify "client" validation type.
     *
     * Validation rules that explicitly define "server" side validation will not
     * be enforced to run client side.
     *
     * Note: If you enforce client validation it will be applied
     * to all rules that were added after the it was enforced. It does not affect
     * rules added before it was invoked.
     * If you want all form rules to be applied first force client validation
     * and then start adding rules.
     *
     */
    public function forceClientValidation($force)
    {
        $this->forceClientValidation = $force;
    }


    /**
     * Overrides parent addRule method. Adds validation rule for an element.
     * Takes in the account current state of forceClientValidation setting
     * modifyig $validation parameter value if required
     *
     * @param    string|array     $element       Form element name
     * @param    string     $message       Message to display for invalid data
     * @param    string     $type          Rule type, use getRegisteredRules() to get types
     * @param    string     $format        (optional)Required for extra rule data
     * @param    string     $validation    (optional)Where to perform validation: "server", "client". Defaults to "server", unless forceClientValidation is not set on form
     * @param    boolean    $reset         Client-side validation: reset the form element to its original value if there is an error?
     * @param    boolean    $force         Force the rule to be applied, even if the target form element does not exist
     * @access   public
     * @throws   HTML_QuickForm_Error
     *
     */
    public function addRule($element, $message, $type, $format = null, $validation = null, $reset = false, $force = false)
    {
        if (empty($validation) && $this->forceClientValidation) {
            $validation = 'client';
        }
        if ($type == 'required') {
            $this->hasRequiredFields = true;
        }
        return parent::addRule($element, $message, $type, $format, $validation, $reset, $force);
    }

    public function addGroupRule($group, $arg1, $type = '', $format = null, $howmany = 0, $validation = 'server', $reset = false)
    {
        if (empty($validation) && $this->forceClientValidation) {
            $validation = 'client';
        }

        if (is_array($arg1)) {
            foreach ($arg1 as $elementIndex => $rules) {
                foreach ($rules as $rule) {
                    if (in_array($rule[1], ['required', 'uploadedfile'])) {
                        $this->hasRequiredFields = true;
                    }
                }
            }
        }

        if ($type == 'required') {
            $this->hasRequiredFields = true;
        }
        return parent::addGroupRule($group, $arg1, $type, $format, $howmany, $validation, $reset);
    }

    public function addElements($elements = [])
    {
        foreach ($elements as $element) {
            $this->addElement($element);
        }
    }


    /**
     * Overrides method from parent, append name defaults to false
     * @param    array      $elements       array of elements composing the group
     * @param    string     $name           (optional)group name
     * @param    string     $groupLabel     (optional)group label
     * @param    string     $separator      (optional)string to separate elements
     * @param    string     $appendName     (optional)specify whether the group name should be
     *                                      used in the form element name ex: group[element], defaults to false
     * @return   HTML_QuickForm_group       reference to a newly added group
     * @since    2.8
     * @access   public
     * @throws   HTML_QuickForm_Error
     */
    public function &addGroup($elements, $name = null, $groupLabel = '', $separator = null, $appendName = false)
    {
        return parent::addGroup($elements, $name, $groupLabel, $separator, $appendName);
    }


    public function addDecorator($elementName, $decoratorName, $decoratorOptions = null)
    {
        $elementDecorators = $this->decorators[$elementName];

        if (empty($elementDecorators)) {
            $elementDecorators = [];
        }
        $elementDecorators[] = OA_Admin_UI_Decorator_Factory::newDecorator($decoratorName, $decoratorOptions);

        $this->decorators[$elementName] = $elementDecorators;
    }


    public function getDecorators($elementName)
    {
        return $this->decorators[$elementName];
    }


    public function toArray($collectHidden = false)
    {
        $renderer = new OA_Admin_UI_Component_ArrayRenderer($collectHidden);
        $this->accept($renderer);
        return $renderer->toArray();
    }


    public function serialize()
    {
        return $this->toArray(true);
    }


    public function getId()
    {
        return $this->id;
    }


    public function getJQueryValidationMethods()
    {
        if (empty($this->_rules) || empty($this->_attributes['onsubmit'])) {
            return '';
        }

        $activeRules = $this->getActiveRules();
        $builder = $this->getJQueryValidationBuilder();

        return $builder->getJQueryValidationMethodsScript($activeRules);
    }


    /**
     * Return a JS rule definition applicable for JQuery validation plugin
     * http://bassistance.de/jquery-plugins/jquery-plugin-validation/
     * based on active client rules.
     * Please not that this does not take into account some properties of the
     * rule like. eg. 'reset'.
     *
     */
    public function getJQueryValidationRules()
    {
        if (empty($this->_rules) || empty($this->_attributes['onsubmit'])) {
            return '';
        }
        $activeRules = $this->getActiveRules();
        $builder = $this->getJQueryValidationBuilder();

        return $builder->getJQueryValidationRulesScript($activeRules);
    }


    /**
     * Gets the builder for the jquery validation script
     *
     * @return OA_Admin_UI_Rule_JQueryValidationRuleBuilder
     */
    private function getJQueryValidationBuilder()
    {
        if ($this->jQueryValidationBuilder == null) {
            $jQueryValidationBuilder = new OA_Admin_UI_Rule_JQueryValidationRuleBuilder();
        }
        return $jQueryValidationBuilder;
    }


    /**
     * Return active validation rules
     *
     * @return unknown
     */
    private function getActiveRules()
    {
        $js_escape = [
            "\r" => '\r',
            "\n" => '\n',
            "\t" => '\t',
            "'" => "\\'",
            '"' => '\"',
            '\\' => '\\\\',
        ];

        $activeRules = [];
        $jqueryMessages = [];

        foreach ($this->_rules as $elementName => $rules) {
            $activeRules[$elementName] = [];
            $jqueryMessages[$elementName] = [];
            foreach ($rules as $rule) {
                if ('client' == $rule['validation'] || $this->forceClientValidation) {
                    unset($element);

                    $dependent = isset($rule['dependent']) && is_array($rule['dependent']);
                    $rule['message'] = strtr($rule['message'], $js_escape);

                    if (isset($rule['group'])) {
                        $group = $this->getElement($rule['group']);
                        // No JavaScript validation for frozen elements
                        if ($group->isFrozen()) {
                            continue 2;
                        }
                        $elements = &$group->getElements();
                        foreach (array_keys($elements) as $key) {
                            if ($elementName == $group->getElementName($key)) {
                                $element = &$elements[$key];
                                break;
                            }
                        }
                    } elseif ($dependent) {
                        $element = [];
                        $element[] = $this->getElement($elementName);
                        foreach ($rule['dependent'] as $elName) {
                            $element[] = $this->getElement($elName);
                        }
                    } else {
                        $element = $this->getElement($elementName);
                    }
                    // No JavaScript validation for frozen elements
                    if (is_object($element) && $element->isFrozen()) {
                        continue 2;
                    } elseif (is_array($element)) {
                        foreach (array_keys($element) as $key) {
                            if ($element[$key]->isFrozen()) {
                                continue 3;
                            }
                        }
                    }

                    $activeRules[$elementName][] = $rule;
                }
            }
        }

        return $activeRules;
    }


    public function hasRequiredFields()
    {
        return $this->hasRequiredFields;
    }
}
