1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530:
<?php
/**
* MvcCore
*
* This source file is subject to the BSD 3 License
* For the full copyright and license information, please view
* the LICENSE.md file that are distributed with this source code.
*
* @copyright Copyright (c) 2016 Tom Flidr (https://github.com/mvccore)
* @license https://mvccore.github.io/docs/mvccore/5.0.0/LICENCE.md
*/
namespace MvcCore\Ext\Forms;
/**
* Responsibility: create and extended MvcCore view instance to render form or
* form field with custom view template. This view contains built-in properties:
* - `view` - Containing parent controller view.
* - `form` - Containing rendered form instance when form or field is rendered.
* - `field` - Containing rendered field instance when field is rendered.
* This view also contains many built-in methods to render specific form parts:
* - `RenderBegin()` - Renders opening `<form>` tag with all configured
* attributes.
* - `RenderErrors()` - Renders translated form errors.
* - `RenderContent()` - Render all configured form fields from
* `$this->form->GetFields()` array by calling `Render()`
* method on every field instance.
* - `RenderEnd()` - Renders opening `<form>` tag and configured form
* field's supporting js/css files.
* - `static Format()`
*/
class View extends \MvcCore\View {
/**
* Rendered form instance reference, which view belongs to.
* @var \MvcCore\Ext\Form|NULL
*/
protected $form = NULL;
/**
* Rendered form field reference if view is not form's view.
* @var \MvcCore\Ext\Forms\Field|NULL
*/
protected $field = NULL;
/**
* Controller view instance reference, which form belongs to.
* Every form is usually created inside MvcCore controller instance,
* and mostly every controller instance has it's own view.
* @var \MvcCore\View|NULL
*/
protected $view = NULL;
/**
* Global views forms directory placed by default
* inside `"/App/Views"` directory.
* Default value is `"Forms"`, so scripts app path
* is `"/App/Views/Forms"`.
* @var string
*/
protected static $formsDir = 'Forms';
/**
* Global views fields directory placed by default
* inside `"/App/Views"` directory.
* Default value is `"Forms/Fields"`, so
* scripts app path is `"/App/Views/Forms/Fields"`.
* @var string
*/
protected static $fieldsDir = 'Forms/Fields';
/**
* Get global views forms directory placed by default
* inside `"/App/Views"` directory.
* Default value is `"Forms"`, so scripts app path
* is `"/App/Views/Forms"`.
* @return string
*/
public static function GetFormsDir () {
return static::$formsDir;
}
/**
* Set global views forms directory placed by default
* inside `"/App/Views"` directory.
* Default value is `"Forms"`, so scripts app path
* is `"/App/Views/Forms"`.
* @param string $formsDir
* @return string
*/
public static function SetFormsDir ($formsDir = 'Forms') {
return static::$formsDir = $formsDir;
}
/**
* Get global views fields directory placed by default
* inside `"/App/Views"` directory.
* Default value is `"Forms/Fields"`, so
* scripts app path is `"/App/Views/Forms/Fields"`.
* @return string
*/
public static function GetFieldsDir () {
return static::$fieldsDir;
}
/**
* Set global views fields directory placed by default
* inside `"/App/Views"` directory.
* Default value is `"Forms/Fields"`, so
* scripts app path is `"/App/Views/Forms/Fields"`.
* @param string $fieldsDir
* @return string
*/
public static function SetFieldsDir ($fieldsDir = 'Forms/Fields') {
return static::$fieldsDir = $fieldsDir;
}
public function __construct () {
/**
* Default flag if view is used for field rendering or only for form
* rendering. Default value is for form rendering - `FALSE`.
*/
$this->__protected['fieldRendering'] = FALSE;
}
/**
* Get controller instance as reference.
* @return \MvcCore\View
*/
public function GetView () {
return $this->view;
}
/**
* Set controller and it's view instance.
* @param \MvcCore\View $view
* @return \MvcCore\Ext\Forms\View
*/
public function SetView (\MvcCore\IView $view) {
$this->view = $view;
return $this;
}
/**
* Get form instance to render.
* @return \MvcCore\Ext\Form
*/
public function GetForm () {
return $this->form;
}
/**
* Set form instance to render.
* @param \MvcCore\Ext\Form $form
* @return \MvcCore\Ext\Forms\View
*/
public function SetForm (\MvcCore\Ext\IForm $form) {
$this->form = $form;
return $this;
}
/**
* Get rendered field.
* @return \MvcCore\Ext\Forms\Field
*/
public function GetField () {
return $this->field;
}
/**
* Set rendered field.
* @param \MvcCore\Ext\Forms\Field $field
* @return \MvcCore\Ext\Forms\View
*/
public function SetField (\MvcCore\Ext\Forms\IField $field) {
$this->field = $field;
$this->__protected['fieldRendering'] = TRUE;
return $this;
}
/**
* Get any value by given name existing in local store. If there is no value
* in local store by given name, try to get result value into store by
* field reflection class from field instance property if view is used for
* field rendering. If there is still no value found, try to get result value
* into store by form reflection class from form instance property and if
* still no value found, try to get result value from local view instance
* `__get()` method.
* @param string $name
* @return mixed
*/
public function __get ($name) {
/** @var $store array */
$store = & $this->__protected['store'];
$phpWithTypes = PHP_VERSION_ID >= 70400;
// if property is in view store - return it
if (array_key_exists($name, $store))
return $store[$name];
/** @var $property \ReflectionProperty */
// if property is not in store and this view is used for field rendering,
// try to complete result with property by `$name` in `$this->field` instance:
if (
$this->__protected['fieldRendering'] &&
$this->field instanceof \MvcCore\Ext\Forms\IField &&
$fieldType = $this->getReflectionClass('field')
) {
if ($fieldType->hasProperty($name)) {
$property = $fieldType->getProperty($name);
if (!$property->isStatic()) {
if (!$property->isPublic()) $property->setAccessible (TRUE); // protected or private
$value = NULL;
if (PHP_VERSION_ID >= 70400 && $property->hasType()) {
if ($property->isInitialized($this->field))
$value = $property->getValue($this->field);
} else {
$value = $property->getValue($this->field);
}
$store[$name] = & $value;
return $value;
}
}
}
// if property is still not in store, try to complete result with property by
// `$name` in `$this->form` instance:
if (
$this->form instanceof \MvcCore\Ext\IForm &&
$formType = $this->getReflectionClass('form')
) {
if ($formType->hasProperty($name)) {
$property = $formType->getProperty($name);
if (!$property->isStatic()) {
if (!$property->isPublic()) $property->setAccessible (TRUE); // protected or private
$value = NULL;
if (PHP_VERSION_ID >= 70400 && $property->hasType()) {
if ($property->isInitialized($this->form))
$value = $property->getValue($this->form);
} else {
$value = $property->getValue($this->form);
}
$store[$name] = & $value;
return $value;
}
}
}
// if property is still not in store, try to complete result by given view
// instance, which search in it's store and in it's controller instance:
if ($this->view instanceof \MvcCore\IView)
return $this->view->__get($name);
// return NULL, if property is not in local store an even anywhere else
return NULL;
}
/**
* Get `TRUE` by given name existing in local store. If there is no value
* in local store by given name, try to get result value into store by
* field reflection class from field instance property if view is used for
* field rendering. If there is still no value found, try to get result value
* into store by form reflection class from form instance property and if
* still no value found, try to get result value from local view instance
* `__get()` method.
* @param string $name
* @return bool
*/
public function __isset ($name) {
/** @var $store array */
$store = & $this->__protected['store'];
$phpWithTypes = PHP_VERSION_ID >= 70400;
// if property is in view store - return TRUE
if (array_key_exists($name, $store)) return TRUE;
/** @var $property \ReflectionProperty */
// if property is not in store and this view is used for field rendering,
// try to complete result with property by `$name` in `$this->field` instance:
if (
$this->__protected['fieldRendering'] &&
$this->field instanceof \MvcCore\Ext\Forms\IField &&
$fieldType = $this->getReflectionClass('field')
) {
if ($fieldType->hasProperty($name)) {
$property = $fieldType->getProperty($name);
if (!$property->isStatic()) {
if (!$property->isPublic())
$property->setAccessible (TRUE); // protected or private
$value = NULL;
if ($phpWithTypes && $property->hasType()) {
if ($property->isInitialized($this->field))
$value = $property->getValue($this->field);
} else {
$value = $property->getValue($this->field);
}
$store[$name] = & $value;
return TRUE;
}
}
}
// if property is still not in store, try to complete result with property by
// `$name` in `$this->form` instance:
if (
$this->form instanceof \MvcCore\Ext\IForm &&
$formType = $this->getReflectionClass('form')
) {
if ($formType->hasProperty($name)) {
$property = $formType->getProperty($name);
if (!$property->isStatic()) {
if (!$property->isPublic())
$property->setAccessible (TRUE); // protected or private
$value = NULL;
if ($phpWithTypes && $property->hasType()) {
if ($property->isInitialized($this->form))
$value = $property->getValue($this->form);
} else {
$value = $property->getValue($this->form);
}
$store[$name] = & $value;
return TRUE;
}
}
}
// if property is still not in store, try to complete result by given view
// instance, which search in it's store and in it's controller instance:
if ($this->view instanceof \MvcCore\IView)
return $this->view->__isset($name);
// return FALSE, if property is not in local store an even anywhere else
return FALSE;
}
/**
* Call public field method if exists in field instance and view is used for
* field rendering or call public form method if exists in form instance or
* try to call view helper by parent `__call()` method.
* @param string $method
* @param mixed $arguments
* @return mixed
*/
public function __call ($method, $arguments) {
$field = $this->field;
$form = $this->form;
if (
$this->__protected['fieldRendering'] &&
$field instanceof \MvcCore\Ext\Forms\IField &&
method_exists($field, $method)
) {
return call_user_func_array([$field, $method], $arguments);
} else if (
$form instanceof \MvcCore\Ext\IForm &&
method_exists($form, $method)
) {
return call_user_func_array([$form, $method], $arguments);
} else {
return parent::__call($method, $arguments);
}
}
/**
* Render configured form template.
* @return string
*/
public function RenderTemplate () {
$formViewScript = $this->form->GetViewScript();
return $this->Render(
static::$formsDir,
is_bool($formViewScript) ? $this->form->GetId() : $formViewScript
);
}
/**
* Render form naturally by cycles inside php scripts.
* All form fields will be rendered inside empty <div> elements.
* @return string
*/
public function RenderNaturally () {
return $this->RenderBegin()
. $this->RenderErrors()
. $this->RenderContent()
. $this->RenderEnd();
}
/**
* Render form begin.
* Render opening <form> tag and hidden input with csrf tokens.
* @return string
*/
public function RenderBegin () {
$this->form->PreDispatch();
$result = "<form";
$attrs = [];
$form = $this->form;
// standard attributes
$formProperties = ['id', 'action', 'method', 'enctype', 'target', 'title'];
foreach ($formProperties as $property) {
$getter = 'Get'.ucfirst($property);
$formPropertyValue = $form->$getter();
if ($formPropertyValue !== NULL)
$attrs[$property] = $formPropertyValue;
}
// css classes
$formCssClasses = $form->GetCssClasses();
if ($formCssClasses)
$attrs['class'] = gettype($formCssClasses) == 'array'
? implode(' ', (array) $formCssClasses)
: $formCssClasses;
// additional custom attributes completing
foreach ($form->GetAttributes() as $key => $value) {
if (!in_array($key, $formProperties))
$attrs[$key] = $value;
}
// pseudo-boolean attributes completing
$formAutoComplete = $form->GetAutoComplete();
if ($formAutoComplete !== NULL)
$attrs['autocomplete'] = $formAutoComplete ? 'on' : 'off';
$formNoValidate = $form->GetNoValidate();
if ($formNoValidate === TRUE)
$attrs['novalidate'] = 'novalidate';
$formAcceptCharsets = $form->GetAcceptCharsets();
if (count($formAcceptCharsets) > 0)
$attrs['accept-charset'] = implode(' ', $formAcceptCharsets);
// boolean and additional attributes
$attrsStr = self::RenderAttrs($attrs);
if ($attrsStr) $result .= ' ' . $attrsStr;
$result .= '>' . $this->RenderCsrf();
return $result;
}
/**
* Render hidden input with CSRF tokens.
* This method is not necessary to call, it's
* called internally by `$form->View->RenderBegin();`
* @return string
*/
public function RenderCsrf () {
if (!$this->csrfEnabled) return '';
$csrf = $this->form->GetCsrf();
return '<input type="hidden" name="'.$csrf->name.'" value="'.$csrf->value.'" />';
}
/**
* Return current CSRF (Cross Site Request Forgery) hidden
* input name and it's value as `\stdClass`.
* Result `\stdClass` has keys: `name` and `value`.
* @return \stdClass
*/
public function GetCsrf () {
return $this->form->GetCsrf();
}
/**
* Render form errors.
* If form is configured to render all errors together at form beginning,
* this function completes all form errors into `div.errors` with `div.error` elements
* inside containing each single errors message.
* @return string
*/
public function RenderErrors () {
$this->form->PreDispatch();
$result = '';
$errors = $this->form->GetErrors();
if ($errors && $this->form->GetErrorsRenderMode() == \MvcCore\Ext\IForm::ERROR_RENDER_MODE_ALL_TOGETHER) {
$result .= '<div class="errors">';
foreach ($errors as $errorMessageAndFieldNames) {
list($errorMessage, $fieldNames) = $errorMessageAndFieldNames;
$result .= '<div class="error ' . implode(' ', $fieldNames) . '">'.$errorMessage.'</div>';
}
$result .= '</div>';
}
return $result;
}
/**
* Render form content - form fields.
* Go through all `$form->fields` and call `$field->Render();` on every field
* and put it into an empty `<div>` element. Render each field in full possible
* way - naturally by label configuration with possible errors configured beside
* or with custom field template.
* @return string
*/
public function RenderContent () {
$this->form->PreDispatch();
$result = "";
$fieldRendered = "";
foreach ($this->form->GetFields() as $field) {
$fieldRendered = $field->Render();
if (!($field instanceof \MvcCore\Ext\Forms\Fields\Hidden)) {
$fieldRendered = "<div>".$fieldRendered."</div>";
}
$result .= $fieldRendered;
}
return $result;
}
/**
* Render form end.
* Render html closing `</form>` tag and supporting javascript and css files
* if is form not using external js/css renderers.
* @return string
*/
public function RenderEnd () {
return $this->form->RenderEnd();
}
/**
* Format string function.
* @param string $str Template with replacements like `{0}`, `{1}`, `{anyStringKey}`...
* @param array $args Each value under it's index is replaced as
* string representation by replacement in form `{arrayKey}`
* @return string
*/
public static function Format ($str = '', array $args = []) {
foreach ($args as $key => $value) {
$pos = strpos($str, '{'.$key.'}');
if ($pos !== FALSE)
$str = substr($str, 0, $pos) . $value . substr($str, $pos + strlen($key) + 2);
}
return $str;
}
/**
* Render content of html tag attributes by key/value array.
* @param array $attributes
* @return string
*/
public static function RenderAttrs (array $attributes = []) {
$result = [];
foreach ($attributes as $attrName => $attrValue) {
//if (gettype($attrValue) == 'array') $stop();
$result[] = $attrName.'="'.$attrValue.'"';
}
return implode(' ', $result);
}
}