CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Team
    • Issues (Github)
    • YouTube Channel
    • Get Involved
    • Bakery
    • Featured Resources
    • Newsletter
    • Certification
    • My CakePHP
    • CakeFest
    • Facebook
    • Twitter
    • Help & Support
    • Forum
    • Stack Overflow
    • IRC
    • Slack
    • Paid Support
CakePHP

C CakePHP 3.8 Red Velvet API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 3.8
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Namespaces

  • Cake
    • Auth
      • Storage
    • Cache
      • Engine
    • Collection
      • Iterator
    • Command
    • Console
      • Exception
    • Controller
      • Component
      • Exception
    • Core
      • Configure
        • Engine
      • Exception
      • Retry
    • Database
      • Driver
      • Exception
      • Expression
      • Schema
      • Statement
      • Type
    • Datasource
      • Exception
    • Error
      • Middleware
    • Event
      • Decorator
    • Filesystem
    • Form
    • Http
      • Client
        • Adapter
        • Auth
      • Cookie
      • Exception
      • Middleware
      • Session
    • I18n
      • Formatter
      • Middleware
      • Parser
    • Log
      • Engine
    • Mailer
      • Exception
      • Transport
    • Network
      • Exception
    • ORM
      • Association
      • Behavior
        • Translate
      • Exception
      • Locator
      • Rule
    • Routing
      • Exception
      • Filter
      • Middleware
      • Route
    • Shell
      • Helper
      • Task
    • TestSuite
      • Fixture
      • Stub
    • Utility
      • Exception
    • Validation
    • View
      • Exception
      • Form
      • Helper
      • Widget
  • None

Classes

  • BreadcrumbsHelper
  • FlashHelper
  • FormHelper
  • HtmlHelper
  • NumberHelper
  • PaginatorHelper
  • RssHelper
  • SessionHelper
  • TextHelper
  • TimeHelper
  • UrlHelper

