vendor/codefog/contao-haste/library/Haste/Form/Form.php line 406

Open in your IDE?
  1. <?php
  2. /**
  3.  * Haste utilities for Contao Open Source CMS
  4.  *
  5.  * Copyright (C) 2012-2013 Codefog & terminal42 gmbh
  6.  *
  7.  * @package    Haste
  8.  * @link       http://github.com/codefog/contao-haste/
  9.  * @license    http://opensource.org/licenses/lgpl-3.0.html LGPL
  10.  */
  11. namespace Haste\Form;
  12. use Contao\Controller;
  13. use Contao\Date;
  14. use Contao\Environment;
  15. use Contao\FormFieldModel;
  16. use Contao\FormHidden;
  17. use Contao\FrontendTemplate;
  18. use Contao\Input;
  19. use Contao\Model;
  20. use Contao\PageModel;
  21. use Contao\System;
  22. use Contao\TemplateLoader;
  23. use Contao\Widget;
  24. use Haste\Form\Validator\ValidatorInterface;
  25. use Haste\Generator\RowClass;
  26. use Haste\Util\ArrayPosition;
  27. class Form extends Controller
  28. {
  29.     /**
  30.      * State of the form
  31.      * Can be either clean or dirty
  32.      */
  33.     const STATE_CLEAN 0;
  34.     const STATE_DIRTY 1;
  35.     /**
  36.      * Form ID
  37.      * @var string
  38.      */
  39.     protected $strFormId;
  40.     /**
  41.      * HTTP Method
  42.      * @var string Can be either GET or POST
  43.      */
  44.     protected $strMethod;
  45.     /**
  46.      * Form action
  47.      * @var string
  48.      */
  49.     protected $strFormAction;
  50.     /**
  51.      * True if the form has been submitted
  52.      * @var boolean
  53.      */
  54.     protected $blnSubmitted;
  55.     /**
  56.      * Render forms tableless
  57.      * @var boolean
  58.      */
  59.     protected $blnTableless true;
  60.     /**
  61.      * True if the form has uploads
  62.      * @var boolean
  63.      */
  64.     protected $blnHasUploads false;
  65.     /**
  66.      * True if the HTML5 validation should be ignored
  67.      * @var bool
  68.      */
  69.     protected $blnNoValidate false;
  70.     /**
  71.      * Form fields in the representation AFTER the Widget::getAttributesFromDca() call
  72.      * @var array
  73.      */
  74.     protected $arrFormFields = array();
  75.     /**
  76.      * Widget instances
  77.      * @var Widget[]
  78.      */
  79.     protected $arrWidgets = array();
  80.     /**
  81.      * Bound model
  82.      * @var Model
  83.      */
  84.     protected $objModel null;
  85.     /**
  86.      * Validators
  87.      * @var array
  88.      */
  89.     protected $arrValidators = array();
  90.     /**
  91.      * Enctype
  92.      * @var string
  93.      */
  94.     protected $strEnctype 'application/x-www-form-urlencoded';
  95.     /**
  96.      * Current form state
  97.      * @var int
  98.      */
  99.     protected $intState self::STATE_CLEAN;
  100.     /**
  101.      * Check if form is valid
  102.      * @var boolean
  103.      */
  104.     protected $blnValid true;
  105.     /**
  106.      * Input callback
  107.      * @var callable
  108.      */
  109.     protected $inputCallback;
  110.     /**
  111.      * Initialize the form
  112.      *
  113.      * @param string   $strId          The ID of the form
  114.      * @param string   $strMethod      The HTTP Method GET or POST
  115.      * @param callable $varSubmitCheck A callable that checks if the form has been submitted
  116.      * @param boolean  $blnTableless   Whether to render the form tableless or not
  117.      *
  118.      * @throws \InvalidArgumentException
  119.      */
  120.     public function __construct($strId$strMethod$varSubmitCheck$blnTableless true)
  121.     {
  122.         parent::__construct();
  123.         if (is_numeric($strId)) {
  124.             throw new \InvalidArgumentException('You cannot use a numeric form id.');
  125.         }
  126.         $this->strFormId $strId;
  127.         if (!in_array($strMethod, array('GET''POST'), true)) {
  128.             throw new \InvalidArgumentException('The method has to be either GET or POST.');
  129.         }
  130.         if (!is_callable($varSubmitCheck)) {
  131.             throw new \InvalidArgumentException('The submit check must be callable.');
  132.         }
  133.         $this->strMethod $strMethod;
  134.         $this->blnSubmitted call_user_func($varSubmitCheck$this);
  135.         $this->blnTableless $blnTableless;
  136.         // The form action can be set using several helper methods but by default it's just
  137.         // pointing to the current page
  138.         $this->strFormAction Environment::get('request');
  139.     }
  140.     /**
  141.      * Set the form action directly
  142.      *
  143.      * @param string $strUri The URI
  144.      *
  145.      * @return $this
  146.      */
  147.     public function setFormActionFromUri($strUri)
  148.     {
  149.         $this->strFormAction $strUri;
  150.         return $this;
  151.     }
  152.     /**
  153.      * Set the form action from a Contao page ID
  154.      *
  155.      * @param   int $intId The page ID
  156.      *
  157.      * @return   $this
  158.      * @throws  \InvalidArgumentException
  159.      */
  160.     public function setFormActionFromPageId($intId)
  161.     {
  162.         if (($objPage PageModel::findWithDetails($intId)) === null) {
  163.             throw new \InvalidArgumentException(sprintf('The page id "%s" does apparently not exist!'$intId));
  164.         }
  165.         $this->strFormAction Controller::generateFrontendUrl($objPage->row(), null$objPage->language);
  166.         return $this;
  167.     }
  168.     /**
  169.      * Preserve the current GET parameters by adding them as hidden fields
  170.      *
  171.      * @param array $arrExclude
  172.      */
  173.     public function preserveGetParameters($arrExclude = array())
  174.     {
  175.         foreach ($_GET as $k => $v) {
  176.             if (in_array($k$arrExcludefalse)) {
  177.                 continue;
  178.             }
  179.             if (array_key_exists($k$this->arrFormFields)) {
  180.                 continue;
  181.             }
  182.             $this->addFormField($k, array(
  183.                 'inputType' => 'hidden',
  184.                 'value' => Input::get($k)
  185.             ));
  186.         }
  187.     }
  188.     /**
  189.      * Get form method
  190.      *
  191.      * @return string
  192.      */
  193.     public function getMethod()
  194.     {
  195.         return $this->strMethod;
  196.     }
  197.     /**
  198.      * Get the form action
  199.      *
  200.      * @return string
  201.      */
  202.     public function getFormAction()
  203.     {
  204.         return $this->strFormAction;
  205.     }
  206.     /**
  207.      * Gets the form ID
  208.      *
  209.      * @return string The form ID
  210.      */
  211.     public function getFormId()
  212.     {
  213.         return $this->strFormId;
  214.     }
  215.     /**
  216.      * Gets the encoding type
  217.      *
  218.      * @return  string
  219.      */
  220.     public function getEnctype()
  221.     {
  222.         return $this->strEnctype;
  223.     }
  224.     /**
  225.      * Get novalidate flag
  226.      *
  227.      * @return bool
  228.      */
  229.     public function isNoValidate()
  230.     {
  231.         return $this->blnNoValidate;
  232.     }
  233.     /**
  234.      * Generate the novalidate attribute
  235.      *
  236.      * @return string
  237.      */
  238.     public function generateNoValidate()
  239.     {
  240.         return $this->isNoValidate() ? ' novalidate' '';
  241.     }
  242.     /**
  243.      * Set novalidate flag
  244.      *
  245.      * @param bool $blnNoValidate
  246.      */
  247.     public function setNoValidate($blnNoValidate)
  248.     {
  249.         $this->blnNoValidate = (bool) $blnNoValidate;
  250.     }
  251.     /**
  252.      * Get tableless flag
  253.      *
  254.      * @return bool
  255.      */
  256.     public function isTableless()
  257.     {
  258.         return $this->blnTableless;
  259.     }
  260.     /**
  261.      * Set tabeless flag
  262.      *
  263.      * @param bool $blnTableless
  264.      *
  265.      * @return $this
  266.      */
  267.     public function setTableless($blnTableless)
  268.     {
  269.         $this->blnTableless = (bool) $blnTableless;
  270.         $this->intState self::STATE_DIRTY;
  271.         return $this;
  272.     }
  273.     /**
  274.      * @param bool $blnSubmitted
  275.      *
  276.      * @return Form
  277.      */
  278.     public function setSubmitted($blnSubmitted)
  279.     {
  280.         $this->blnSubmitted = (bool) $blnSubmitted;
  281.         return $this;
  282.     }
  283.     /**
  284.      * Check if the form has been submitted
  285.      *
  286.      * @return bool
  287.      */
  288.     public function isSubmitted()
  289.     {
  290.         return (bool) $this->blnSubmitted;
  291.     }
  292.     /**
  293.      * Check if the form is valid (no widget has an error)
  294.      *
  295.      * @return  bool
  296.      */
  297.     public function isValid()
  298.     {
  299.         return (bool) $this->blnValid;
  300.     }
  301.     /**
  302.      * Check if form is dirty (widgets need to be generated)
  303.      *
  304.      * @return bool
  305.      */
  306.     public function isDirty()
  307.     {
  308.         return (bool) ($this->intState === static::STATE_DIRTY);
  309.     }
  310.     /**
  311.      * Check if there are uploads
  312.      *
  313.      * @return  bool
  314.      */
  315.     public function hasUploads()
  316.     {
  317.         // We need to create the widgets to know if we have uploads
  318.         $this->createWidgets();
  319.         return (bool) $this->blnHasUploads;
  320.     }
  321.     /**
  322.      * Check if form has fields
  323.      *
  324.      * @return bool
  325.      */
  326.     public function hasFields()
  327.     {
  328.         return !empty($this->arrFormFields);
  329.     }
  330.     /**
  331.      * Set a callback to fetch the widget input instead of using getPost()
  332.      *
  333.      * @param callable|null $callback The callback
  334.      *
  335.      * @return $this The widget object
  336.      */
  337.     public function setInputCallback(callable $callback null)
  338.     {
  339.         $this->inputCallback $callback;
  340.         return $this;
  341.     }
  342.     /**
  343.      * Adds a form field
  344.      *
  345.      * @param string        $strName the form field name
  346.      * @param array         $arrDca The DCA representation of the field
  347.      * @param ArrayPosition $position
  348.      *
  349.      * @return $this
  350.      */
  351.     public function addFormField($strName, array $arrDcaArrayPosition $position null)
  352.     {
  353.         $this->checkFormFieldNameIsValid($strName);
  354.         if (null === $position) {
  355.             $position ArrayPosition::last();
  356.         }
  357.         // Make sure it has a "name" attribute because it is mandatory
  358.         if (!isset($arrDca['name'])) {
  359.             $arrDca['name'] = $strName;
  360.         }
  361.         // Support default values
  362.         if (!$this->isSubmitted()) {
  363.             if (isset($arrDca['default']) && !isset($arrDca['value'])) {
  364.                 $arrDca['value'] = $arrDca['default'];
  365.             }
  366.             // Try to load the default value from bound Model
  367.             if (!($arrDca['ignoreModelValue'] ?? false) && $this->objModel !== null) {
  368.                 $arrDca['value'] = $this->objModel->$strName;
  369.             }
  370.         }
  371.         if (!isset($arrDca['inputType'])) {
  372.             throw new \RuntimeException(sprintf('You did not specify any inputType for the field "%s"!'$strName));
  373.         }
  374.         /** @type Widget $strClass */
  375.         $strClass $GLOBALS['TL_FFL'][$arrDca['inputType']];
  376.         if (!class_exists($strClass)) {
  377.             throw new \RuntimeException(sprintf('The class "%s" for type "%s" could not be found.'$strClass$arrDca['inputType']));
  378.         }
  379.         // Convert date formats into timestamps
  380.         $rgxp $arrDca['eval']['rgxp'] ?? null;
  381.         if (in_array($rgxp, array('date''time''datim'), true)) {
  382.             $this->addValidator($strName, function($varValue) use ($rgxp) {
  383.                 if ($varValue != '') {
  384.                     $key $rgxp 'Format';
  385.                     $format = isset($GLOBALS['objPage']) ? $GLOBALS['objPage']->{$key} : $GLOBALS['TL_CONFIG'][$key];
  386.                     $objDate = new Date($varValue$format);
  387.                     $varValue $objDate->tstamp;
  388.                 }
  389.                 return $varValue;
  390.             });
  391.         }
  392.         if (is_array($arrDca['save_callback'] ?? null)) {
  393.             $this->addValidator(
  394.                 $strName,
  395.                 function($varValueWidget $objWidgetForm $objForm) use ($arrDca$strName) {
  396.                     $objModel $objForm->getBoundModel();
  397.                     $dc = (object) array(
  398.                         'id'            => $objModel $objModel->id 0,
  399.                         'table'         => $objModel $objModel::getTable() : '',
  400.                         'value'         => $varValue,
  401.                         'field'         => $strName,
  402.                         'inputName'     => $objWidget->name,
  403.                         'activeRecord'  => $objModel
  404.                     );
  405.                     foreach ($arrDca['save_callback'] as $callback) {
  406.                         if (is_array($callback)) {
  407.                             $objCallback System::importStatic($callback[0]);
  408.                             $varValue $objCallback->{$callback[1]}($varValue$dc);
  409.                         } elseif (is_callable($callback)) {
  410.                             $varValue $callback($varValue$dc);
  411.                         }
  412.                     }
  413.                     return $varValue;
  414.                 }
  415.             );
  416.         }
  417.         $objModel $this->getBoundModel();
  418.         $dc = (object) array(
  419.             'id'            => $objModel $objModel->id 0,
  420.             'table'         => $objModel $objModel::getTable() : '',
  421.             'value'         => $arrDca['value'] ?? null,
  422.             'field'         => $strName,
  423.             'inputName'     => $arrDca['name'],
  424.             'activeRecord'  => $objModel
  425.         );
  426.         // Preserve the label
  427.         $strLabel $arrDca['label'] ?? null;
  428.         $arrDca $strClass::getAttributesFromDca($arrDca$arrDca['name'], $arrDca['value'] ?? null$strName$dc->table$dc);
  429.         // Reset the ID to the field name
  430.         $arrDca['id'] = $strName;
  431.         // Remove the label if it was not set â€“ Contao will set it to field name if it's not present
  432.         if (!isset($strLabel) || !$strLabel) {
  433.             $arrDca['label'] = '';
  434.         }
  435.         // Convert optgroups so they work with FormSelectMenu
  436.         if (is_array($arrDca['options'] ?? null) && array_is_assoc($arrDca['options'])) {
  437.             $arrOptions $arrDca['options'];
  438.             $arrDca['options'] = array();
  439.             foreach ($arrOptions as $k => $v) {
  440.                 if (isset($v['label'])) {
  441.                     $arrDca['options'][] = $v;
  442.                 } else {
  443.                     $arrDca['options'][] = array(
  444.                         'label'     => $k,
  445.                         'value'     => $k,
  446.                         'group'     => '1',
  447.                     );
  448.                     foreach ($v as $vv) {
  449.                         $arrDca['options'][] = $vv;
  450.                     }
  451.                 }
  452.             }
  453.         }
  454.         $this->arrFormFields $position->addToArray($this->arrFormFields, array($strName=>$arrDca));
  455.         $this->intState self::STATE_DIRTY;
  456.         return $this;
  457.     }
  458.     /**
  459.      * Add multiple form fields
  460.      *
  461.      * @param array         $arrFormFields
  462.      * @param ArrayPosition $position
  463.      *
  464.      * @return $this
  465.      */
  466.     public function addFormFields(array $arrFormFieldsArrayPosition $position null)
  467.     {
  468.         if (null !== $position && ($position->position() === ArrayPosition::FIRST || $position->position() === ArrayPosition::BEFORE)) {
  469.             $arrFormFields array_reverse($arrFormFieldstrue);
  470.         }
  471.         foreach ($arrFormFields as $strName => $arrDca) {
  472.             $this->addFormField($strName$arrDca$position);
  473.         }
  474.         return $this;
  475.     }
  476.     /**
  477.      * Binds a model instance to the form. If there is data, haste form will add
  478.      * the present values as default values.
  479.      *
  480.      * @param Model
  481.      *
  482.      * @return $this
  483.      */
  484.     public function bindModel(Model $objModel null)
  485.     {
  486.         $this->objModel $objModel;
  487.         return $this;
  488.     }
  489.     /**
  490.      * Gets the bound model
  491.      *
  492.      * @return Model
  493.      */
  494.     public function getBoundModel()
  495.     {
  496.         return $this->objModel;
  497.     }
  498.     /**
  499.      * Add the Contao hidden fields FORM_SUBMIT and REQUEST_TOKEN
  500.      */
  501.     public function addContaoHiddenFields()
  502.     {
  503.         $this->addFormField('FORM_SUBMIT', array(
  504.             'name' => 'FORM_SUBMIT',
  505.             'inputType' => 'hidden',
  506.             'ignoreModelValue' => true,
  507.             'value' => $this->getFormId()
  508.         ));
  509.         $tokenName System::getContainer()->getParameter('contao.csrf_token_name');
  510.         $tokenManager System::getContainer()->has('contao.csrf.token_manager') ? System::getContainer()->get('contao.csrf.token_manager') : System::getContainer()->get('security.csrf.token_manager');
  511.         $this->addFormField('REQUEST_TOKEN', array(
  512.             'name' => 'REQUEST_TOKEN',
  513.             'inputType' => 'hidden',
  514.             'ignoreModelValue' => true,
  515.             'value' => $tokenManager->getToken($tokenName)->getValue()
  516.         ));
  517.     }
  518.     /**
  519.      * Helper method to easily add a captcha field
  520.      *
  521.      * @param string        $strName The form field name
  522.      * @param ArrayPosition $position
  523.      */
  524.     public function addCaptchaFormField($strNameArrayPosition $position null)
  525.     {
  526.         $this->addFormField($strName, array(
  527.             'name'      => $strName '_' $this->strFormId// make sure they're unique on a page
  528.             'label'     => $GLOBALS['TL_LANG']['MSC']['securityQuestion'],
  529.             'inputType' => 'captcha',
  530.             'eval'      => array('mandatory'=>true)
  531.         ), $position);
  532.     }
  533.     /**
  534.      * Helper method to easily add a submit field
  535.      *
  536.      * @param string        $strName  The form field name
  537.      * @param string        $strLabel The label for the submit button
  538.      * @param ArrayPosition $position
  539.      */
  540.     public function addSubmitFormField($strName$strLabelArrayPosition $position null)
  541.     {
  542.         $this->addFormField($strName, array(
  543.             'name'      => $strName,
  544.             'label'     => $strLabel,
  545.             'inputType' => 'submit'
  546.         ), $position);
  547.     }
  548.     /**
  549.      * Add form fields from a back end DCA
  550.      *
  551.      * @param string   $strTable    The DCA table name
  552.      * @param callable $varCallback Called for each field, return true if you want to include the field in the form
  553.      *
  554.      * @return $this
  555.      */
  556.     public function addFieldsFromDca($strTable$varCallback null)
  557.     {
  558.         System::loadLanguageFile($strTable);
  559.         $this->loadDataContainer($strTable);
  560.         $arrFields = &$GLOBALS['TL_DCA'][$strTable]['fields'];
  561.         foreach (($arrFields ?? []) as $k => $v) {
  562.             if (is_callable($varCallback) && !call_user_func_array($varCallback, array(&$k, &$v))) {
  563.                 continue;
  564.             }
  565.             $this->addFormField($k$v);
  566.         }
  567.         return $this;
  568.     }
  569.     /**
  570.      * Return true if the field has "inputType" set, false otherwise
  571.      *
  572.      * This is a default callback that can be used with addFieldsFromDca() method. It prevents from adding fields
  573.      * that do not have inputType specified which would result in an exception. The fields you typically would
  574.      * like to skip are: id, tstamp, pid, sorting.
  575.      *
  576.      * @param string $field
  577.      * @param array  $dca
  578.      *
  579.      * @return bool
  580.      */
  581.     public function skipFieldsWithoutInputType($field, array $dca)
  582.     {
  583.         return isset($dca['inputType']);
  584.     }
  585.     /**
  586.      * Add form fields from a back end form generator form ID
  587.      *
  588.      * @param int      $intId       The form generator form ID
  589.      * @param callable $varCallback Called for each field, return true if you want to include the field in the form
  590.      *
  591.      * @return $this
  592.      * @throws \InvalidArgumentException
  593.      */
  594.     public function addFieldsFromFormGenerator($intId$varCallback null)
  595.     {
  596.         if (($objFields FormFieldModel::findPublishedByPid($intId)) === null) {
  597.             throw new \InvalidArgumentException('Form ID "' $intId '" does not exist or has no published fields.');
  598.         }
  599.         while ($objFields->next()) {
  600.             // make sure "name" is set because not all form fields do need it and it would thus overwrite the array indexes
  601.             $strName $objFields->name ?: 'field_' $objFields->id;
  602.             $this->checkFormFieldNameIsValid($strName);
  603.             $arrDca $objFields->row();
  604.             // Make sure it has a "name" attribute because it is mandatory
  605.             if (!isset($arrDca['name'])) {
  606.                 $arrDca['name'] = $strName;
  607.             }
  608.             if (is_callable($varCallback) && !call_user_func_array($varCallback, array(&$strName, &$arrDca))) {
  609.                 continue;
  610.             }
  611.             $this->arrFormFields[$strName] = $arrDca;
  612.         }
  613.         $this->intState self::STATE_DIRTY;
  614.         return $this;
  615.     }
  616.     /**
  617.      * Adds a form field from the form generator without trying to convert a DCA configuration.
  618.      *
  619.      * @param string             $strName
  620.      * @param array              $arrDca
  621.      * @param ArrayPosition|null $position
  622.      */
  623.     public function addFieldFromFormGenerator($strName, array $arrDcaArrayPosition $position null)
  624.     {
  625.         $this->checkFormFieldNameIsValid($strName);
  626.         if (null === $position) {
  627.             $position ArrayPosition::last();
  628.         }
  629.         // make sure "name" is set because not all form fields do need it and it would thus overwrite the array indexes
  630.         $strName $arrDca['name'] ?: 'field_' $arrDca['id'];
  631.         // Make sure it has a "name" attribute because it is mandatory
  632.         if (!isset($arrDca['name'])) {
  633.             $arrDca['name'] = $strName;
  634.         }
  635.         $this->arrFormFields $position->addToArray($this->arrFormFields, array($strName=>$arrDca));
  636.         $this->intState self::STATE_DIRTY;
  637.     }
  638.     /**
  639.      * Get a form field by a given name
  640.      *
  641.      * @param string $strName The form field name
  642.      *
  643.      * @return array
  644.      */
  645.     public function getFormField($strName)
  646.     {
  647.         return $this->arrFormFields[$strName];
  648.     }
  649.     /**
  650.      * Get all form fields
  651.      *
  652.      * @return array
  653.      */
  654.     public function getFormFields()
  655.     {
  656.         return $this->arrFormFields;
  657.     }
  658.     /**
  659.      * Removes a form field
  660.      *
  661.      * @param string $strName The form field name
  662.      *
  663.      * @return $this
  664.      */
  665.     public function removeFormField($strName)
  666.     {
  667.         unset($this->arrFormFields[$strName]);
  668.         $this->intState self::STATE_DIRTY;
  669.         return $this;
  670.     }
  671.     /**
  672.      * Checks if there is a form field with a given name
  673.      *
  674.      * @param string $strName The form field name
  675.      *
  676.      * @return bool
  677.      */
  678.     public function hasFormField($strName)
  679.     {
  680.         return isset($this->arrFormFields[$strName]);
  681.     }
  682.     /**
  683.      * Returns a widget instance if existing
  684.      *
  685.      * @param string $strName The form field name
  686.      *
  687.      * @return Widget
  688.      */
  689.     public function getWidget($strName)
  690.     {
  691.         $this->createWidgets();
  692.         return $this->arrWidgets[$strName];
  693.     }
  694.     /**
  695.      * Return all widgets
  696.      *
  697.      * @return array
  698.      */
  699.     public function getWidgets()
  700.     {
  701.         $this->createWidgets();
  702.         return $this->arrWidgets;
  703.     }
  704.     /**
  705.      * Add a validator to the form field
  706.      *
  707.      * @param string                      $strName      The form field name
  708.      * @param ValidatorInterface|callable $varValidator An instance of ValidatorInterface or a callable that will be called on widget validation
  709.      *
  710.      * @return $this
  711.      * @throws \InvalidArgumentException
  712.      */
  713.     public function addValidator($strName$varValidator)
  714.     {
  715.         if ($varValidator instanceof ValidatorInterface || is_callable($varValidator)) {
  716.             $this->arrValidators[$strName][] = $varValidator;
  717.         } else {
  718.             throw new \InvalidArgumentException('Your validator is invalid!');
  719.         }
  720.         return $this;
  721.     }
  722.     /**
  723.      * Create the widget instances
  724.      *
  725.      * @return $this
  726.      * @throws \RuntimeException
  727.      */
  728.     public function createWidgets()
  729.     {
  730.         // Do nothing if already generated
  731.         if (!$this->isDirty()) {
  732.             return $this;
  733.         }
  734.         $i 0;
  735.         // Reset to initial values
  736.         $this->arrWidgets = array();
  737.         $this->blnHasUploads false;
  738.         // Initialize widgets
  739.         foreach ($this->arrFormFields as $strName => $arrField) {
  740.             $strClass $GLOBALS['TL_FFL'][$arrField['type']] ?? '';
  741.             if (!class_exists($strClass)) {
  742.                 throw new \RuntimeException(sprintf('The class "%s" for type "%s" could not be found.'$strClass$arrField['type']));
  743.             }
  744.             $arrField['tableless']  = $this->blnTableless;
  745.             // Some widgets render the mandatory asterisk only based on "require" attribute
  746.             if (!isset($arrField['required'])) {
  747.                 $arrField['required'] = (bool) ($arrField['mandatory'] ?? false);
  748.             }
  749.             $objWidget = new $strClass($arrField);
  750.             if ($objWidget instanceof \uploadable) {
  751.                 $this->blnHasUploads true;
  752.             }
  753.             $this->arrWidgets[$strName] = $objWidget;
  754.             $i++;
  755.         }
  756.         RowClass::withKey('rowClass')->addCount('row_')->addFirstLast('row_')->addEvenOdd()->applyTo($this->arrWidgets);
  757.         $this->intState self::STATE_CLEAN;
  758.         if ($this->hasUploads()) {
  759.             if ('GET' === $this->getMethod()) {
  760.                 throw new \RuntimeException('How do you want me to upload your file using GET?');
  761.             }
  762.             $this->strEnctype 'multipart/form-data';
  763.         } else {
  764.             $this->strEnctype 'application/x-www-form-urlencoded';
  765.         }
  766.         return $this;
  767.     }
  768.     /**
  769.      * Validate the form
  770.      *
  771.      * @return bool
  772.      */
  773.     public function validate()
  774.     {
  775.         if (!$this->isSubmitted()) {
  776.             return false;
  777.         }
  778.         $this->createWidgets();
  779.         $this->blnValid true;
  780.         foreach ($this->arrWidgets as $strName => $objWidget) {
  781.             if (null !== $this->inputCallback && method_exists($objWidget'setInputCallback')) {
  782.                 $objWidget->setInputCallback($this->inputCallback);
  783.             }
  784.             $objWidget->validate();
  785.             if ($objWidget->hasErrors()) {
  786.                 $this->blnValid false;
  787.             } elseif ($objWidget->submitInput()) {
  788.                 $varValue $objWidget->value;
  789.                 // Run custom validators
  790.                 if (isset($this->arrValidators[$strName])) {
  791.                     try {
  792.                         foreach ($this->arrValidators[$strName] as $varValidator) {
  793.                             if ($varValidator instanceof ValidatorInterface) {
  794.                                 $varValue $varValidator->validate($varValue$objWidget$this);
  795.                             } else {
  796.                                 $varValue call_user_func($varValidator$varValue$objWidget$this);
  797.                             }
  798.                         }
  799.                     } catch (\Exception $e) {
  800.                         $objWidget->class 'error';
  801.                         $objWidget->addError($e->getMessage());
  802.                         $this->blnValid false;
  803.                     }
  804.                 }
  805.                 /** @noinspection NotOptimalIfConditionsInspection */
  806.                 if ($objWidget->hasErrors()) {
  807.                     // Re-check the status in case a custom validator has added an error
  808.                     $this->blnValid false;
  809.                 } elseif ($this->objModel !== null) {
  810.                     // Bind to Model instance
  811.                     $this->objModel->$strName =  $varValue;
  812.                 }
  813.             }
  814.         }
  815.         return $this->isValid();
  816.     }
  817.     /**
  818.      * Add form to a template
  819.      *
  820.      * @param FrontendTemplate $objTemplate
  821.      *
  822.      * @return $this
  823.      */
  824.     public function addToTemplate(FrontendTemplate $objTemplate)
  825.     {
  826.         return $this->addToObject($objTemplate);
  827.     }
  828.     /**
  829.      * Add form to a object
  830.      *
  831.      * @param object $objObject
  832.      *
  833.      * @return $this
  834.      */
  835.     public function addToObject($objObject)
  836.     {
  837.         $this->createWidgets();
  838.         $tokenName System::getContainer()->getParameter('contao.csrf_token_name');
  839.         $tokenManager System::getContainer()->has('contao.csrf.token_manager') ? System::getContainer()->get('contao.csrf.token_manager') : System::getContainer()->get('security.csrf.token_manager');
  840.         $objObject->action $this->getFormAction();
  841.         $objObject->formId $this->getFormId();
  842.         $objObject->requestToken $tokenManager->getToken($tokenName)->getValue();
  843.         $objObject->method strtolower($this->getMethod());
  844.         $objObject->enctype $this->getEnctype();
  845.         $objObject->widgets $this->arrWidgets;
  846.         $objObject->valid $this->isValid();
  847.         $objObject->submitted $this->isSubmitted();
  848.         $objObject->hasUploads $this->hasUploads();
  849.         $objObject->novalidate $this->generateNoValidate();
  850.         $objObject->tableless $this->blnTableless;
  851.         /** @type Widget $objWidget */
  852.         $arrWidgets $this->splitHiddenAndVisibleWidgets();
  853.         $objObject->hidden '';
  854.         // Generate hidden form fields
  855.         foreach ((array) ($arrWidgets['hidden'] ?? []) as $objWidget) {
  856.             $objObject->hidden .= $objWidget->parse();
  857.         }
  858.         $objObject->fields '';
  859.         // Generate visible form fields
  860.         foreach ((array) $arrWidgets['visible'] as $objWidget) {
  861.             $objObject->fields .= $objWidget->parse();
  862.         }
  863.         $objObject->hiddenWidgets  $arrWidgets['hidden'] ?? [];
  864.         $objObject->visibleWidgets $arrWidgets['visible'] ?? [];
  865.         $objObject->hasteFormInstance $this;
  866.         return $this;
  867.     }
  868.     /**
  869.      * Get the helper object
  870.      *
  871.      * @return \stdClass
  872.      */
  873.     public function getHelperObject()
  874.     {
  875.         $helper = new \stdClass();
  876.         $this->addToObject($helper);
  877.         return $helper;
  878.     }
  879.     /**
  880.      * Generate a form and return it as HTML string
  881.      *
  882.      * @param string|null $templateName The form wrapper template name or null to auto-select (based on Contao version).
  883.      *
  884.      * @return string
  885.      */
  886.     public function generate($templateName null)
  887.     {
  888.         if (null === $templateName) {
  889.             $templateName 'form';
  890.             try {
  891.                 TemplateLoader::getPath($templateName'html5');
  892.             } catch (\Exception $e) {
  893.                 $templateName 'form_wrapper';
  894.             }
  895.         }
  896.         $objTemplate = new FrontendTemplate($templateName);
  897.         $objTemplate->class 'hasteform_' $this->getFormId();
  898.         $objTemplate->formSubmit $this->getFormId();
  899.         $this->addToTemplate($objTemplate);
  900.         return $objTemplate->parse();
  901.     }
  902.     /**
  903.      * Return the submitted data of a specific form field
  904.      *
  905.      * @param string $strName The form field name
  906.      *
  907.      * @return mixed    The value of the widget
  908.      * @throws \BadMethodCallException
  909.      * @throws \InvalidArgumentException
  910.      */
  911.     public function fetch($strName)
  912.     {
  913.         if (!$this->isSubmitted()) {
  914.             throw new \BadMethodCallException('How do you want me to fetch data from an unsubmitted form?');
  915.         }
  916.         if ($this->getMethod() !== 'POST') {
  917.             throw new \BadMethodCallException('Widgets only support fetching POST values. Use the Contao Input class for other purposes.');
  918.         }
  919.         if (!isset($this->arrWidgets[$strName])) {
  920.             throw new \InvalidArgumentException('The widget with name "' $strName '" does not exist.');
  921.         }
  922.         $objWidget $this->arrWidgets[$strName];
  923.         if (!$objWidget->submitInput()) {
  924.             // Do not throw exception here for BC
  925.             return null;
  926.         }
  927.         return $objWidget->value;
  928.     }
  929.     /**
  930.      * Return the submitted data as an associative array
  931.      *
  932.      * @param callable $varCallback A callable that should be used to fetch the data instead of the built in functionality
  933.      *
  934.      * @return array
  935.      */
  936.     public function fetchAll($varCallback null)
  937.     {
  938.         $arrData = array();
  939.         foreach ($this->arrWidgets as $strName => $objWidget) {
  940.             // Do not check $objWidget->submitInput() here because the callback could handle it differently
  941.             if (is_callable($varCallback)) {
  942.                 $varValue call_user_func($varCallback$strName$objWidget);
  943.             } else {
  944.                 $varValue $this->fetch($strName);
  945.             }
  946.             if (null !== $varValue) {
  947.                 $arrData[$strName] = $varValue;
  948.             }
  949.         }
  950.         return $arrData;
  951.     }
  952.     /**
  953.      * Generate a form and return it as HTML string
  954.      *
  955.      * @return string
  956.      */
  957.     public function __toString()
  958.     {
  959.         return $this->generate();
  960.     }
  961.     /**
  962.      * Check for a valid form field name
  963.      *
  964.      * @param string $strName The form field name
  965.      *
  966.      * @throws \InvalidArgumentException
  967.      */
  968.     protected function checkFormFieldNameIsValid($strName)
  969.     {
  970.         if (is_numeric($strName)) {
  971.             throw new \InvalidArgumentException('You cannot use a numeric form field name.');
  972.         }
  973.         if (in_array($strNamearray_keys($this->arrFormFields), true)) {
  974.             throw new \InvalidArgumentException(sprintf('"%s" has already been added to the form.'$strName));
  975.         }
  976.     }
  977.     /**
  978.      * Splits hidden and visible widgets
  979.      *
  980.      * @return array
  981.      */
  982.     protected function splitHiddenAndVisibleWidgets()
  983.     {
  984.         $arrResult = array();
  985.         foreach ($this->arrWidgets as $k => $objWidget) {
  986.             $strKey = ($objWidget instanceof FormHidden) ? 'hidden' 'visible';
  987.             $arrResult[$strKey][$k] = $objWidget;
  988.         }
  989.         return $arrResult;
  990.     }
  991. }