Traits

  • IdGeneratorTrait
  • SecureFieldTokenTrait
   1: <?php
   2: /**
   3:  * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
   4:  * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
   5:  *
   6:  * Licensed under The MIT License
   7:  * For full copyright and license information, please see the LICENSE.txt
   8:  * Redistributions of files must retain the above copyright notice.
   9:  *
  10:  * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11:  * @link          https://cakephp.org CakePHP(tm) Project
  12:  * @since         0.10.0
  13:  * @license       https://opensource.org/licenses/mit-license.php MIT License
  14:  */
  15: namespace Cake\View\Helper;
  16: 
  17: use Cake\Core\Configure;
  18: use Cake\Core\Exception\Exception;
  19: use Cake\Routing\Router;
  20: use Cake\Utility\Hash;
  21: use Cake\Utility\Inflector;
  22: use Cake\View\Form\ContextFactory;
  23: use Cake\View\Form\ContextInterface;
  24: use Cake\View\Helper;
  25: use Cake\View\StringTemplateTrait;
  26: use Cake\View\View;
  27: use Cake\View\Widget\WidgetLocator;
  28: use Cake\View\Widget\WidgetRegistry;
  29: use DateTime;
  30: use RuntimeException;
  31: use Traversable;
  32: 
  33: /**
  34:  * Form helper library.
  35:  *
  36:  * Automatic generation of HTML FORMs from given data.
  37:  *
  38:  * @method string text($fieldName, array $options = [])
  39:  * @method string number($fieldName, array $options = [])
  40:  * @method string email($fieldName, array $options = [])
  41:  * @method string password($fieldName, array $options = [])
  42:  * @method string search($fieldName, array $options = [])
  43:  * @property \Cake\View\Helper\HtmlHelper $Html
  44:  * @property \Cake\View\Helper\UrlHelper $Url
  45:  * @link https://book.cakephp.org/3.0/en/views/helpers/form.html
  46:  */
  47: class FormHelper extends Helper
  48: {
  49:     use IdGeneratorTrait;
  50:     use SecureFieldTokenTrait;
  51:     use StringTemplateTrait;
  52: 
  53:     /**
  54:      * Other helpers used by FormHelper
  55:      *
  56:      * @var array
  57:      */
  58:     public $helpers = ['Url', 'Html'];
  59: 
  60:     /**
  61:      * The various pickers that make up a datetime picker.
  62:      *
  63:      * @var array
  64:      */
  65:     protected $_datetimeParts = ['year', 'month', 'day', 'hour', 'minute', 'second', 'meridian'];
  66: 
  67:     /**
  68:      * Special options used for datetime inputs.
  69:      *
  70:      * @var array
  71:      */
  72:     protected $_datetimeOptions = [
  73:         'interval', 'round', 'monthNames', 'minYear', 'maxYear',
  74:         'orderYear', 'timeFormat', 'second'
  75:     ];
  76: 
  77:     /**
  78:      * Default config for the helper.
  79:      *
  80:      * @var array
  81:      */
  82:     protected $_defaultConfig = [
  83:         'idPrefix' => null,
  84:         'errorClass' => 'form-error',
  85:         'typeMap' => [
  86:             'string' => 'text',
  87:             'text' => 'textarea',
  88:             'uuid' => 'string',
  89:             'datetime' => 'datetime',
  90:             'timestamp' => 'datetime',
  91:             'date' => 'date',
  92:             'time' => 'time',
  93:             'boolean' => 'checkbox',
  94:             'float' => 'number',
  95:             'integer' => 'number',
  96:             'tinyinteger' => 'number',
  97:             'smallinteger' => 'number',
  98:             'decimal' => 'number',
  99:             'binary' => 'file',
 100:         ],
 101:         'templates' => [
 102:             // Used for button elements in button().
 103:             'button' => '<button{{attrs}}>{{text}}</button>',
 104:             // Used for checkboxes in checkbox() and multiCheckbox().
 105:             'checkbox' => '<input type="checkbox" name="{{name}}" value="{{value}}"{{attrs}}>',
 106:             // Input group wrapper for checkboxes created via control().
 107:             'checkboxFormGroup' => '{{label}}',
 108:             // Wrapper container for checkboxes.
 109:             'checkboxWrapper' => '<div class="checkbox">{{label}}</div>',
 110:             // Widget ordering for date/time/datetime pickers.
 111:             'dateWidget' => '{{year}}{{month}}{{day}}{{hour}}{{minute}}{{second}}{{meridian}}',
 112:             // Error message wrapper elements.
 113:             'error' => '<div class="error-message">{{content}}</div>',
 114:             // Container for error items.
 115:             'errorList' => '<ul>{{content}}</ul>',
 116:             // Error item wrapper.
 117:             'errorItem' => '<li>{{text}}</li>',
 118:             // File input used by file().
 119:             'file' => '<input type="file" name="{{name}}"{{attrs}}>',
 120:             // Fieldset element used by allControls().
 121:             'fieldset' => '<fieldset{{attrs}}>{{content}}</fieldset>',
 122:             // Open tag used by create().
 123:             'formStart' => '<form{{attrs}}>',
 124:             // Close tag used by end().
 125:             'formEnd' => '</form>',
 126:             // General grouping container for control(). Defines input/label ordering.
 127:             'formGroup' => '{{label}}{{input}}',
 128:             // Wrapper content used to hide other content.
 129:             'hiddenBlock' => '<div style="display:none;">{{content}}</div>',
 130:             // Generic input element.
 131:             'input' => '<input type="{{type}}" name="{{name}}"{{attrs}}/>',
 132:             // Submit input element.
 133:             'inputSubmit' => '<input type="{{type}}"{{attrs}}/>',
 134:             // Container element used by control().
 135:             'inputContainer' => '<div class="input {{type}}{{required}}">{{content}}</div>',
 136:             // Container element used by control() when a field has an error.
 137:             'inputContainerError' => '<div class="input {{type}}{{required}} error">{{content}}{{error}}</div>',
 138:             // Label element when inputs are not nested inside the label.
 139:             'label' => '<label{{attrs}}>{{text}}</label>',
 140:             // Label element used for radio and multi-checkbox inputs.
 141:             'nestingLabel' => '{{hidden}}<label{{attrs}}>{{input}}{{text}}</label>',
 142:             // Legends created by allControls()
 143:             'legend' => '<legend>{{text}}</legend>',
 144:             // Multi-Checkbox input set title element.
 145:             'multicheckboxTitle' => '<legend>{{text}}</legend>',
 146:             // Multi-Checkbox wrapping container.
 147:             'multicheckboxWrapper' => '<fieldset{{attrs}}>{{content}}</fieldset>',
 148:             // Option element used in select pickers.
 149:             'option' => '<option value="{{value}}"{{attrs}}>{{text}}</option>',
 150:             // Option group element used in select pickers.
 151:             'optgroup' => '<optgroup label="{{label}}"{{attrs}}>{{content}}</optgroup>',
 152:             // Select element,
 153:             'select' => '<select name="{{name}}"{{attrs}}>{{content}}</select>',
 154:             // Multi-select element,
 155:             'selectMultiple' => '<select name="{{name}}[]" multiple="multiple"{{attrs}}>{{content}}</select>',
 156:             // Radio input element,
 157:             'radio' => '<input type="radio" name="{{name}}" value="{{value}}"{{attrs}}>',
 158:             // Wrapping container for radio input/label,
 159:             'radioWrapper' => '{{label}}',
 160:             // Textarea input element,
 161:             'textarea' => '<textarea name="{{name}}"{{attrs}}>{{value}}</textarea>',
 162:             // Container for submit buttons.
 163:             'submitContainer' => '<div class="submit">{{content}}</div>',
 164:             //Confirm javascript template for postLink()
 165:             'confirmJs' => '{{confirm}}',
 166:         ],
 167:         // set HTML5 validation message to custom required/empty messages
 168:         'autoSetCustomValidity' => false,
 169:     ];
 170: 
 171:     /**
 172:      * Default widgets
 173:      *
 174:      * @var array
 175:      */
 176:     protected $_defaultWidgets = [
 177:         'button' => ['Button'],
 178:         'checkbox' => ['Checkbox'],
 179:         'file' => ['File'],
 180:         'label' => ['Label'],
 181:         'nestingLabel' => ['NestingLabel'],
 182:         'multicheckbox' => ['MultiCheckbox', 'nestingLabel'],
 183:         'radio' => ['Radio', 'nestingLabel'],
 184:         'select' => ['SelectBox'],
 185:         'textarea' => ['Textarea'],
 186:         'datetime' => ['DateTime', 'select'],
 187:         '_default' => ['Basic'],
 188:     ];
 189: 
 190:     /**
 191:      * List of fields created, used with secure forms.
 192:      *
 193:      * @var array
 194:      */
 195:     public $fields = [];
 196: 
 197:     /**
 198:      * Constant used internally to skip the securing process,
 199:      * and neither add the field to the hash or to the unlocked fields.
 200:      *
 201:      * @var string
 202:      */
 203:     const SECURE_SKIP = 'skip';
 204: 
 205:     /**
 206:      * Defines the type of form being created. Set by FormHelper::create().
 207:      *
 208:      * @var string|null
 209:      */
 210:     public $requestType;
 211: 
 212:     /**
 213:      * An array of field names that have been excluded from
 214:      * the Token hash used by SecurityComponent's validatePost method
 215:      *
 216:      * @see \Cake\View\Helper\FormHelper::_secure()
 217:      * @see \Cake\Controller\Component\SecurityComponent::validatePost()
 218:      * @var string[]
 219:      */
 220:     protected $_unlockedFields = [];
 221: 
 222:     /**
 223:      * Locator for input widgets.
 224:      *
 225:      * @var \Cake\View\Widget\WidgetLocator
 226:      */
 227:     protected $_locator;
 228: 
 229:     /**
 230:      * Context for the current form.
 231:      *
 232:      * @var \Cake\View\Form\ContextInterface|null
 233:      */
 234:     protected $_context;
 235: 
 236:     /**
 237:      * Context factory.
 238:      *
 239:      * @var \Cake\View\Form\ContextFactory
 240:      */
 241:     protected $_contextFactory;
 242: 
 243:     /**
 244:      * The action attribute value of the last created form.
 245:      * Used to make form/request specific hashes for SecurityComponent.
 246:      *
 247:      * @var string
 248:      */
 249:     protected $_lastAction = '';
 250: 
 251:     /**
 252:      * The sources to be used when retrieving prefilled input values.
 253:      *
 254:      * @var string[]
 255:      */
 256:     protected $_valueSources = ['context'];
 257: 
 258:     /**
 259:      * Grouped input types.
 260:      *
 261:      * @var string[]
 262:      */
 263:     protected $_groupedInputTypes = ['radio', 'multicheckbox', 'date', 'time', 'datetime'];
 264: 
 265:     /**
 266:      * Construct the widgets and binds the default context providers
 267:      *
 268:      * @param \Cake\View\View $View The View this helper is being attached to.
 269:      * @param array $config Configuration settings for the helper.
 270:      */
 271:     public function __construct(View $View, array $config = [])
 272:     {
 273:         $locator = null;
 274:         $widgets = $this->_defaultWidgets;
 275:         if (isset($config['registry'])) {
 276:             deprecationWarning('`registry` config key is deprecated in FormHelper, use `locator` instead.');
 277:             $config['locator'] = $config['registry'];
 278:             unset($config['registry']);
 279:         }
 280:         if (isset($config['locator'])) {
 281:             $locator = $config['locator'];
 282:             unset($config['locator']);
 283:         }
 284:         if (isset($config['widgets'])) {
 285:             if (is_string($config['widgets'])) {
 286:                 $config['widgets'] = (array)$config['widgets'];
 287:             }
 288:             $widgets = $config['widgets'] + $widgets;
 289:             unset($config['widgets']);
 290:         }
 291: 
 292:         if (isset($config['groupedInputTypes'])) {
 293:             $this->_groupedInputTypes = $config['groupedInputTypes'];
 294:             unset($config['groupedInputTypes']);
 295:         }
 296: 
 297:         parent::__construct($View, $config);
 298: 
 299:         if (!$locator) {
 300:             $locator = new WidgetLocator($this->templater(), $this->_View, $widgets);
 301:         }
 302:         $this->setWidgetLocator($locator);
 303:         $this->_idPrefix = $this->getConfig('idPrefix');
 304:     }
 305: 
 306:     /**
 307:      * Set the widget registry the helper will use.
 308:      *
 309:      * @param \Cake\View\Widget\WidgetLocator|null $instance The registry instance to set.
 310:      * @param array $widgets An array of widgets
 311:      * @return \Cake\View\Widget\WidgetLocator
 312:      * @deprecated 3.6.0 Use FormHelper::widgetLocator() instead.
 313:      */
 314:     public function widgetRegistry(WidgetRegistry $instance = null, $widgets = [])
 315:     {
 316:         deprecationWarning('widgetRegistry is deprecated, use widgetLocator instead.');
 317: 
 318:         if ($instance) {
 319:             $instance->add($widgets);
 320:             $this->setWidgetLocator($instance);
 321:         }
 322: 
 323:         return $this->getWidgetLocator();
 324:     }
 325: 
 326:     /**
 327:      * Get the widget locator currently used by the helper.
 328:      *
 329:      * @return \Cake\View\Widget\WidgetLocator Current locator instance
 330:      * @since 3.6.0
 331:      */
 332:     public function getWidgetLocator()
 333:     {
 334:         return $this->_locator;
 335:     }
 336: 
 337:     /**
 338:      * Set the widget locator the helper will use.
 339:      *
 340:      * @param \Cake\View\Widget\WidgetLocator $instance The locator instance to set.
 341:      * @return $this
 342:      * @since 3.6.0
 343:      */
 344:     public function setWidgetLocator(WidgetLocator $instance)
 345:     {
 346:         $this->_locator = $instance;
 347: 
 348:         return $this;
 349:     }
 350: 
 351:     /**
 352:      * Set the context factory the helper will use.
 353:      *
 354:      * @param \Cake\View\Form\ContextFactory|null $instance The context factory instance to set.
 355:      * @param array $contexts An array of context providers.
 356:      * @return \Cake\View\Form\ContextFactory
 357:      */
 358:     public function contextFactory(ContextFactory $instance = null, array $contexts = [])
 359:     {
 360:         if ($instance === null) {
 361:             if ($this->_contextFactory === null) {
 362:                 $this->_contextFactory = ContextFactory::createWithDefaults($contexts);
 363:             }
 364: 
 365:             return $this->_contextFactory;
 366:         }
 367:         $this->_contextFactory = $instance;
 368: 
 369:         return $this->_contextFactory;
 370:     }
 371: 
 372:     /**
 373:      * Returns an HTML form element.
 374:      *
 375:      * ### Options:
 376:      *
 377:      * - `type` Form method defaults to autodetecting based on the form context. If
 378:      *   the form context's isCreate() method returns false, a PUT request will be done.
 379:      * - `method` Set the form's method attribute explicitly.
 380:      * - `action` The controller action the form submits to, (optional). Use this option if you
 381:      *   don't need to change the controller from the current request's controller. Deprecated since 3.2, use `url`.
 382:      * - `url` The URL the form submits to. Can be a string or a URL array. If you use 'url'
 383:      *    you should leave 'action' undefined.
 384:      * - `encoding` Set the accept-charset encoding for the form. Defaults to `Configure::read('App.encoding')`
 385:      * - `enctype` Set the form encoding explicitly. By default `type => file` will set `enctype`
 386:      *   to `multipart/form-data`.
 387:      * - `templates` The templates you want to use for this form. Any templates will be merged on top of
 388:      *   the already loaded templates. This option can either be a filename in /config that contains
 389:      *   the templates you want to load, or an array of templates to use.
 390:      * - `context` Additional options for the context class. For example the EntityContext accepts a 'table'
 391:      *   option that allows you to set the specific Table class the form should be based on.
 392:      * - `idPrefix` Prefix for generated ID attributes.
 393:      * - `valueSources` The sources that values should be read from. See FormHelper::setValueSources()
 394:      * - `templateVars` Provide template variables for the formStart template.
 395:      *
 396:      * @param mixed $context The context for which the form is being defined.
 397:      *   Can be a ContextInterface instance, ORM entity, ORM resultset, or an
 398:      *   array of meta data. You can use false or null to make a context-less form.
 399:      * @param array $options An array of html attributes and options.
 400:      * @return string An formatted opening FORM tag.
 401:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#Cake\View\Helper\FormHelper::create
 402:      */
 403:     public function create($context = null, array $options = [])
 404:     {
 405:         $append = '';
 406: 
 407:         if ($context instanceof ContextInterface) {
 408:             $this->context($context);
 409:         } else {
 410:             if (empty($options['context'])) {
 411:                 $options['context'] = [];
 412:             }
 413:             $options['context']['entity'] = $context;
 414:             $context = $this->_getContext($options['context']);
 415:             unset($options['context']);
 416:         }
 417: 
 418:         $isCreate = $context->isCreate();
 419: 
 420:         $options += [
 421:             'type' => $isCreate ? 'post' : 'put',
 422:             'action' => null,
 423:             'url' => null,
 424:             'encoding' => strtolower(Configure::read('App.encoding')),
 425:             'templates' => null,
 426:             'idPrefix' => null,
 427:             'valueSources' => null,
 428:         ];
 429: 
 430:         if (isset($options['action'])) {
 431:             trigger_error('Using key `action` is deprecated, use `url` directly instead.', E_USER_DEPRECATED);
 432:         }
 433: 
 434:         if (isset($options['valueSources'])) {
 435:             $this->setValueSources($options['valueSources']);
 436:             unset($options['valueSources']);
 437:         }
 438: 
 439:         if ($options['idPrefix'] !== null) {
 440:             $this->_idPrefix = $options['idPrefix'];
 441:         }
 442:         $templater = $this->templater();
 443: 
 444:         if (!empty($options['templates'])) {
 445:             $templater->push();
 446:             $method = is_string($options['templates']) ? 'load' : 'add';
 447:             $templater->{$method}($options['templates']);
 448:         }
 449:         unset($options['templates']);
 450: 
 451:         if ($options['action'] === false || $options['url'] === false) {
 452:             $url = $this->_View->getRequest()->getRequestTarget();
 453:             $action = null;
 454:         } else {
 455:             $url = $this->_formUrl($context, $options);
 456:             $action = $this->Url->build($url);
 457:         }
 458: 
 459:         $this->_lastAction($url);
 460:         unset($options['url'], $options['action'], $options['idPrefix']);
 461: 
 462:         $htmlAttributes = [];
 463:         switch (strtolower($options['type'])) {
 464:             case 'get':
 465:                 $htmlAttributes['method'] = 'get';
 466:                 break;
 467:             // Set enctype for form
 468:             case 'file':
 469:                 $htmlAttributes['enctype'] = 'multipart/form-data';
 470:                 $options['type'] = $isCreate ? 'post' : 'put';
 471:             // Move on
 472:             case 'post':
 473:             // Move on
 474:             case 'put':
 475:             // Move on
 476:             case 'delete':
 477:             // Set patch method
 478:             case 'patch':
 479:                 $append .= $this->hidden('_method', [
 480:                     'name' => '_method',
 481:                     'value' => strtoupper($options['type']),
 482:                     'secure' => static::SECURE_SKIP
 483:                 ]);
 484:             // Default to post method
 485:             default:
 486:                 $htmlAttributes['method'] = 'post';
 487:         }
 488:         if (isset($options['method'])) {
 489:             $htmlAttributes['method'] = strtolower($options['method']);
 490:         }
 491:         if (isset($options['enctype'])) {
 492:             $htmlAttributes['enctype'] = strtolower($options['enctype']);
 493:         }
 494: 
 495:         $this->requestType = strtolower($options['type']);
 496: 
 497:         if (!empty($options['encoding'])) {
 498:             $htmlAttributes['accept-charset'] = $options['encoding'];
 499:         }
 500:         unset($options['type'], $options['encoding']);
 501: 
 502:         $htmlAttributes += $options;
 503: 
 504:         $this->fields = [];
 505:         if ($this->requestType !== 'get') {
 506:             $append .= $this->_csrfField();
 507:         }
 508: 
 509:         if (!empty($append)) {
 510:             $append = $templater->format('hiddenBlock', ['content' => $append]);
 511:         }
 512: 
 513:         $actionAttr = $templater->formatAttributes(['action' => $action, 'escape' => false]);
 514: 
 515:         return $this->formatTemplate('formStart', [
 516:             'attrs' => $templater->formatAttributes($htmlAttributes) . $actionAttr,
 517:             'templateVars' => isset($options['templateVars']) ? $options['templateVars'] : []
 518:         ]) . $append;
 519:     }
 520: 
 521:     /**
 522:      * Create the URL for a form based on the options.
 523:      *
 524:      * @param \Cake\View\Form\ContextInterface $context The context object to use.
 525:      * @param array $options An array of options from create()
 526:      * @return string|array The action attribute for the form.
 527:      */
 528:     protected function _formUrl($context, $options)
 529:     {
 530:         $request = $this->_View->getRequest();
 531: 
 532:         if ($options['action'] === null && $options['url'] === null) {
 533:             return $request->getRequestTarget();
 534:         }
 535: 
 536:         if (is_string($options['url']) ||
 537:             (is_array($options['url']) && isset($options['url']['_name']))
 538:         ) {
 539:             return $options['url'];
 540:         }
 541: 
 542:         if (isset($options['action']) && empty($options['url']['action'])) {
 543:             $options['url']['action'] = $options['action'];
 544:         }
 545: 
 546:         $actionDefaults = [
 547:             'plugin' => $this->_View->getPlugin(),
 548:             'controller' => $request->getParam('controller'),
 549:             'action' => $request->getParam('action'),
 550:         ];
 551: 
 552:         $action = (array)$options['url'] + $actionDefaults;
 553: 
 554:         $pk = $context->primaryKey();
 555:         if (count($pk)) {
 556:             $id = $this->getSourceValue($pk[0]);
 557:         }
 558:         if (empty($action[0]) && isset($id)) {
 559:             $action[0] = $id;
 560:         }
 561: 
 562:         return $action;
 563:     }
 564: 
 565:     /**
 566:      * Correctly store the last created form action URL.
 567:      *
 568:      * @param string|array $url The URL of the last form.
 569:      * @return void
 570:      */
 571:     protected function _lastAction($url)
 572:     {
 573:         $action = Router::url($url, true);
 574:         $query = parse_url($action, PHP_URL_QUERY);
 575:         $query = $query ? '?' . $query : '';
 576: 
 577:         $path = parse_url($action, PHP_URL_PATH) ?: '';
 578:         $this->_lastAction = $path . $query;
 579:     }
 580: 
 581:     /**
 582:      * Return a CSRF input if the request data is present.
 583:      * Used to secure forms in conjunction with CsrfComponent &
 584:      * SecurityComponent
 585:      *
 586:      * @return string
 587:      */
 588:     protected function _csrfField()
 589:     {
 590:         $request = $this->_View->getRequest();
 591: 
 592:         if ($request->getParam('_Token.unlockedFields')) {
 593:             foreach ((array)$request->getParam('_Token.unlockedFields') as $unlocked) {
 594:                 $this->_unlockedFields[] = $unlocked;
 595:             }
 596:         }
 597:         if (!$request->getParam('_csrfToken')) {
 598:             return '';
 599:         }
 600: 
 601:         return $this->hidden('_csrfToken', [
 602:             'value' => $request->getParam('_csrfToken'),
 603:             'secure' => static::SECURE_SKIP,
 604:             'autocomplete' => 'off',
 605:         ]);
 606:     }
 607: 
 608:     /**
 609:      * Closes an HTML form, cleans up values set by FormHelper::create(), and writes hidden
 610:      * input fields where appropriate.
 611:      *
 612:      * Resets some parts of the state, shared among multiple FormHelper::create() calls, to defaults.
 613:      *
 614:      * @param array $secureAttributes Secure attributes which will be passed as HTML attributes
 615:      *   into the hidden input elements generated for the Security Component.
 616:      * @return string A closing FORM tag.
 617:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#closing-the-form
 618:      */
 619:     public function end(array $secureAttributes = [])
 620:     {
 621:         $out = '';
 622: 
 623:         if ($this->requestType !== 'get' && $this->_View->getRequest()->getParam('_Token')) {
 624:             $out .= $this->secure($this->fields, $secureAttributes);
 625:             $this->fields = [];
 626:             $this->_unlockedFields = [];
 627:         }
 628:         $out .= $this->formatTemplate('formEnd', []);
 629: 
 630:         $this->templater()->pop();
 631:         $this->requestType = null;
 632:         $this->_context = null;
 633:         $this->_valueSources = ['context'];
 634:         $this->_idPrefix = $this->getConfig('idPrefix');
 635: 
 636:         return $out;
 637:     }
 638: 
 639:     /**
 640:      * Generates a hidden field with a security hash based on the fields used in
 641:      * the form.
 642:      *
 643:      * If $secureAttributes is set, these HTML attributes will be merged into
 644:      * the hidden input tags generated for the Security Component. This is
 645:      * especially useful to set HTML5 attributes like 'form'.
 646:      *
 647:      * @param array $fields If set specifies the list of fields to use when
 648:      *    generating the hash, else $this->fields is being used.
 649:      * @param array $secureAttributes will be passed as HTML attributes into the hidden
 650:      *    input elements generated for the Security Component.
 651:      * @return string A hidden input field with a security hash, or empty string when
 652:      *   secured forms are not in use.
 653:      */
 654:     public function secure(array $fields = [], array $secureAttributes = [])
 655:     {
 656:         if (!$this->_View->getRequest()->getParam('_Token')) {
 657:             return '';
 658:         }
 659:         $debugSecurity = Configure::read('debug');
 660:         if (isset($secureAttributes['debugSecurity'])) {
 661:             $debugSecurity = $debugSecurity && $secureAttributes['debugSecurity'];
 662:             unset($secureAttributes['debugSecurity']);
 663:         }
 664:         $secureAttributes['secure'] = static::SECURE_SKIP;
 665:         $secureAttributes['autocomplete'] = 'off';
 666: 
 667:         $tokenData = $this->_buildFieldToken(
 668:             $this->_lastAction,
 669:             $fields,
 670:             $this->_unlockedFields
 671:         );
 672:         $tokenFields = array_merge($secureAttributes, [
 673:             'value' => $tokenData['fields'],
 674:         ]);
 675:         $out = $this->hidden('_Token.fields', $tokenFields);
 676:         $tokenUnlocked = array_merge($secureAttributes, [
 677:             'value' => $tokenData['unlocked'],
 678:         ]);
 679:         $out .= $this->hidden('_Token.unlocked', $tokenUnlocked);
 680:         if ($debugSecurity) {
 681:             $tokenDebug = array_merge($secureAttributes, [
 682:                 'value' => urlencode(json_encode([
 683:                     $this->_lastAction,
 684:                     $fields,
 685:                     $this->_unlockedFields
 686:                 ])),
 687:             ]);
 688:             $out .= $this->hidden('_Token.debug', $tokenDebug);
 689:         }
 690: 
 691:         return $this->formatTemplate('hiddenBlock', ['content' => $out]);
 692:     }
 693: 
 694:     /**
 695:      * Add to or get the list of fields that are currently unlocked.
 696:      * Unlocked fields are not included in the field hash used by SecurityComponent
 697:      * unlocking a field once its been added to the list of secured fields will remove
 698:      * it from the list of fields.
 699:      *
 700:      * @param string|null $name The dot separated name for the field.
 701:      * @return array|null Either null, or the list of fields.
 702:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#working-with-securitycomponent
 703:      */
 704:     public function unlockField($name = null)
 705:     {
 706:         if ($name === null) {
 707:             return $this->_unlockedFields;
 708:         }
 709:         if (!in_array($name, $this->_unlockedFields, true)) {
 710:             $this->_unlockedFields[] = $name;
 711:         }
 712:         $index = array_search($name, $this->fields, true);
 713:         if ($index !== false) {
 714:             unset($this->fields[$index]);
 715:         }
 716:         unset($this->fields[$name]);
 717:     }
 718: 
 719:     /**
 720:      * Determine which fields of a form should be used for hash.
 721:      * Populates $this->fields
 722:      *
 723:      * @param bool $lock Whether this field should be part of the validation
 724:      *   or excluded as part of the unlockedFields.
 725:      * @param string|array $field Reference to field to be secured. Can be dot
 726:      *   separated string to indicate nesting or array of fieldname parts.
 727:      * @param mixed $value Field value, if value should not be tampered with.
 728:      * @return void
 729:      */
 730:     protected function _secure($lock, $field, $value = null)
 731:     {
 732:         if (empty($field) && $field !== '0') {
 733:             return;
 734:         }
 735: 
 736:         if (is_string($field)) {
 737:             $field = Hash::filter(explode('.', $field));
 738:         }
 739: 
 740:         foreach ($this->_unlockedFields as $unlockField) {
 741:             $unlockParts = explode('.', $unlockField);
 742:             if (array_values(array_intersect($field, $unlockParts)) === $unlockParts) {
 743:                 return;
 744:             }
 745:         }
 746: 
 747:         $field = implode('.', $field);
 748:         $field = preg_replace('/(\.\d+)+$/', '', $field);
 749: 
 750:         if ($lock) {
 751:             if (!in_array($field, $this->fields, true)) {
 752:                 if ($value !== null) {
 753:                     $this->fields[$field] = $value;
 754: 
 755:                     return;
 756:                 }
 757:                 if (isset($this->fields[$field]) && $value === null) {
 758:                     unset($this->fields[$field]);
 759:                 }
 760:                 $this->fields[] = $field;
 761:             }
 762:         } else {
 763:             $this->unlockField($field);
 764:         }
 765:     }
 766: 
 767:     /**
 768:      * Returns true if there is an error for the given field, otherwise false
 769:      *
 770:      * @param string $field This should be "modelname.fieldname"
 771:      * @return bool If there are errors this method returns true, else false.
 772:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#displaying-and-checking-errors
 773:      */
 774:     public function isFieldError($field)
 775:     {
 776:         return $this->_getContext()->hasError($field);
 777:     }
 778: 
 779:     /**
 780:      * Returns a formatted error message for given form field, '' if no errors.
 781:      *
 782:      * Uses the `error`, `errorList` and `errorItem` templates. The `errorList` and
 783:      * `errorItem` templates are used to format multiple error messages per field.
 784:      *
 785:      * ### Options:
 786:      *
 787:      * - `escape` boolean - Whether or not to html escape the contents of the error.
 788:      *
 789:      * @param string $field A field name, like "modelname.fieldname"
 790:      * @param string|array|null $text Error message as string or array of messages. If an array,
 791:      *   it should be a hash of key names => messages.
 792:      * @param array $options See above.
 793:      * @return string Formatted errors or ''.
 794:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#displaying-and-checking-errors
 795:      */
 796:     public function error($field, $text = null, array $options = [])
 797:     {
 798:         if (substr($field, -5) === '._ids') {
 799:             $field = substr($field, 0, -5);
 800:         }
 801:         $options += ['escape' => true];
 802: 
 803:         $context = $this->_getContext();
 804:         if (!$context->hasError($field)) {
 805:             return '';
 806:         }
 807:         $error = $context->error($field);
 808: 
 809:         if (is_array($text)) {
 810:             $tmp = [];
 811:             foreach ($error as $k => $e) {
 812:                 if (isset($text[$k])) {
 813:                     $tmp[] = $text[$k];
 814:                 } elseif (isset($text[$e])) {
 815:                     $tmp[] = $text[$e];
 816:                 } else {
 817:                     $tmp[] = $e;
 818:                 }
 819:             }
 820:             $text = $tmp;
 821:         }
 822: 
 823:         if ($text !== null) {
 824:             $error = $text;
 825:         }
 826: 
 827:         if ($options['escape']) {
 828:             $error = h($error);
 829:             unset($options['escape']);
 830:         }
 831: 
 832:         if (is_array($error)) {
 833:             if (count($error) > 1) {
 834:                 $errorText = [];
 835:                 foreach ($error as $err) {
 836:                     $errorText[] = $this->formatTemplate('errorItem', ['text' => $err]);
 837:                 }
 838:                 $error = $this->formatTemplate('errorList', [
 839:                     'content' => implode('', $errorText)
 840:                 ]);
 841:             } else {
 842:                 $error = array_pop($error);
 843:             }
 844:         }
 845: 
 846:         return $this->formatTemplate('error', ['content' => $error]);
 847:     }
 848: 
 849:     /**
 850:      * Returns a formatted LABEL element for HTML forms.
 851:      *
 852:      * Will automatically generate a `for` attribute if one is not provided.
 853:      *
 854:      * ### Options
 855:      *
 856:      * - `for` - Set the for attribute, if its not defined the for attribute
 857:      *   will be generated from the $fieldName parameter using
 858:      *   FormHelper::_domId().
 859:      * - `escape` - Set to `false` to turn off escaping of label text.
 860:      *   Defaults to `true`.
 861:      *
 862:      * Examples:
 863:      *
 864:      * The text and for attribute are generated off of the fieldname
 865:      *
 866:      * ```
 867:      * echo $this->Form->label('published');
 868:      * <label for="PostPublished">Published</label>
 869:      * ```
 870:      *
 871:      * Custom text:
 872:      *
 873:      * ```
 874:      * echo $this->Form->label('published', 'Publish');
 875:      * <label for="published">Publish</label>
 876:      * ```
 877:      *
 878:      * Custom attributes:
 879:      *
 880:      * ```
 881:      * echo $this->Form->label('published', 'Publish', [
 882:      *   'for' => 'post-publish'
 883:      * ]);
 884:      * <label for="post-publish">Publish</label>
 885:      * ```
 886:      *
 887:      * Nesting an input tag:
 888:      *
 889:      * ```
 890:      * echo $this->Form->label('published', 'Publish', [
 891:      *   'for' => 'published',
 892:      *   'input' => $this->text('published'),
 893:      * ]);
 894:      * <label for="post-publish">Publish <input type="text" name="published"></label>
 895:      * ```
 896:      *
 897:      * If you want to nest inputs in the labels, you will need to modify the default templates.
 898:      *
 899:      * @param string $fieldName This should be "modelname.fieldname"
 900:      * @param string|null $text Text that will appear in the label field. If
 901:      *   $text is left undefined the text will be inflected from the
 902:      *   fieldName.
 903:      * @param array $options An array of HTML attributes.
 904:      * @return string The formatted LABEL element
 905:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-labels
 906:      */
 907:     public function label($fieldName, $text = null, array $options = [])
 908:     {
 909:         if ($text === null) {
 910:             $text = $fieldName;
 911:             if (substr($text, -5) === '._ids') {
 912:                 $text = substr($text, 0, -5);
 913:             }
 914:             if (strpos($text, '.') !== false) {
 915:                 $fieldElements = explode('.', $text);
 916:                 $text = array_pop($fieldElements);
 917:             }
 918:             if (substr($text, -3) === '_id') {
 919:                 $text = substr($text, 0, -3);
 920:             }
 921:             $text = __(Inflector::humanize(Inflector::underscore($text)));
 922:         }
 923: 
 924:         if (isset($options['for'])) {
 925:             $labelFor = $options['for'];
 926:             unset($options['for']);
 927:         } else {
 928:             $labelFor = $this->_domId($fieldName);
 929:         }
 930:         $attrs = $options + [
 931:             'for' => $labelFor,
 932:             'text' => $text,
 933:         ];
 934:         if (isset($options['input'])) {
 935:             if (is_array($options['input'])) {
 936:                 $attrs = $options['input'] + $attrs;
 937:             }
 938: 
 939:             return $this->widget('nestingLabel', $attrs);
 940:         }
 941: 
 942:         return $this->widget('label', $attrs);
 943:     }
 944: 
 945:     /**
 946:      * Generate a set of controls for `$fields`. If $fields is empty the fields
 947:      * of current model will be used.
 948:      *
 949:      * You can customize individual controls through `$fields`.
 950:      * ```
 951:      * $this->Form->allControls([
 952:      *   'name' => ['label' => 'custom label']
 953:      * ]);
 954:      * ```
 955:      *
 956:      * You can exclude fields by specifying them as `false`:
 957:      *
 958:      * ```
 959:      * $this->Form->allControls(['title' => false]);
 960:      * ```
 961:      *
 962:      * In the above example, no field would be generated for the title field.
 963:      *
 964:      * @param array $fields An array of customizations for the fields that will be
 965:      *   generated. This array allows you to set custom types, labels, or other options.
 966:      * @param array $options Options array. Valid keys are:
 967:      * - `fieldset` Set to false to disable the fieldset. You can also pass an array of params to be
 968:      *    applied as HTML attributes to the fieldset tag. If you pass an empty array, the fieldset will
 969:      *    be enabled
 970:      * - `legend` Set to false to disable the legend for the generated control set. Or supply a string
 971:      *    to customize the legend text.
 972:      * @return string Completed form controls.
 973:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#generating-entire-forms
 974:      */
 975:     public function allControls(array $fields = [], array $options = [])
 976:     {
 977:         $context = $this->_getContext();
 978: 
 979:         $modelFields = $context->fieldNames();
 980: 
 981:         $fields = array_merge(
 982:             Hash::normalize($modelFields),
 983:             Hash::normalize($fields)
 984:         );
 985: 
 986:         return $this->controls($fields, $options);
 987:     }
 988: 
 989:     /**
 990:      * Generate a set of controls for `$fields`. If $fields is empty the fields
 991:      * of current model will be used.
 992:      *
 993:      * @param array $fields An array of customizations for the fields that will be
 994:      *   generated. This array allows you to set custom types, labels, or other options.
 995:      * @param array $options Options array. Valid keys are:
 996:      * - `fieldset` Set to false to disable the fieldset. You can also pass an array of params to be
 997:      *    applied as HTML attributes to the fieldset tag. If you pass an empty array, the fieldset will
 998:      *    be enabled
 999:      * - `legend` Set to false to disable the legend for the generated control set. Or supply a string
1000:      *    to customize the legend text.
1001:      * @return string Completed form controls.
1002:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#generating-entire-forms
1003:      * @deprecated 3.4.0 Use FormHelper::allControls() instead.
1004:      */
1005:     public function allInputs(array $fields = [], array $options = [])
1006:     {
1007:         deprecationWarning(
1008:             'FormHelper::allInputs() is deprecated. ' .
1009:             'Use FormHelper::allControls() instead.'
1010:         );
1011: 
1012:         return $this->allControls($fields, $options);
1013:     }
1014: 
1015:     /**
1016:      * Generate a set of controls for `$fields` wrapped in a fieldset element.
1017:      *
1018:      * You can customize individual controls through `$fields`.
1019:      * ```
1020:      * $this->Form->controls([
1021:      *   'name' => ['label' => 'custom label'],
1022:      *   'email'
1023:      * ]);
1024:      * ```
1025:      *
1026:      * @param array $fields An array of the fields to generate. This array allows
1027:      *   you to set custom types, labels, or other options.
1028:      * @param array $options Options array. Valid keys are:
1029:      * - `fieldset` Set to false to disable the fieldset. You can also pass an
1030:      *    array of params to be applied as HTML attributes to the fieldset tag.
1031:      *    If you pass an empty array, the fieldset will be enabled.
1032:      * - `legend` Set to false to disable the legend for the generated input set.
1033:      *    Or supply a string to customize the legend text.
1034:      * @return string Completed form inputs.
1035:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#generating-entire-forms
1036:      */
1037:     public function controls(array $fields, array $options = [])
1038:     {
1039:         $fields = Hash::normalize($fields);
1040: 
1041:         $out = '';
1042:         foreach ($fields as $name => $opts) {
1043:             if ($opts === false) {
1044:                 continue;
1045:             }
1046: 
1047:             $out .= $this->control($name, (array)$opts);
1048:         }
1049: 
1050:         return $this->fieldset($out, $options);
1051:     }
1052: 
1053:     /**
1054:      * Generate a set of controls for `$fields` wrapped in a fieldset element.
1055:      *
1056:      * @param array $fields An array of the fields to generate. This array allows
1057:      *   you to set custom types, labels, or other options.
1058:      * @param array $options Options array. Valid keys are:
1059:      * - `fieldset` Set to false to disable the fieldset. You can also pass an
1060:      *    array of params to be applied as HTML attributes to the fieldset tag.
1061:      *    If you pass an empty array, the fieldset will be enabled.
1062:      * - `legend` Set to false to disable the legend for the generated input set.
1063:      *    Or supply a string to customize the legend text.
1064:      * @return string Completed form inputs.
1065:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#generating-entire-forms
1066:      * @deprecated 3.4.0 Use FormHelper::controls() instead.
1067:      */
1068:     public function inputs(array $fields, array $options = [])
1069:     {
1070:         deprecationWarning(
1071:             'FormHelper::inputs() is deprecated. ' .
1072:             'Use FormHelper::controls() instead.'
1073:         );
1074: 
1075:         return $this->controls($fields, $options);
1076:     }
1077: 
1078:     /**
1079:      * Wrap a set of inputs in a fieldset
1080:      *
1081:      * @param string $fields the form inputs to wrap in a fieldset
1082:      * @param array $options Options array. Valid keys are:
1083:      * - `fieldset` Set to false to disable the fieldset. You can also pass an array of params to be
1084:      *    applied as HTML attributes to the fieldset tag. If you pass an empty array, the fieldset will
1085:      *    be enabled
1086:      * - `legend` Set to false to disable the legend for the generated input set. Or supply a string
1087:      *    to customize the legend text.
1088:      * @return string Completed form inputs.
1089:      */
1090:     public function fieldset($fields = '', array $options = [])
1091:     {
1092:         $fieldset = $legend = true;
1093:         $context = $this->_getContext();
1094:         $out = $fields;
1095: 
1096:         if (isset($options['legend'])) {
1097:             $legend = $options['legend'];
1098:         }
1099:         if (isset($options['fieldset'])) {
1100:             $fieldset = $options['fieldset'];
1101:         }
1102: 
1103:         if ($legend === true) {
1104:             $isCreate = $context->isCreate();
1105:             $modelName = Inflector::humanize(Inflector::singularize($this->_View->getRequest()->getParam('controller')));
1106:             if (!$isCreate) {
1107:                 $legend = __d('cake', 'Edit {0}', $modelName);
1108:             } else {
1109:                 $legend = __d('cake', 'New {0}', $modelName);
1110:             }
1111:         }
1112: 
1113:         if ($fieldset !== false) {
1114:             if ($legend) {
1115:                 $out = $this->formatTemplate('legend', ['text' => $legend]) . $out;
1116:             }
1117: 
1118:             $fieldsetParams = ['content' => $out, 'attrs' => ''];
1119:             if (is_array($fieldset) && !empty($fieldset)) {
1120:                 $fieldsetParams['attrs'] = $this->templater()->formatAttributes($fieldset);
1121:             }
1122:             $out = $this->formatTemplate('fieldset', $fieldsetParams);
1123:         }
1124: 
1125:         return $out;
1126:     }
1127: 
1128:     /**
1129:      * Generates a form control element complete with label and wrapper div.
1130:      *
1131:      * ### Options
1132:      *
1133:      * See each field type method for more information. Any options that are part of
1134:      * $attributes or $options for the different **type** methods can be included in `$options` for input().
1135:      * Additionally, any unknown keys that are not in the list below, or part of the selected type's options
1136:      * will be treated as a regular HTML attribute for the generated input.
1137:      *
1138:      * - `type` - Force the type of widget you want. e.g. `type => 'select'`
1139:      * - `label` - Either a string label, or an array of options for the label. See FormHelper::label().
1140:      * - `options` - For widgets that take options e.g. radio, select.
1141:      * - `error` - Control the error message that is produced. Set to `false` to disable any kind of error reporting (field
1142:      *    error and error messages).
1143:      * - `empty` - String or boolean to enable empty select box options.
1144:      * - `nestedInput` - Used with checkbox and radio inputs. Set to false to render inputs outside of label
1145:      *   elements. Can be set to true on any input to force the input inside the label. If you
1146:      *   enable this option for radio buttons you will also need to modify the default `radioWrapper` template.
1147:      * - `templates` - The templates you want to use for this input. Any templates will be merged on top of
1148:      *   the already loaded templates. This option can either be a filename in /config that contains
1149:      *   the templates you want to load, or an array of templates to use.
1150:      * - `labelOptions` - Either `false` to disable label around nestedWidgets e.g. radio, multicheckbox or an array
1151:      *   of attributes for the label tag. `selected` will be added to any classes e.g. `class => 'myclass'` where
1152:      *   widget is checked
1153:      *
1154:      * @param string $fieldName This should be "modelname.fieldname"
1155:      * @param array $options Each type of input takes different options.
1156:      * @return string Completed form widget.
1157:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-form-inputs
1158:      */
1159:     public function control($fieldName, array $options = [])
1160:     {
1161:         $options += [
1162:             'type' => null,
1163:             'label' => null,
1164:             'error' => null,
1165:             'required' => null,
1166:             'options' => null,
1167:             'templates' => [],
1168:             'templateVars' => [],
1169:             'labelOptions' => true
1170:         ];
1171:         $options = $this->_parseOptions($fieldName, $options);
1172:         $options += ['id' => $this->_domId($fieldName)];
1173: 
1174:         $templater = $this->templater();
1175:         $newTemplates = $options['templates'];
1176: 
1177:         if ($newTemplates) {
1178:             $templater->push();
1179:             $templateMethod = is_string($options['templates']) ? 'load' : 'add';
1180:             $templater->{$templateMethod}($options['templates']);
1181:         }
1182:         unset($options['templates']);
1183: 
1184:         $error = null;
1185:         $errorSuffix = '';
1186:         if ($options['type'] !== 'hidden' && $options['error'] !== false) {
1187:             if (is_array($options['error'])) {
1188:                 $error = $this->error($fieldName, $options['error'], $options['error']);
1189:             } else {
1190:                 $error = $this->error($fieldName, $options['error']);
1191:             }
1192:             $errorSuffix = empty($error) ? '' : 'Error';
1193:             unset($options['error']);
1194:         }
1195: 
1196:         $label = $options['label'];
1197:         unset($options['label']);
1198: 
1199:         $labelOptions = $options['labelOptions'];
1200:         unset($options['labelOptions']);
1201: 
1202:         $nestedInput = false;
1203:         if ($options['type'] === 'checkbox') {
1204:             $nestedInput = true;
1205:         }
1206:         $nestedInput = isset($options['nestedInput']) ? $options['nestedInput'] : $nestedInput;
1207:         unset($options['nestedInput']);
1208: 
1209:         if ($nestedInput === true && $options['type'] === 'checkbox' && !array_key_exists('hiddenField', $options) && $label !== false) {
1210:             $options['hiddenField'] = '_split';
1211:         }
1212: 
1213:         $input = $this->_getInput($fieldName, $options + ['labelOptions' => $labelOptions]);
1214:         if ($options['type'] === 'hidden' || $options['type'] === 'submit') {
1215:             if ($newTemplates) {
1216:                 $templater->pop();
1217:             }
1218: 
1219:             return $input;
1220:         }
1221: 
1222:         $label = $this->_getLabel($fieldName, compact('input', 'label', 'error', 'nestedInput') + $options);
1223:         if ($nestedInput) {
1224:             $result = $this->_groupTemplate(compact('label', 'error', 'options'));
1225:         } else {
1226:             $result = $this->_groupTemplate(compact('input', 'label', 'error', 'options'));
1227:         }
1228:         $result = $this->_inputContainerTemplate([
1229:             'content' => $result,
1230:             'error' => $error,
1231:             'errorSuffix' => $errorSuffix,
1232:             'options' => $options
1233:         ]);
1234: 
1235:         if ($newTemplates) {
1236:             $templater->pop();
1237:         }
1238: 
1239:         return $result;
1240:     }
1241: 
1242:     /**
1243:      * Generates a form control element complete with label and wrapper div.
1244:      *
1245:      * @param string $fieldName This should be "modelname.fieldname"
1246:      * @param array $options Each type of input takes different options.
1247:      * @return string Completed form widget.
1248:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-form-inputs
1249:      * @deprecated 3.4.0 Use FormHelper::control() instead.
1250:      */
1251:     public function input($fieldName, array $options = [])
1252:     {
1253:         deprecationWarning(
1254:             'FormHelper::input() is deprecated. ' .
1255:             'Use FormHelper::control() instead.'
1256:         );
1257: 
1258:         return $this->control($fieldName, $options);
1259:     }
1260: 
1261:     /**
1262:      * Generates an group template element
1263:      *
1264:      * @param array $options The options for group template
1265:      * @return string The generated group template
1266:      */
1267:     protected function _groupTemplate($options)
1268:     {
1269:         $groupTemplate = $options['options']['type'] . 'FormGroup';
1270:         if (!$this->templater()->get($groupTemplate)) {
1271:             $groupTemplate = 'formGroup';
1272:         }
1273: 
1274:         return $this->formatTemplate($groupTemplate, [
1275:             'input' => isset($options['input']) ? $options['input'] : [],
1276:             'label' => $options['label'],
1277:             'error' => $options['error'],
1278:             'templateVars' => isset($options['options']['templateVars']) ? $options['options']['templateVars'] : []
1279:         ]);
1280:     }
1281: 
1282:     /**
1283:      * Generates an input container template
1284:      *
1285:      * @param array $options The options for input container template
1286:      * @return string The generated input container template
1287:      */
1288:     protected function _inputContainerTemplate($options)
1289:     {
1290:         $inputContainerTemplate = $options['options']['type'] . 'Container' . $options['errorSuffix'];
1291:         if (!$this->templater()->get($inputContainerTemplate)) {
1292:             $inputContainerTemplate = 'inputContainer' . $options['errorSuffix'];
1293:         }
1294: 
1295:         return $this->formatTemplate($inputContainerTemplate, [
1296:             'content' => $options['content'],
1297:             'error' => $options['error'],
1298:             'required' => $options['options']['required'] ? ' required' : '',
1299:             'type' => $options['options']['type'],
1300:             'templateVars' => isset($options['options']['templateVars']) ? $options['options']['templateVars'] : []
1301:         ]);
1302:     }
1303: 
1304:     /**
1305:      * Generates an input element
1306:      *
1307:      * @param string $fieldName the field name
1308:      * @param array $options The options for the input element
1309:      * @return string The generated input element
1310:      */
1311:     protected function _getInput($fieldName, $options)
1312:     {
1313:         $label = $options['labelOptions'];
1314:         unset($options['labelOptions']);
1315:         switch (strtolower($options['type'])) {
1316:             case 'select':
1317:                 $opts = $options['options'];
1318:                 unset($options['options']);
1319: 
1320:                 return $this->select($fieldName, $opts, $options + ['label' => $label]);
1321:             case 'radio':
1322:                 $opts = $options['options'];
1323:                 unset($options['options']);
1324: 
1325:                 return $this->radio($fieldName, $opts, $options + ['label' => $label]);
1326:             case 'multicheckbox':
1327:                 $opts = $options['options'];
1328:                 unset($options['options']);
1329: 
1330:                 return $this->multiCheckbox($fieldName, $opts, $options + ['label' => $label]);
1331:             case 'input':
1332:                 throw new RuntimeException("Invalid type 'input' used for field '$fieldName'");
1333: 
1334:             default:
1335:                 return $this->{$options['type']}($fieldName, $options);
1336:         }
1337:     }
1338: 
1339:     /**
1340:      * Generates input options array
1341:      *
1342:      * @param string $fieldName The name of the field to parse options for.
1343:      * @param array $options Options list.
1344:      * @return array Options
1345:      */
1346:     protected function _parseOptions($fieldName, $options)
1347:     {
1348:         $needsMagicType = false;
1349:         if (empty($options['type'])) {
1350:             $needsMagicType = true;
1351:             $options['type'] = $this->_inputType($fieldName, $options);
1352:         }
1353: 
1354:         $options = $this->_magicOptions($fieldName, $options, $needsMagicType);
1355: 
1356:         return $options;
1357:     }
1358: 
1359:     /**
1360:      * Returns the input type that was guessed for the provided fieldName,
1361:      * based on the internal type it is associated too, its name and the
1362:      * variables that can be found in the view template
1363:      *
1364:      * @param string $fieldName the name of the field to guess a type for
1365:      * @param array $options the options passed to the input method
1366:      * @return string
1367:      */
1368:     protected function _inputType($fieldName, $options)
1369:     {
1370:         $context = $this->_getContext();
1371: 
1372:         if ($context->isPrimaryKey($fieldName)) {
1373:             return 'hidden';
1374:         }
1375: 
1376:         if (substr($fieldName, -3) === '_id') {
1377:             return 'select';
1378:         }
1379: 
1380:         $internalType = $context->type($fieldName);
1381:         $map = $this->_config['typeMap'];
1382:         $type = isset($map[$internalType]) ? $map[$internalType] : 'text';
1383:         $fieldName = array_slice(explode('.', $fieldName), -1)[0];
1384: 
1385:         switch (true) {
1386:             case isset($options['checked']):
1387:                 return 'checkbox';
1388:             case isset($options['options']):
1389:                 return 'select';
1390:             case in_array($fieldName, ['passwd', 'password']):
1391:                 return 'password';
1392:             case in_array($fieldName, ['tel', 'telephone', 'phone']):
1393:                 return 'tel';
1394:             case $fieldName === 'email':
1395:                 return 'email';
1396:             case isset($options['rows']) || isset($options['cols']):
1397:                 return 'textarea';
1398:         }
1399: 
1400:         return $type;
1401:     }
1402: 
1403:     /**
1404:      * Selects the variable containing the options for a select field if present,
1405:      * and sets the value to the 'options' key in the options array.
1406:      *
1407:      * @param string $fieldName The name of the field to find options for.
1408:      * @param array $options Options list.
1409:      * @return array
1410:      */
1411:     protected function _optionsOptions($fieldName, $options)
1412:     {
1413:         if (isset($options['options'])) {
1414:             return $options;
1415:         }
1416: 
1417:         $pluralize = true;
1418:         if (substr($fieldName, -5) === '._ids') {
1419:             $fieldName = substr($fieldName, 0, -5);
1420:             $pluralize = false;
1421:         } elseif (substr($fieldName, -3) === '_id') {
1422:             $fieldName = substr($fieldName, 0, -3);
1423:         }
1424:         $fieldName = array_slice(explode('.', $fieldName), -1)[0];
1425: 
1426:         $varName = Inflector::variable(
1427:             $pluralize ? Inflector::pluralize($fieldName) : $fieldName
1428:         );
1429:         $varOptions = $this->_View->get($varName);
1430:         if (!is_array($varOptions) && !($varOptions instanceof Traversable)) {
1431:             return $options;
1432:         }
1433:         if ($options['type'] !== 'radio') {
1434:             $options['type'] = 'select';
1435:         }
1436:         $options['options'] = $varOptions;
1437: 
1438:         return $options;
1439:     }
1440: 
1441:     /**
1442:      * Magically set option type and corresponding options
1443:      *
1444:      * @param string $fieldName The name of the field to generate options for.
1445:      * @param array $options Options list.
1446:      * @param bool $allowOverride Whether or not it is allowed for this method to
1447:      * overwrite the 'type' key in options.
1448:      * @return array
1449:      */
1450:     protected function _magicOptions($fieldName, $options, $allowOverride)
1451:     {
1452:         $context = $this->_getContext();
1453: 
1454:         $options += [
1455:             'templateVars' => []
1456:         ];
1457: 
1458:         if (!isset($options['required']) && $options['type'] !== 'hidden') {
1459:             $options['required'] = $context->isRequired($fieldName);
1460:         }
1461: 
1462:         if (method_exists($context, 'getRequiredMessage')) {
1463:             $message = $context->getRequiredMessage($fieldName);
1464:             $message = h($message);
1465: 
1466:             if ($options['required'] && $message) {
1467:                 $options['templateVars']['customValidityMessage'] = $message;
1468: 
1469:                 if ($this->getConfig('autoSetCustomValidity')) {
1470:                     $options['oninvalid'] = "this.setCustomValidity(''); if (!this.validity.valid) this.setCustomValidity('$message')";
1471:                     $options['oninput'] = "this.setCustomValidity('')";
1472:                 }
1473:             }
1474:         }
1475: 
1476:         $type = $context->type($fieldName);
1477:         $fieldDef = $context->attributes($fieldName);
1478: 
1479:         if ($options['type'] === 'number' && !isset($options['step'])) {
1480:             if ($type === 'decimal' && isset($fieldDef['precision'])) {
1481:                 $decimalPlaces = $fieldDef['precision'];
1482:                 $options['step'] = sprintf('%.' . $decimalPlaces . 'F', pow(10, -1 * $decimalPlaces));
1483:             } elseif ($type === 'float') {
1484:                 $options['step'] = 'any';
1485:             }
1486:         }
1487: 
1488:         $typesWithOptions = ['text', 'number', 'radio', 'select'];
1489:         $magicOptions = (in_array($options['type'], ['radio', 'select']) || $allowOverride);
1490:         if ($magicOptions && in_array($options['type'], $typesWithOptions)) {
1491:             $options = $this->_optionsOptions($fieldName, $options);
1492:         }
1493: 
1494:         if ($allowOverride && substr($fieldName, -5) === '._ids') {
1495:             $options['type'] = 'select';
1496:             if (!isset($options['multiple']) || ($options['multiple'] && $options['multiple'] != 'checkbox')) {
1497:                 $options['multiple'] = true;
1498:             }
1499:         }
1500: 
1501:         if ($options['type'] === 'select' && array_key_exists('step', $options)) {
1502:             unset($options['step']);
1503:         }
1504: 
1505:         $typesWithMaxLength = ['text', 'textarea', 'email', 'tel', 'url', 'search'];
1506:         if (!array_key_exists('maxlength', $options)
1507:             && in_array($options['type'], $typesWithMaxLength)
1508:         ) {
1509:             $maxLength = null;
1510:             if (method_exists($context, 'getMaxLength')) {
1511:                 $maxLength = $context->getMaxLength($fieldName);
1512:             }
1513: 
1514:             if ($maxLength === null && !empty($fieldDef['length'])) {
1515:                 $maxLength = $fieldDef['length'];
1516:             }
1517: 
1518:             if ($maxLength !== null) {
1519:                 $options['maxlength'] = min($maxLength, 100000);
1520:             }
1521:         }
1522: 
1523:         if (in_array($options['type'], ['datetime', 'date', 'time', 'select'])) {
1524:             $options += ['empty' => false];
1525:         }
1526: 
1527:         return $options;
1528:     }
1529: 
1530:     /**
1531:      * Generate label for input
1532:      *
1533:      * @param string $fieldName The name of the field to generate label for.
1534:      * @param array $options Options list.
1535:      * @return bool|string false or Generated label element
1536:      */
1537:     protected function _getLabel($fieldName, $options)
1538:     {
1539:         if ($options['type'] === 'hidden') {
1540:             return false;
1541:         }
1542: 
1543:         $label = null;
1544:         if (isset($options['label'])) {
1545:             $label = $options['label'];
1546:         }
1547: 
1548:         if ($label === false && $options['type'] === 'checkbox') {
1549:             return $options['input'];
1550:         }
1551:         if ($label === false) {
1552:             return false;
1553:         }
1554: 
1555:         return $this->_inputLabel($fieldName, $label, $options);
1556:     }
1557: 
1558:     /**
1559:      * Extracts a single option from an options array.
1560:      *
1561:      * @param string $name The name of the option to pull out.
1562:      * @param array $options The array of options you want to extract.
1563:      * @param mixed $default The default option value
1564:      * @return mixed the contents of the option or default
1565:      */
1566:     protected function _extractOption($name, $options, $default = null)
1567:     {
1568:         if (array_key_exists($name, $options)) {
1569:             return $options[$name];
1570:         }
1571: 
1572:         return $default;
1573:     }
1574: 
1575:     /**
1576:      * Generate a label for an input() call.
1577:      *
1578:      * $options can contain a hash of id overrides. These overrides will be
1579:      * used instead of the generated values if present.
1580:      *
1581:      * @param string $fieldName The name of the field to generate label for.
1582:      * @param string $label Label text.
1583:      * @param array $options Options for the label element.
1584:      * @return string Generated label element
1585:      */
1586:     protected function _inputLabel($fieldName, $label, $options)
1587:     {
1588:         $options += ['id' => null, 'input' => null, 'nestedInput' => false, 'templateVars' => []];
1589:         $labelAttributes = ['templateVars' => $options['templateVars']];
1590:         if (is_array($label)) {
1591:             $labelText = null;
1592:             if (isset($label['text'])) {
1593:                 $labelText = $label['text'];
1594:                 unset($label['text']);
1595:             }
1596:             $labelAttributes = array_merge($labelAttributes, $label);
1597:         } else {
1598:             $labelText = $label;
1599:         }
1600: 
1601:         $labelAttributes['for'] = $options['id'];
1602:         if (in_array($options['type'], $this->_groupedInputTypes, true)) {
1603:             $labelAttributes['for'] = false;
1604:         }
1605:         if ($options['nestedInput']) {
1606:             $labelAttributes['input'] = $options['input'];
1607:         }
1608:         if (isset($options['escape'])) {
1609:             $labelAttributes['escape'] = $options['escape'];
1610:         }
1611: 
1612:         return $this->label($fieldName, $labelText, $labelAttributes);
1613:     }
1614: 
1615:     /**
1616:      * Creates a checkbox input widget.
1617:      *
1618:      * ### Options:
1619:      *
1620:      * - `value` - the value of the checkbox
1621:      * - `checked` - boolean indicate that this checkbox is checked.
1622:      * - `hiddenField` - boolean to indicate if you want the results of checkbox() to include
1623:      *    a hidden input with a value of ''.
1624:      * - `disabled` - create a disabled input.
1625:      * - `default` - Set the default value for the checkbox. This allows you to start checkboxes
1626:      *    as checked, without having to check the POST data. A matching POST data value, will overwrite
1627:      *    the default value.
1628:      *
1629:      * @param string $fieldName Name of a field, like this "modelname.fieldname"
1630:      * @param array $options Array of HTML attributes.
1631:      * @return string|array An HTML text input element.
1632:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-checkboxes
1633:      */
1634:     public function checkbox($fieldName, array $options = [])
1635:     {
1636:         $options += ['hiddenField' => true, 'value' => 1];
1637: 
1638:         // Work around value=>val translations.
1639:         $value = $options['value'];
1640:         unset($options['value']);
1641:         $options = $this->_initInputField($fieldName, $options);
1642:         $options['value'] = $value;
1643: 
1644:         $output = '';
1645:         if ($options['hiddenField']) {
1646:             $hiddenOptions = [
1647:                 'name' => $options['name'],
1648:                 'value' => $options['hiddenField'] !== true && $options['hiddenField'] !== '_split' ? $options['hiddenField'] : '0',
1649:                 'form' => isset($options['form']) ? $options['form'] : null,
1650:                 'secure' => false
1651:             ];
1652:             if (isset($options['disabled']) && $options['disabled']) {
1653:                 $hiddenOptions['disabled'] = 'disabled';
1654:             }
1655:             $output = $this->hidden($fieldName, $hiddenOptions);
1656:         }
1657: 
1658:         if ($options['hiddenField'] === '_split') {
1659:             unset($options['hiddenField'], $options['type']);
1660: 
1661:             return ['hidden' => $output, 'input' => $this->widget('checkbox', $options)];
1662:         }
1663:         unset($options['hiddenField'], $options['type']);
1664: 
1665:         return $output . $this->widget('checkbox', $options);
1666:     }
1667: 
1668:     /**
1669:      * Creates a set of radio widgets.
1670:      *
1671:      * ### Attributes:
1672:      *
1673:      * - `value` - Indicates the value when this radio button is checked.
1674:      * - `label` - Either `false` to disable label around the widget or an array of attributes for
1675:      *    the label tag. `selected` will be added to any classes e.g. `'class' => 'myclass'` where widget
1676:      *    is checked
1677:      * - `hiddenField` - boolean to indicate if you want the results of radio() to include
1678:      *    a hidden input with a value of ''. This is useful for creating radio sets that are non-continuous.
1679:      * - `disabled` - Set to `true` or `disabled` to disable all the radio buttons. Use an array of
1680:      *   values to disable specific radio buttons.
1681:      * - `empty` - Set to `true` to create an input with the value '' as the first option. When `true`
1682:      *   the radio label will be 'empty'. Set this option to a string to control the label value.
1683:      *
1684:      * @param string $fieldName Name of a field, like this "modelname.fieldname"
1685:      * @param array|\Traversable $options Radio button options array.
1686:      * @param array $attributes Array of attributes.
1687:      * @return string Completed radio widget set.
1688:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-radio-buttons
1689:      */
1690:     public function radio($fieldName, $options = [], array $attributes = [])
1691:     {
1692:         $attributes['options'] = $options;
1693:         $attributes['idPrefix'] = $this->_idPrefix;
1694:         $attributes = $this->_initInputField($fieldName, $attributes);
1695: 
1696:         $hiddenField = isset($attributes['hiddenField']) ? $attributes['hiddenField'] : true;
1697:         unset($attributes['hiddenField']);
1698: 
1699:         $radio = $this->widget('radio', $attributes);
1700: 
1701:         $hidden = '';
1702:         if ($hiddenField) {
1703:             $hidden = $this->hidden($fieldName, [
1704:                 'value' => $hiddenField === true ? '' : $hiddenField,
1705:                 'form' => isset($attributes['form']) ? $attributes['form'] : null,
1706:                 'name' => $attributes['name'],
1707:             ]);
1708:         }
1709: 
1710:         return $hidden . $radio;
1711:     }
1712: 
1713:     /**
1714:      * Missing method handler - implements various simple input types. Is used to create inputs
1715:      * of various types. e.g. `$this->Form->text();` will create `<input type="text" />` while
1716:      * `$this->Form->range();` will create `<input type="range" />`
1717:      *
1718:      * ### Usage
1719:      *
1720:      * ```
1721:      * $this->Form->search('User.query', ['value' => 'test']);
1722:      * ```
1723:      *
1724:      * Will make an input like:
1725:      *
1726:      * `<input type="search" id="UserQuery" name="User[query]" value="test" />`
1727:      *
1728:      * The first argument to an input type should always be the fieldname, in `Model.field` format.
1729:      * The second argument should always be an array of attributes for the input.
1730:      *
1731:      * @param string $method Method name / input type to make.
1732:      * @param array $params Parameters for the method call
1733:      * @return string Formatted input method.
1734:      * @throws \Cake\Core\Exception\Exception When there are no params for the method call.
1735:      */
1736:     public function __call($method, $params)
1737:     {
1738:         $options = [];
1739:         if (empty($params)) {
1740:             throw new Exception(sprintf('Missing field name for FormHelper::%s', $method));
1741:         }
1742:         if (isset($params[1])) {
1743:             $options = $params[1];
1744:         }
1745:         if (!isset($options['type'])) {
1746:             $options['type'] = $method;
1747:         }
1748:         $options = $this->_initInputField($params[0], $options);
1749: 
1750:         return $this->widget($options['type'], $options);
1751:     }
1752: 
1753:     /**
1754:      * Creates a textarea widget.
1755:      *
1756:      * ### Options:
1757:      *
1758:      * - `escape` - Whether or not the contents of the textarea should be escaped. Defaults to true.
1759:      *
1760:      * @param string $fieldName Name of a field, in the form "modelname.fieldname"
1761:      * @param array $options Array of HTML attributes, and special options above.
1762:      * @return string A generated HTML text input element
1763:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-textareas
1764:      */
1765:     public function textarea($fieldName, array $options = [])
1766:     {
1767:         $options = $this->_initInputField($fieldName, $options);
1768:         unset($options['type']);
1769: 
1770:         return $this->widget('textarea', $options);
1771:     }
1772: 
1773:     /**
1774:      * Creates a hidden input field.
1775:      *
1776:      * @param string $fieldName Name of a field, in the form of "modelname.fieldname"
1777:      * @param array $options Array of HTML attributes.
1778:      * @return string A generated hidden input
1779:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-hidden-inputs
1780:      */
1781:     public function hidden($fieldName, array $options = [])
1782:     {
1783:         $options += ['required' => false, 'secure' => true];
1784: 
1785:         $secure = $options['secure'];
1786:         unset($options['secure']);
1787: 
1788:         $options = $this->_initInputField($fieldName, array_merge(
1789:             $options,
1790:             ['secure' => static::SECURE_SKIP]
1791:         ));
1792: 
1793:         if ($secure === true) {
1794:             $this->_secure(true, $this->_secureFieldName($options['name']), (string)$options['val']);
1795:         }
1796: 
1797:         $options['type'] = 'hidden';
1798: 
1799:         return $this->widget('hidden', $options);
1800:     }
1801: 
1802:     /**
1803:      * Creates file input widget.
1804:      *
1805:      * @param string $fieldName Name of a field, in the form "modelname.fieldname"
1806:      * @param array $options Array of HTML attributes.
1807:      * @return string A generated file input.
1808:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-file-inputs
1809:      */
1810:     public function file($fieldName, array $options = [])
1811:     {
1812:         $options += ['secure' => true];
1813:         $options = $this->_initInputField($fieldName, $options);
1814: 
1815:         unset($options['type']);
1816: 
1817:         return $this->widget('file', $options);
1818:     }
1819: 
1820:     /**
1821:      * Creates a `<button>` tag.
1822:      *
1823:      * The type attribute defaults to `type="submit"`
1824:      * You can change it to a different value by using `$options['type']`.
1825:      *
1826:      * ### Options:
1827:      *
1828:      * - `escape` - HTML entity encode the $title of the button. Defaults to false.
1829:      * - `confirm` - Confirm message to show. Form execution will only continue if confirmed then.
1830:      *
1831:      * @param string $title The button's caption. Not automatically HTML encoded
1832:      * @param array $options Array of options and HTML attributes.
1833:      * @return string A HTML button tag.
1834:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-button-elements
1835:      */
1836:     public function button($title, array $options = [])
1837:     {
1838:         $options += ['type' => 'submit', 'escape' => false, 'secure' => false, 'confirm' => null];
1839:         $options['text'] = $title;
1840: 
1841:         $confirmMessage = $options['confirm'];
1842:         unset($options['confirm']);
1843:         if ($confirmMessage) {
1844:             $options['onclick'] = $this->_confirm($confirmMessage, 'return true;', 'return false;', $options);
1845:         }
1846: 
1847:         return $this->widget('button', $options);
1848:     }
1849: 
1850:     /**
1851:      * Create a `<button>` tag with a surrounding `<form>` that submits via POST as default.
1852:      *
1853:      * This method creates a `<form>` element. So do not use this method in an already opened form.
1854:      * Instead use FormHelper::submit() or FormHelper::button() to create buttons inside opened forms.
1855:      *
1856:      * ### Options:
1857:      *
1858:      * - `data` - Array with key/value to pass in input hidden
1859:      * - `method` - Request method to use. Set to 'delete' or others to simulate
1860:      *   HTTP/1.1 DELETE (or others) request. Defaults to 'post'.
1861:      * - `form` - Array with any option that FormHelper::create() can take
1862:      * - Other options is the same of button method.
1863:      * - `confirm` - Confirm message to show. Form execution will only continue if confirmed then.
1864:      *
1865:      * @param string $title The button's caption. Not automatically HTML encoded
1866:      * @param string|array $url URL as string or array
1867:      * @param array $options Array of options and HTML attributes.
1868:      * @return string A HTML button tag.
1869:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-standalone-buttons-and-post-links
1870:      */
1871:     public function postButton($title, $url, array $options = [])
1872:     {
1873:         $formOptions = ['url' => $url];
1874:         if (isset($options['method'])) {
1875:             $formOptions['type'] = $options['method'];
1876:             unset($options['method']);
1877:         }
1878:         if (isset($options['form']) && is_array($options['form'])) {
1879:             $formOptions = $options['form'] + $formOptions;
1880:             unset($options['form']);
1881:         }
1882:         $out = $this->create(false, $formOptions);
1883:         if (isset($options['data']) && is_array($options['data'])) {
1884:             foreach (Hash::flatten($options['data']) as $key => $value) {
1885:                 $out .= $this->hidden($key, ['value' => $value]);
1886:             }
1887:             unset($options['data']);
1888:         }
1889:         $out .= $this->button($title, $options);
1890:         $out .= $this->end();
1891: 
1892:         return $out;
1893:     }
1894: 
1895:     /**
1896:      * Creates an HTML link, but access the URL using the method you specify
1897:      * (defaults to POST). Requires javascript to be enabled in browser.
1898:      *
1899:      * This method creates a `<form>` element. If you want to use this method inside of an
1900:      * existing form, you must use the `block` option so that the new form is being set to
1901:      * a view block that can be rendered outside of the main form.
1902:      *
1903:      * If all you are looking for is a button to submit your form, then you should use
1904:      * `FormHelper::button()` or `FormHelper::submit()` instead.
1905:      *
1906:      * ### Options:
1907:      *
1908:      * - `data` - Array with key/value to pass in input hidden
1909:      * - `method` - Request method to use. Set to 'delete' to simulate
1910:      *   HTTP/1.1 DELETE request. Defaults to 'post'.
1911:      * - `confirm` - Confirm message to show. Form execution will only continue if confirmed then.
1912:      * - `block` - Set to true to append form to view block "postLink" or provide
1913:      *   custom block name.
1914:      * - Other options are the same of HtmlHelper::link() method.
1915:      * - The option `onclick` will be replaced.
1916:      *
1917:      * @param string $title The content to be wrapped by <a> tags.
1918:      * @param string|array|null $url Cake-relative URL or array of URL parameters, or
1919:      *   external URL (starts with http://)
1920:      * @param array $options Array of HTML attributes.
1921:      * @return string An `<a />` element.
1922:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-standalone-buttons-and-post-links
1923:      */
1924:     public function postLink($title, $url = null, array $options = [])
1925:     {
1926:         $options += ['block' => null, 'confirm' => null];
1927: 
1928:         $requestMethod = 'POST';
1929:         if (!empty($options['method'])) {
1930:             $requestMethod = strtoupper($options['method']);
1931:             unset($options['method']);
1932:         }
1933: 
1934:         $confirmMessage = $options['confirm'];
1935:         unset($options['confirm']);
1936: 
1937:         $formName = str_replace('.', '', uniqid('post_', true));
1938:         $formOptions = [
1939:             'name' => $formName,
1940:             'style' => 'display:none;',
1941:             'method' => 'post',
1942:         ];
1943:         if (isset($options['target'])) {
1944:             $formOptions['target'] = $options['target'];
1945:             unset($options['target']);
1946:         }
1947:         $templater = $this->templater();
1948: 
1949:         $restoreAction = $this->_lastAction;
1950:         $this->_lastAction($url);
1951: 
1952:         $action = $templater->formatAttributes([
1953:             'action' => $this->Url->build($url),
1954:             'escape' => false
1955:         ]);
1956: 
1957:         $out = $this->formatTemplate('formStart', [
1958:             'attrs' => $templater->formatAttributes($formOptions) . $action
1959:         ]);
1960:         $out .= $this->hidden('_method', [
1961:             'value' => $requestMethod,
1962:             'secure' => static::SECURE_SKIP
1963:         ]);
1964:         $out .= $this->_csrfField();
1965: 
1966:         $fields = [];
1967:         if (isset($options['data']) && is_array($options['data'])) {
1968:             foreach (Hash::flatten($options['data']) as $key => $value) {
1969:                 $fields[$key] = $value;
1970:                 $out .= $this->hidden($key, ['value' => $value, 'secure' => static::SECURE_SKIP]);
1971:             }
1972:             unset($options['data']);
1973:         }
1974:         $out .= $this->secure($fields);
1975:         $out .= $this->formatTemplate('formEnd', []);
1976:         $this->_lastAction = $restoreAction;
1977: 
1978:         if ($options['block']) {
1979:             if ($options['block'] === true) {
1980:                 $options['block'] = __FUNCTION__;
1981:             }
1982:             $this->_View->append($options['block'], $out);
1983:             $out = '';
1984:         }
1985:         unset($options['block']);
1986: 
1987:         $url = '#';
1988:         $onClick = 'document.' . $formName . '.submit();';
1989:         if ($confirmMessage) {
1990:             $confirm = $this->_confirm($confirmMessage, $onClick, '', $options);
1991:         } else {
1992:             $confirm = $onClick . ' ';
1993:         }
1994:         $confirm .= 'event.returnValue = false; return false;';
1995:         $options['onclick'] = $this->templater()->format('confirmJs', [
1996:             'confirmMessage' => $this->_cleanConfirmMessage($confirmMessage),
1997:             'formName' => $formName,
1998:             'confirm' => $confirm
1999:         ]);
2000: 
2001:         $out .= $this->Html->link($title, $url, $options);
2002: 
2003:         return $out;
2004:     }
2005: 
2006:     /**
2007:      * Creates a submit button element. This method will generate `<input />` elements that
2008:      * can be used to submit, and reset forms by using $options. image submits can be created by supplying an
2009:      * image path for $caption.
2010:      *
2011:      * ### Options
2012:      *
2013:      * - `type` - Set to 'reset' for reset inputs. Defaults to 'submit'
2014:      * - `templateVars` - Additional template variables for the input element and its container.
2015:      * - Other attributes will be assigned to the input element.
2016:      *
2017:      * @param string|null $caption The label appearing on the button OR if string contains :// or the
2018:      *  extension .jpg, .jpe, .jpeg, .gif, .png use an image if the extension
2019:      *  exists, AND the first character is /, image is relative to webroot,
2020:      *  OR if the first character is not /, image is relative to webroot/img.
2021:      * @param array $options Array of options. See above.
2022:      * @return string A HTML submit button
2023:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-buttons-and-submit-elements
2024:      */
2025:     public function submit($caption = null, array $options = [])
2026:     {
2027:         if (!is_string($caption) && empty($caption)) {
2028:             $caption = __d('cake', 'Submit');
2029:         }
2030:         $options += [
2031:             'type' => 'submit',
2032:             'secure' => false,
2033:             'templateVars' => []
2034:         ];
2035: 
2036:         if (isset($options['name'])) {
2037:             $this->_secure($options['secure'], $this->_secureFieldName($options['name']));
2038:         }
2039:         unset($options['secure']);
2040: 
2041:         $isUrl = strpos($caption, '://') !== false;
2042:         $isImage = preg_match('/\.(jpg|jpe|jpeg|gif|png|ico)$/', $caption);
2043: 
2044:         $type = $options['type'];
2045:         unset($options['type']);
2046: 
2047:         if ($isUrl || $isImage) {
2048:             $unlockFields = ['x', 'y'];
2049:             if (isset($options['name'])) {
2050:                 $unlockFields = [
2051:                     $options['name'] . '_x',
2052:                     $options['name'] . '_y'
2053:                 ];
2054:             }
2055:             foreach ($unlockFields as $ignore) {
2056:                 $this->unlockField($ignore);
2057:             }
2058:             $type = 'image';
2059:         }
2060: 
2061:         if ($isUrl) {
2062:             $options['src'] = $caption;
2063:         } elseif ($isImage) {
2064:             if ($caption[0] !== '/') {
2065:                 $url = $this->Url->webroot(Configure::read('App.imageBaseUrl') . $caption);
2066:             } else {
2067:                 $url = $this->Url->webroot(trim($caption, '/'));
2068:             }
2069:             $url = $this->Url->assetTimestamp($url);
2070:             $options['src'] = $url;
2071:         } else {
2072:             $options['value'] = $caption;
2073:         }
2074: 
2075:         $input = $this->formatTemplate('inputSubmit', [
2076:             'type' => $type,
2077:             'attrs' => $this->templater()->formatAttributes($options),
2078:             'templateVars' => $options['templateVars']
2079:         ]);
2080: 
2081:         return $this->formatTemplate('submitContainer', [
2082:             'content' => $input,
2083:             'templateVars' => $options['templateVars']
2084:         ]);
2085:     }
2086: 
2087:     /**
2088:      * Returns a formatted SELECT element.
2089:      *
2090:      * ### Attributes:
2091:      *
2092:      * - `multiple` - show a multiple select box. If set to 'checkbox' multiple checkboxes will be
2093:      *   created instead.
2094:      * - `empty` - If true, the empty select option is shown. If a string,
2095:      *   that string is displayed as the empty element.
2096:      * - `escape` - If true contents of options will be HTML entity encoded. Defaults to true.
2097:      * - `val` The selected value of the input.
2098:      * - `disabled` - Control the disabled attribute. When creating a select box, set to true to disable the
2099:      *   select box. Set to an array to disable specific option elements.
2100:      *
2101:      * ### Using options
2102:      *
2103:      * A simple array will create normal options:
2104:      *
2105:      * ```
2106:      * $options = [1 => 'one', 2 => 'two'];
2107:      * $this->Form->select('Model.field', $options));
2108:      * ```
2109:      *
2110:      * While a nested options array will create optgroups with options inside them.
2111:      * ```
2112:      * $options = [
2113:      *  1 => 'bill',
2114:      *     'fred' => [
2115:      *         2 => 'fred',
2116:      *         3 => 'fred jr.'
2117:      *     ]
2118:      * ];
2119:      * $this->Form->select('Model.field', $options);
2120:      * ```
2121:      *
2122:      * If you have multiple options that need to have the same value attribute, you can
2123:      * use an array of arrays to express this:
2124:      *
2125:      * ```
2126:      * $options = [
2127:      *     ['text' => 'United states', 'value' => 'USA'],
2128:      *     ['text' => 'USA', 'value' => 'USA'],
2129:      * ];
2130:      * ```
2131:      *
2132:      * @param string $fieldName Name attribute of the SELECT
2133:      * @param array|\Traversable $options Array of the OPTION elements (as 'value'=>'Text' pairs) to be used in the
2134:      *   SELECT element
2135:      * @param array $attributes The HTML attributes of the select element.
2136:      * @return string Formatted SELECT element
2137:      * @see \Cake\View\Helper\FormHelper::multiCheckbox() for creating multiple checkboxes.
2138:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-select-pickers
2139:      */
2140:     public function select($fieldName, $options = [], array $attributes = [])
2141:     {
2142:         $attributes += [
2143:             'disabled' => null,
2144:             'escape' => true,
2145:             'hiddenField' => true,
2146:             'multiple' => null,
2147:             'secure' => true,
2148:             'empty' => false,
2149:         ];
2150: 
2151:         if ($attributes['multiple'] === 'checkbox') {
2152:             unset($attributes['multiple'], $attributes['empty']);
2153: 
2154:             return $this->multiCheckbox($fieldName, $options, $attributes);
2155:         }
2156: 
2157:         unset($attributes['label']);
2158: 
2159:         // Secure the field if there are options, or it's a multi select.
2160:         // Single selects with no options don't submit, but multiselects do.
2161:         if ($attributes['secure'] &&
2162:             empty($options) &&
2163:             empty($attributes['empty']) &&
2164:             empty($attributes['multiple'])
2165:         ) {
2166:             $attributes['secure'] = false;
2167:         }
2168: 
2169:         $attributes = $this->_initInputField($fieldName, $attributes);
2170:         $attributes['options'] = $options;
2171: 
2172:         $hidden = '';
2173:         if ($attributes['multiple'] && $attributes['hiddenField']) {
2174:             $hiddenAttributes = [
2175:                 'name' => $attributes['name'],
2176:                 'value' => '',
2177:                 'form' => isset($attributes['form']) ? $attributes['form'] : null,
2178:                 'secure' => false,
2179:             ];
2180:             $hidden = $this->hidden($fieldName, $hiddenAttributes);
2181:         }
2182:         unset($attributes['hiddenField'], $attributes['type']);
2183: 
2184:         return $hidden . $this->widget('select', $attributes);
2185:     }
2186: 
2187:     /**
2188:      * Creates a set of checkboxes out of options.
2189:      *
2190:      * ### Options
2191:      *
2192:      * - `escape` - If true contents of options will be HTML entity encoded. Defaults to true.
2193:      * - `val` The selected value of the input.
2194:      * - `class` - When using multiple = checkbox the class name to apply to the divs. Defaults to 'checkbox'.
2195:      * - `disabled` - Control the disabled attribute. When creating checkboxes, `true` will disable all checkboxes.
2196:      *   You can also set disabled to a list of values you want to disable when creating checkboxes.
2197:      * - `hiddenField` - Set to false to remove the hidden field that ensures a value
2198:      *   is always submitted.
2199:      * - `label` - Either `false` to disable label around the widget or an array of attributes for
2200:      *   the label tag. `selected` will be added to any classes e.g. `'class' => 'myclass'` where
2201:      *   widget is checked
2202:      *
2203:      * Can be used in place of a select box with the multiple attribute.
2204:      *
2205:      * @param string $fieldName Name attribute of the SELECT
2206:      * @param array|\Traversable $options Array of the OPTION elements
2207:      *   (as 'value'=>'Text' pairs) to be used in the checkboxes element.
2208:      * @param array $attributes The HTML attributes of the select element.
2209:      * @return string Formatted SELECT element
2210:      * @see \Cake\View\Helper\FormHelper::select() for supported option formats.
2211:      */
2212:     public function multiCheckbox($fieldName, $options, array $attributes = [])
2213:     {
2214:         $attributes += [
2215:             'disabled' => null,
2216:             'escape' => true,
2217:             'hiddenField' => true,
2218:             'secure' => true,
2219:         ];
2220:         $attributes = $this->_initInputField($fieldName, $attributes);
2221:         $attributes['options'] = $options;
2222:         $attributes['idPrefix'] = $this->_idPrefix;
2223: 
2224:         $hidden = '';
2225:         if ($attributes['hiddenField']) {
2226:             $hiddenAttributes = [
2227:                 'name' => $attributes['name'],
2228:                 'value' => '',
2229:                 'secure' => false,
2230:                 'disabled' => $attributes['disabled'] === true || $attributes['disabled'] === 'disabled',
2231:             ];
2232:             $hidden = $this->hidden($fieldName, $hiddenAttributes);
2233:         }
2234:         unset($attributes['hiddenField']);
2235: 
2236:         return $hidden . $this->widget('multicheckbox', $attributes);
2237:     }
2238: 
2239:     /**
2240:      * Helper method for the various single datetime component methods.
2241:      *
2242:      * @param array $options The options array.
2243:      * @param string $keep The option to not disable.
2244:      * @return array
2245:      */
2246:     protected function _singleDatetime($options, $keep)
2247:     {
2248:         $off = array_diff($this->_datetimeParts, [$keep]);
2249:         $off = array_combine(
2250:             $off,
2251:             array_fill(0, count($off), false)
2252:         );
2253: 
2254:         $attributes = array_diff_key(
2255:             $options,
2256:             array_flip(array_merge($this->_datetimeOptions, ['value', 'empty']))
2257:         );
2258:         $options = $options + $off + [$keep => $attributes];
2259: 
2260:         if (isset($options['value'])) {
2261:             $options['val'] = $options['value'];
2262:         }
2263: 
2264:         return $options;
2265:     }
2266: 
2267:     /**
2268:      * Returns a SELECT element for days.
2269:      *
2270:      * ### Options:
2271:      *
2272:      * - `empty` - If true, the empty select option is shown. If a string,
2273:      *   that string is displayed as the empty element.
2274:      * - `value` The selected value of the input.
2275:      *
2276:      * @param string|null $fieldName Prefix name for the SELECT element
2277:      * @param array $options Options & HTML attributes for the select element
2278:      * @return string A generated day select box.
2279:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-day-inputs
2280:      */
2281:     public function day($fieldName = null, array $options = [])
2282:     {
2283:         $options = $this->_singleDatetime($options, 'day');
2284: 
2285:         if (isset($options['val']) && $options['val'] > 0 && $options['val'] <= 31) {
2286:             $options['val'] = [
2287:                 'year' => date('Y'),
2288:                 'month' => date('m'),
2289:                 'day' => (int)$options['val']
2290:             ];
2291:         }
2292: 
2293:         return $this->dateTime($fieldName, $options);
2294:     }
2295: 
2296:     /**
2297:      * Returns a SELECT element for years
2298:      *
2299:      * ### Attributes:
2300:      *
2301:      * - `empty` - If true, the empty select option is shown. If a string,
2302:      *   that string is displayed as the empty element.
2303:      * - `orderYear` - Ordering of year values in select options.
2304:      *   Possible values 'asc', 'desc'. Default 'desc'
2305:      * - `value` The selected value of the input.
2306:      * - `maxYear` The max year to appear in the select element.
2307:      * - `minYear` The min year to appear in the select element.
2308:      *
2309:      * @param string $fieldName Prefix name for the SELECT element
2310:      * @param array $options Options & attributes for the select elements.
2311:      * @return string Completed year select input
2312:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-year-inputs
2313:      */
2314:     public function year($fieldName, array $options = [])
2315:     {
2316:         $options = $this->_singleDatetime($options, 'year');
2317: 
2318:         $len = isset($options['val']) ? strlen($options['val']) : 0;
2319:         if (isset($options['val']) && $len > 0 && $len < 5) {
2320:             $options['val'] = [
2321:                 'year' => (int)$options['val'],
2322:                 'month' => date('m'),
2323:                 'day' => date('d')
2324:             ];
2325:         }
2326: 
2327:         return $this->dateTime($fieldName, $options);
2328:     }
2329: 
2330:     /**
2331:      * Returns a SELECT element for months.
2332:      *
2333:      * ### Options:
2334:      *
2335:      * - `monthNames` - If false, 2 digit numbers will be used instead of text.
2336:      *   If an array, the given array will be used.
2337:      * - `empty` - If true, the empty select option is shown. If a string,
2338:      *   that string is displayed as the empty element.
2339:      * - `value` The selected value of the input.
2340:      *
2341:      * @param string $fieldName Prefix name for the SELECT element
2342:      * @param array $options Attributes for the select element
2343:      * @return string A generated month select dropdown.
2344:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-month-inputs
2345:      */
2346:     public function month($fieldName, array $options = [])
2347:     {
2348:         $options = $this->_singleDatetime($options, 'month');
2349: 
2350:         if (isset($options['val']) && $options['val'] > 0 && $options['val'] <= 12) {
2351:             $options['val'] = [
2352:                 'year' => date('Y'),
2353:                 'month' => (int)$options['val'],
2354:                 'day' => date('d')
2355:             ];
2356:         }
2357: 
2358:         return $this->dateTime($fieldName, $options);
2359:     }
2360: 
2361:     /**
2362:      * Returns a SELECT element for hours.
2363:      *
2364:      * ### Attributes:
2365:      *
2366:      * - `empty` - If true, the empty select option is shown. If a string,
2367:      *   that string is displayed as the empty element.
2368:      * - `value` The selected value of the input.
2369:      * - `format` Set to 12 or 24 to use 12 or 24 hour formatting. Defaults to 24.
2370:      *
2371:      * @param string $fieldName Prefix name for the SELECT element
2372:      * @param array $options List of HTML attributes
2373:      * @return string Completed hour select input
2374:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-hour-inputs
2375:      */
2376:     public function hour($fieldName, array $options = [])
2377:     {
2378:         $options += ['format' => 24];
2379:         $options = $this->_singleDatetime($options, 'hour');
2380: 
2381:         $options['timeFormat'] = $options['format'];
2382:         unset($options['format']);
2383: 
2384:         if (isset($options['val']) && $options['val'] > 0 && $options['val'] <= 24) {
2385:             $options['val'] = [
2386:                 'hour' => (int)$options['val'],
2387:                 'minute' => date('i'),
2388:             ];
2389:         }
2390: 
2391:         return $this->dateTime($fieldName, $options);
2392:     }
2393: 
2394:     /**
2395:      * Returns a SELECT element for minutes.
2396:      *
2397:      * ### Attributes:
2398:      *
2399:      * - `empty` - If true, the empty select option is shown. If a string,
2400:      *   that string is displayed as the empty element.
2401:      * - `value` The selected value of the input.
2402:      * - `interval` The interval that minute options should be created at.
2403:      * - `round` How you want the value rounded when it does not fit neatly into an
2404:      *   interval. Accepts 'up', 'down', and null.
2405:      *
2406:      * @param string $fieldName Prefix name for the SELECT element
2407:      * @param array $options Array of options.
2408:      * @return string Completed minute select input.
2409:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-minute-inputs
2410:      */
2411:     public function minute($fieldName, array $options = [])
2412:     {
2413:         $options = $this->_singleDatetime($options, 'minute');
2414: 
2415:         if (isset($options['val']) && $options['val'] > 0 && $options['val'] <= 60) {
2416:             $options['val'] = [
2417:                 'hour' => date('H'),
2418:                 'minute' => (int)$options['val'],
2419:             ];
2420:         }
2421: 
2422:         return $this->dateTime($fieldName, $options);
2423:     }
2424: 
2425:     /**
2426:      * Returns a SELECT element for AM or PM.
2427:      *
2428:      * ### Attributes:
2429:      *
2430:      * - `empty` - If true, the empty select option is shown. If a string,
2431:      *   that string is displayed as the empty element.
2432:      * - `value` The selected value of the input.
2433:      *
2434:      * @param string $fieldName Prefix name for the SELECT element
2435:      * @param array $options Array of options
2436:      * @return string Completed meridian select input
2437:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-meridian-inputs
2438:      */
2439:     public function meridian($fieldName, array $options = [])
2440:     {
2441:         $options = $this->_singleDatetime($options, 'meridian');
2442: 
2443:         if (isset($options['val'])) {
2444:             $hour = date('H');
2445:             $options['val'] = [
2446:                 'hour' => $hour,
2447:                 'minute' => (int)$options['val'],
2448:                 'meridian' => $hour > 11 ? 'pm' : 'am',
2449:             ];
2450:         }
2451: 
2452:         return $this->dateTime($fieldName, $options);
2453:     }
2454: 
2455:     /**
2456:      * Returns a set of SELECT elements for a full datetime setup: day, month and year, and then time.
2457:      *
2458:      * ### Date Options:
2459:      *
2460:      * - `empty` - If true, the empty select option is shown. If a string,
2461:      *   that string is displayed as the empty element.
2462:      * - `value` | `default` The default value to be used by the input. A value in `$this->data`
2463:      *   matching the field name will override this value. If no default is provided `time()` will be used.
2464:      * - `monthNames` If false, 2 digit numbers will be used instead of text.
2465:      *   If an array, the given array will be used.
2466:      * - `minYear` The lowest year to use in the year select
2467:      * - `maxYear` The maximum year to use in the year select
2468:      * - `orderYear` - Order of year values in select options.
2469:      *   Possible values 'asc', 'desc'. Default 'desc'.
2470:      *
2471:      * ### Time options:
2472:      *
2473:      * - `empty` - If true, the empty select option is shown. If a string,
2474:      * - `value` | `default` The default value to be used by the input. A value in `$this->data`
2475:      *   matching the field name will override this value. If no default is provided `time()` will be used.
2476:      * - `timeFormat` The time format to use, either 12 or 24.
2477:      * - `interval` The interval for the minutes select. Defaults to 1
2478:      * - `round` - Set to `up` or `down` if you want to force rounding in either direction. Defaults to null.
2479:      * - `second` Set to true to enable seconds drop down.
2480:      *
2481:      * To control the order of inputs, and any elements/content between the inputs you
2482:      * can override the `dateWidget` template. By default the `dateWidget` template is:
2483:      *
2484:      * `{{month}}{{day}}{{year}}{{hour}}{{minute}}{{second}}{{meridian}}`
2485:      *
2486:      * @param string $fieldName Prefix name for the SELECT element
2487:      * @param array $options Array of Options
2488:      * @return string Generated set of select boxes for the date and time formats chosen.
2489:      * @link https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-date-and-time-inputs
2490:      */
2491:     public function dateTime($fieldName, array $options = [])
2492:     {
2493:         $options += [
2494:             'empty' => true,
2495:             'value' => null,
2496:             'interval' => 1,
2497:             'round' => null,
2498:             'monthNames' => true,
2499:             'minYear' => null,
2500:             'maxYear' => null,
2501:             'orderYear' => 'desc',
2502:             'timeFormat' => 24,
2503:             'second' => false,
2504:         ];
2505:         $options = $this->_initInputField($fieldName, $options);
2506:         $options = $this->_datetimeOptions($options);
2507: 
2508:         return $this->widget('datetime', $options);
2509:     }
2510: 
2511:     /**
2512:      * Helper method for converting from FormHelper options data to widget format.
2513:      *
2514:      * @param array $options Options to convert.
2515:      * @return array Converted options.
2516:      */
2517:     protected function _datetimeOptions($options)
2518:     {
2519:         foreach ($this->_datetimeParts as $type) {
2520:             if (!array_key_exists($type, $options)) {
2521:                 $options[$type] = [];
2522:             }
2523:             if ($options[$type] === true) {
2524:                 $options[$type] = [];
2525:             }
2526: 
2527:             // Pass boolean/scalar empty options to each type.
2528:             if (is_array($options[$type]) && isset($options['empty']) && !is_array($options['empty'])) {
2529:                 $options[$type]['empty'] = $options['empty'];
2530:             }
2531: 
2532:             // Move empty options into each type array.
2533:             if (isset($options['empty'][$type])) {
2534:                 $options[$type]['empty'] = $options['empty'][$type];
2535:             }
2536:             if (isset($options['required']) && is_array($options[$type])) {
2537:                 $options[$type]['required'] = $options['required'];
2538:             }
2539:         }
2540: 
2541:         $hasYear = is_array($options['year']);
2542:         if ($hasYear && isset($options['minYear'])) {
2543:             $options['year']['start'] = $options['minYear'];
2544:         }
2545:         if ($hasYear && isset($options['maxYear'])) {
2546:             $options['year']['end'] = $options['maxYear'];
2547:         }
2548:         if ($hasYear && isset($options['orderYear'])) {
2549:             $options['year']['order'] = $options['orderYear'];
2550:         }
2551:         unset($options['minYear'], $options['maxYear'], $options['orderYear']);
2552: 
2553:         if (is_array($options['month'])) {
2554:             $options['month']['names'] = $options['monthNames'];
2555:         }
2556:         unset($options['monthNames']);
2557: 
2558:         if (is_array($options['hour']) && isset($options['timeFormat'])) {
2559:             $options['hour']['format'] = $options['timeFormat'];
2560:         }
2561:         unset($options['timeFormat']);
2562: 
2563:         if (is_array($options['minute'])) {
2564:             $options['minute']['interval'] = $options['interval'];
2565:             $options['minute']['round'] = $options['round'];
2566:         }
2567:         unset($options['interval'], $options['round']);
2568: 
2569:         if ($options['val'] === true || $options['val'] === null && isset($options['empty']) && $options['empty'] === false) {
2570:             $val = new DateTime();
2571:             $currentYear = $val->format('Y');
2572:             if (isset($options['year']['end']) && $options['year']['end'] < $currentYear) {
2573:                 $val->setDate($options['year']['end'], $val->format('n'), $val->format('j'));
2574:             }
2575:             $options['val'] = $val;
2576:         }
2577: 
2578:         unset($options['empty']);
2579: 
2580:         return $options;
2581:     }
2582: 
2583:     /**
2584:      * Generate time inputs.
2585:      *
2586:      * ### Options:
2587:      *
2588:      * See dateTime() for time options.
2589:      *
2590:      * @param string $fieldName Prefix name for the SELECT element
2591:      * @param array $options Array of Options
2592:      * @return string Generated set of select boxes for time formats chosen.
2593:      * @see \Cake\View\Helper\FormHelper::dateTime() for templating options.
2594:      */
2595:     public function time($fieldName, array $options = [])
2596:     {
2597:         $options += [
2598:             'empty' => true,
2599:             'value' => null,
2600:             'interval' => 1,
2601:             'round' => null,
2602:             'timeFormat' => 24,
2603:             'second' => false,
2604:         ];
2605:         $options['year'] = $options['month'] = $options['day'] = false;
2606:         $options = $this->_initInputField($fieldName, $options);
2607:         $options = $this->_datetimeOptions($options);
2608: 
2609:         return $this->widget('datetime', $options);
2610:     }
2611: 
2612:     /**
2613:      * Generate date inputs.
2614:      *
2615:      * ### Options:
2616:      *
2617:      * See dateTime() for date options.
2618:      *
2619:      * @param string $fieldName Prefix name for the SELECT element
2620:      * @param array $options Array of Options
2621:      * @return string Generated set of select boxes for time formats chosen.
2622:      * @see \Cake\View\Helper\FormHelper::dateTime() for templating options.
2623:      */
2624:     public function date($fieldName, array $options = [])
2625:     {
2626:         $options += [
2627:             'empty' => true,
2628:             'value' => null,
2629:             'monthNames' => true,
2630:             'minYear' => null,
2631:             'maxYear' => null,
2632:             'orderYear' => 'desc',
2633:         ];
2634:         $options['hour'] = $options['minute'] = false;
2635:         $options['meridian'] = $options['second'] = false;
2636: 
2637:         $options = $this->_initInputField($fieldName, $options);
2638:         $options = $this->_datetimeOptions($options);
2639: 
2640:         return $this->widget('datetime', $options);
2641:     }
2642: 
2643:     /**
2644:      * Sets field defaults and adds field to form security input hash.
2645:      * Will also add the error class if the field contains validation errors.
2646:      *
2647:      * ### Options
2648:      *
2649:      * - `secure` - boolean whether or not the field should be added to the security fields.
2650:      *   Disabling the field using the `disabled` option, will also omit the field from being
2651:      *   part of the hashed key.
2652:      * - `default` - mixed - The value to use if there is no value in the form's context.
2653:      * - `disabled` - mixed - Either a boolean indicating disabled state, or the string in
2654:      *   a numerically indexed value.
2655:      * - `id` - mixed - If `true` it will be auto generated based on field name.
2656:      *
2657:      * This method will convert a numerically indexed 'disabled' into an associative
2658:      * array value. FormHelper's internals expect associative options.
2659:      *
2660:      * The output of this function is a more complete set of input attributes that
2661:      * can be passed to a form widget to generate the actual input.
2662:      *
2663:      * @param string $field Name of the field to initialize options for.
2664:      * @param array $options Array of options to append options into.
2665:      * @return array Array of options for the input.
2666:      */
2667:     protected function _initInputField($field, $options = [])
2668:     {
2669:         if (!isset($options['secure'])) {
2670:             $options['secure'] = (bool)$this->_View->getRequest()->getParam('_Token');
2671:         }
2672:         $context = $this->_getContext();
2673: 
2674:         if (isset($options['id']) && $options['id'] === true) {
2675:             $options['id'] = $this->_domId($field);
2676:         }
2677: 
2678:         $disabledIndex = array_search('disabled', $options, true);
2679:         if (is_int($disabledIndex)) {
2680:             unset($options[$disabledIndex]);
2681:             $options['disabled'] = true;
2682:         }
2683: 
2684:         if (!isset($options['name'])) {
2685:             $endsWithBrackets = '';
2686:             if (substr($field, -2) === '[]') {
2687:                 $field = substr($field, 0, -2);
2688:                 $endsWithBrackets = '[]';
2689:             }
2690:             $parts = explode('.', $field);
2691:             $first = array_shift($parts);
2692:             $options['name'] = $first . (!empty($parts) ? '[' . implode('][', $parts) . ']' : '') . $endsWithBrackets;
2693:         }
2694: 
2695:         if (isset($options['value']) && !isset($options['val'])) {
2696:             $options['val'] = $options['value'];
2697:             unset($options['value']);
2698:         }
2699:         if (!isset($options['val'])) {
2700:             $valOptions = [
2701:                 'default' => isset($options['default']) ? $options['default'] : null,
2702:                 'schemaDefault' => isset($options['schemaDefault']) ? $options['schemaDefault'] : true,
2703:             ];
2704:             $options['val'] = $this->getSourceValue($field, $valOptions);
2705:         }
2706:         if (!isset($options['val']) && isset($options['default'])) {
2707:             $options['val'] = $options['default'];
2708:         }
2709:         unset($options['value'], $options['default']);
2710: 
2711:         if ($context->hasError($field)) {
2712:             $options = $this->addClass($options, $this->_config['errorClass']);
2713:         }
2714:         $isDisabled = $this->_isDisabled($options);
2715:         if ($isDisabled) {
2716:             $options['secure'] = self::SECURE_SKIP;
2717:         }
2718:         if ($options['secure'] === self::SECURE_SKIP) {
2719:             return $options;
2720:         }
2721:         if (!isset($options['required']) && empty($options['disabled']) && $context->isRequired($field)) {
2722:             $options['required'] = true;
2723:         }
2724: 
2725:         return $options;
2726:     }
2727: 
2728:     /**
2729:      * Determine if a field is disabled.
2730:      *
2731:      * @param array $options The option set.
2732:      * @return bool Whether or not the field is disabled.
2733:      */
2734:     protected function _isDisabled(array $options)
2735:     {
2736:         if (!isset($options['disabled'])) {
2737:             return false;
2738:         }
2739:         if (is_scalar($options['disabled'])) {
2740:             return ($options['disabled'] === true || $options['disabled'] === 'disabled');
2741:         }
2742:         if (!isset($options['options'])) {
2743:             return false;
2744:         }
2745:         if (is_array($options['options'])) {
2746:             // Simple list options
2747:             $first = $options['options'][array_keys($options['options'])[0]];
2748:             if (is_scalar($first)) {
2749:                 return array_diff($options['options'], $options['disabled']) === [];
2750:             }
2751:             // Complex option types
2752:             if (is_array($first)) {
2753:                 $disabled = array_filter($options['options'], function ($i) use ($options) {
2754:                     return in_array($i['value'], $options['disabled']);
2755:                 });
2756: 
2757:                 return count($disabled) > 0;
2758:             }
2759:         }
2760: 
2761:         return false;
2762:     }
2763: 
2764:     /**
2765:      * Get the field name for use with _secure().
2766:      *
2767:      * Parses the name attribute to create a dot separated name value for use
2768:      * in secured field hash. If filename is of form Model[field] an array of
2769:      * fieldname parts like ['Model', 'field'] is returned.
2770:      *
2771:      * @param string $name The form inputs name attribute.
2772:      * @return array Array of field name params like ['Model.field'] or
2773:      *   ['Model', 'field'] for array fields or empty array if $name is empty.
2774:      */
2775:     protected function _secureFieldName($name)
2776:     {
2777:         if (empty($name) && $name !== '0') {
2778:             return [];
2779:         }
2780: 
2781:         if (strpos($name, '[') === false) {
2782:             return [$name];
2783:         }
2784:         $parts = explode('[', $name);
2785:         $parts = array_map(function ($el) {
2786:             return trim($el, ']');
2787:         }, $parts);
2788: 
2789:         return array_filter($parts, 'strlen');
2790:     }
2791: 
2792:     /**
2793:      * Add a new context type.
2794:      *
2795:      * Form context types allow FormHelper to interact with
2796:      * data providers that come from outside CakePHP. For example
2797:      * if you wanted to use an alternative ORM like Doctrine you could
2798:      * create and connect a new context class to allow FormHelper to
2799:      * read metadata from doctrine.
2800:      *
2801:      * @param string $type The type of context. This key
2802:      *   can be used to overwrite existing providers.
2803:      * @param callable $check A callable that returns an object
2804:      *   when the form context is the correct type.
2805:      * @return void
2806:      */
2807:     public function addContextProvider($type, callable $check)
2808:     {
2809:         $this->contextFactory()->addProvider($type, $check);
2810:     }
2811: 
2812:     /**
2813:      * Get the context instance for the current form set.
2814:      *
2815:      * If there is no active form null will be returned.
2816:      *
2817:      * @param \Cake\View\Form\ContextInterface|null $context Either the new context when setting, or null to get.
2818:      * @return \Cake\View\Form\ContextInterface The context for the form.
2819:      */
2820:     public function context($context = null)
2821:     {
2822:         if ($context instanceof ContextInterface) {
2823:             $this->_context = $context;
2824:         }
2825: 
2826:         return $this->_getContext();
2827:     }
2828: 
2829:     /**
2830:      * Find the matching context provider for the data.
2831:      *
2832:      * If no type can be matched a NullContext will be returned.
2833:      *
2834:      * @param mixed $data The data to get a context provider for.
2835:      * @return \Cake\View\Form\ContextInterface Context provider.
2836:      * @throws \RuntimeException when the context class does not implement the
2837:      *   ContextInterface.
2838:      */
2839:     protected function _getContext($data = [])
2840:     {
2841:         if (isset($this->_context) && empty($data)) {
2842:             return $this->_context;
2843:         }
2844:         $data += ['entity' => null];
2845: 
2846:         return $this->_context = $this->contextFactory()
2847:             ->get($this->_View->getRequest(), $data);
2848:     }
2849: 
2850:     /**
2851:      * Add a new widget to FormHelper.
2852:      *
2853:      * Allows you to add or replace widget instances with custom code.
2854:      *
2855:      * @param string $name The name of the widget. e.g. 'text'.
2856:      * @param array|\Cake\View\Widget\WidgetInterface $spec Either a string class
2857:      *   name or an object implementing the WidgetInterface.
2858:      * @return void
2859:      */
2860:     public function addWidget($name, $spec)
2861:     {
2862:         $this->_locator->add([$name => $spec]);
2863:     }
2864: 
2865:     /**
2866:      * Render a named widget.
2867:      *
2868:      * This is a lower level method. For built-in widgets, you should be using
2869:      * methods like `text`, `hidden`, and `radio`. If you are using additional
2870:      * widgets you should use this method render the widget without the label
2871:      * or wrapping div.
2872:      *
2873:      * @param string $name The name of the widget. e.g. 'text'.
2874:      * @param array $data The data to render.
2875:      * @return string
2876:      */
2877:     public function widget($name, array $data = [])
2878:     {
2879:         $secure = null;
2880:         if (isset($data['secure'])) {
2881:             $secure = $data['secure'];
2882:             unset($data['secure']);
2883:         }
2884:         $widget = $this->_locator->get($name);
2885:         $out = $widget->render($data, $this->context());
2886:         if (isset($data['name']) && $secure !== null && $secure !== self::SECURE_SKIP) {
2887:             foreach ($widget->secureFields($data) as $field) {
2888:                 $this->_secure($secure, $this->_secureFieldName($field));
2889:             }
2890:         }
2891: 
2892:         return $out;
2893:     }
2894: 
2895:     /**
2896:      * Restores the default values built into FormHelper.
2897:      *
2898:      * This method will not reset any templates set in custom widgets.
2899:      *
2900:      * @return void
2901:      */
2902:     public function resetTemplates()
2903:     {
2904:         $this->setTemplates($this->_defaultConfig['templates']);
2905:     }
2906: 
2907:     /**
2908:      * Event listeners.
2909:      *
2910:      * @return array
2911:      */
2912:     public function implementedEvents()
2913:     {
2914:         return [];
2915:     }
2916: 
2917:     /**
2918:      * Gets the value sources.
2919:      *
2920:      * Returns a list, but at least one item, of valid sources, such as: `'context'`, `'data'` and `'query'`.
2921:      *
2922:      * @return string[] List of value sources.
2923:      */
2924:     public function getValueSources()
2925:     {
2926:         return $this->_valueSources;
2927:     }
2928: 
2929:     /**
2930:      * Sets the value sources.
2931:      *
2932:      * Valid values are `'context'`, `'data'` and `'query'`.
2933:      * You need to supply one valid context or multiple, as a list of strings. Order sets priority.
2934:      *
2935:      * @param string|string[] $sources A string or a list of strings identifying a source.
2936:      * @return $this
2937:      */
2938:     public function setValueSources($sources)
2939:     {
2940:         $this->_valueSources = array_values(array_intersect((array)$sources, ['context', 'data', 'query']));
2941: 
2942:         return $this;
2943:     }
2944: 
2945:     /**
2946:      * Gets a single field value from the sources available.
2947:      *
2948:      * @param string $fieldname The fieldname to fetch the value for.
2949:      * @param array|null $options The options containing default values.
2950:      * @return string|null Field value derived from sources or defaults.
2951:      */
2952:     public function getSourceValue($fieldname, $options = [])
2953:     {
2954:         $valueMap = [
2955:             'data' => 'getData',
2956:             'query' => 'getQuery'
2957:         ];
2958:         foreach ($this->getValueSources() as $valuesSource) {
2959:             if ($valuesSource === 'context') {
2960:                 $val = $this->_getContext()->val($fieldname, $options);
2961:                 if ($val !== null) {
2962:                     return $val;
2963:                 }
2964:             }
2965:             if (isset($valueMap[$valuesSource])) {
2966:                 $method = $valueMap[$valuesSource];
2967:                 $value = $this->_View->getRequest()->{$method}($fieldname);
2968:                 if ($value !== null) {
2969:                     return $value;
2970:                 }
2971:             }
2972:         }
2973: 
2974:         return null;
2975:     }
2976: }
2977: 
Follow @CakePHP
#IRC
OpenHub
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Logos & Trademarks
  • Community
  • Team
  • Issues (Github)
  • YouTube Channel
  • Get Involved
  • Bakery
  • Featured Resources
  • Newsletter
  • Certification
  • My CakePHP
  • CakeFest
  • Facebook
  • Twitter
  • Help & Support
  • Forum
  • Stack Overflow
  • IRC
  • Slack
  • Paid Support

Generated using CakePHP API Docs