1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Ffcms\Core\Helper\HTML; |
4
|
|
|
|
5
|
|
|
use Ffcms\Core\App; |
6
|
|
|
use Ffcms\Core\Exception\SyntaxException; |
7
|
|
|
use Ffcms\Core\Helper\HTML\System\NativeGenerator; |
8
|
|
|
use Ffcms\Core\Helper\Type\Arr; |
9
|
|
|
use Ffcms\Core\Helper\FileSystem\File; |
10
|
|
|
use Ffcms\Core\Helper\Type\Obj; |
11
|
|
|
use Ffcms\Core\Helper\Type\Str; |
12
|
|
|
use Ffcms\Core\Arch\Model; |
13
|
|
|
use Ffcms\Core\Helper\HTML\Form\Constructor; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Class Form. Simple HTML model generator for fast form building. |
17
|
|
|
*/ |
18
|
|
|
class Form extends NativeGenerator |
19
|
|
|
{ |
20
|
|
|
protected $structure = '<div class="form-group"><label for="%name%" class="col-md-3 control-label">%label%</label><div class="col-md-9">%item% <p class="help-block">%help%</p></div></div>'; |
21
|
|
|
protected $structureCheckbox = '<div class="form-group"><div class="col-md-9 col-md-offset-3"><div class="checkbox"><label>%item% %label%</label></div><p class="help-block">%help%</p></div></div>'; |
22
|
|
|
protected $structureCheckboxes = '<div class="checkbox"><label>%item%</label></div>'; |
23
|
|
|
protected $structureJSError = '$("#%itemId%").parent().parent(".form-group").addClass("has-error")'; |
24
|
|
|
protected $name; |
25
|
|
|
protected $formProperty = []; |
26
|
|
|
/** @var Model */ |
27
|
|
|
protected $model; |
28
|
|
|
|
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Build form based on model properties |
32
|
|
|
* @param Model $model |
33
|
|
|
* @param array $property |
34
|
|
|
* @param array $structure |
35
|
|
|
* @throws SyntaxException |
36
|
|
|
*/ |
37
|
|
|
public function __construct($model, array $property = null, array $structure = null) |
38
|
|
|
{ |
39
|
|
|
// prevent white-screen locks when model is not passed or passed wrong |
40
|
|
|
if (!$model instanceof Model) { |
41
|
|
|
throw new SyntaxException('Bad model type passed in form builder. Check for init: new Form()'); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
$this->model = $model; |
45
|
|
|
$this->name = $model->getFormName(); |
46
|
|
|
|
47
|
|
|
// set custom html build structure form fields |
48
|
|
|
if (Obj::isArray($structure)) { |
49
|
|
|
if (isset($structure['base']) && !Str::likeEmpty($structure['base'])) { |
50
|
|
|
$this->structure = $structure['base']; |
51
|
|
|
} |
52
|
|
|
if (isset($structure['checkbox']) && !Str::likeEmpty($structure['checkbox'])) { |
53
|
|
|
$this->structureCheckbox = $structure['checkbox']; |
54
|
|
|
} |
55
|
|
|
if (isset($structure['checkboxes']) && !Str::likeEmpty($structure['checkboxes'])) { |
56
|
|
|
$this->structureCheckboxes = $structure['checkboxes']; |
57
|
|
|
} |
58
|
|
|
if (isset($structure['jserror']) && !Str::likeEmpty($structure['jserror'])) { |
59
|
|
|
$this->structureJSError = $structure['jserror']; |
60
|
|
|
} |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
$property['method'] = $this->model->getSubmitMethod(); |
64
|
|
|
|
65
|
|
|
$property['id'] = $this->name; // define form id |
66
|
|
|
// if action is not defined - define it |
67
|
|
|
if (Str::likeEmpty($property['action'])) { |
68
|
|
|
$property['action'] = App::$Request->getFullUrl(); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
// set property global for this form |
72
|
|
|
$this->formProperty = $property; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Open form tag with prepared properties |
77
|
|
|
* @return string |
78
|
|
|
*/ |
79
|
|
|
public function start() |
80
|
|
|
{ |
81
|
|
|
return '<form' . self::applyProperty($this->formProperty) . '>'; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Display form field. Allowed type: text, password, textarea, checkbox, select, checkboxes, file, captcha, email, hidden |
86
|
|
|
* @param $object |
87
|
|
|
* @param $type |
88
|
|
|
* @param null|array $property |
89
|
|
|
* @param null|string $helper |
90
|
|
|
* @param null|string $structure |
91
|
|
|
* @return null|string |
92
|
|
|
*/ |
93
|
|
|
public function field($object, $type, $property = null, $helper = null, $structure = null) |
94
|
|
|
{ |
95
|
|
|
if ($this->model === null) { |
96
|
|
|
if (App::$Debug !== null) { |
97
|
|
|
App::$Debug->addMessage('Form model is not defined for field name: ' . strip_tags($object)); |
98
|
|
|
} |
99
|
|
|
return null; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
// can be dots separated object |
103
|
|
|
$propertyName = $object; |
104
|
|
|
if (Str::contains('.', $propertyName)) { |
105
|
|
|
$propertyName = strstr($propertyName, '.', true); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
// check if model contains current tag name as property |
109
|
|
|
if (!property_exists($this->model, $propertyName)) { |
110
|
|
|
if (App::$Debug !== null) { |
111
|
|
|
App::$Debug->addMessage('Form field "' . $object . '" is not defined in model: ' . get_class($this->model), 'error'); |
112
|
|
|
} |
113
|
|
|
return null; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
if ($structure === null) { |
117
|
|
|
if ($type === 'checkbox') { |
118
|
|
|
$structure = $this->structureCheckbox; |
119
|
|
|
} else { |
120
|
|
|
$structure = $this->structure; |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
// prepare labels text and label "for" attr |
125
|
|
|
$labelFor = $this->name . '-' . $propertyName; |
126
|
|
|
$labelText = $this->model->getLabel($object); |
127
|
|
|
$itemValue = $this->model->{$propertyName}; |
128
|
|
|
// sounds like a dot-separated $object |
129
|
|
|
if ($propertyName !== $object) { |
130
|
|
|
$nesting = trim(strstr($object, '.'), '.'); |
131
|
|
|
$labelFor .= '-' . Str::replace('.', '-', $nesting); |
132
|
|
|
$itemValue = Arr::getByPath($nesting, $itemValue); |
133
|
|
|
} |
134
|
|
|
//$itemBody = $this->dataTypeTag($type, $object, $itemValue, $property); |
|
|
|
|
135
|
|
|
$constructor = new Constructor($this->model, $this->name, $type); |
|
|
|
|
136
|
|
|
$elementDOM = $constructor->makeTag($object, $itemValue, $property); |
137
|
|
|
|
138
|
|
|
// if item is hidden - return tag without assign of global template |
139
|
|
|
if ($type === 'hidden') { |
140
|
|
|
return $elementDOM; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
return Str::replace( |
144
|
|
|
['%name%', '%label%', '%item%', '%help%'], |
145
|
|
|
[$labelFor, $labelText, $elementDOM, self::nohtml($helper)], |
146
|
|
|
$structure |
147
|
|
|
); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Display submit button for current form |
152
|
|
|
* @param string $title |
153
|
|
|
* @param array $property |
154
|
|
|
* @return string |
155
|
|
|
*/ |
156
|
|
|
public function submitButton($title, array $property = []) |
157
|
|
|
{ |
158
|
|
|
$property['type'] = 'submit'; |
159
|
|
|
$property['name'] = $this->name . '[submit]'; |
160
|
|
|
$property['value'] = $title; |
161
|
|
|
return self::buildSingleTag('input', $property); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Finish current form. |
166
|
|
|
* @param bool $validate |
167
|
|
|
* @return string |
168
|
|
|
*/ |
169
|
|
|
public function finish($validate = true) |
170
|
|
|
{ |
171
|
|
|
// pre-validate form fields based on model rules and jquery.validation |
172
|
|
|
if ($validate) { |
173
|
|
|
App::$Alias->addPlainCode('js', '$().ready(function() { $("#' . $this->name . '").validate(); });'); |
174
|
|
|
App::$Alias->setCustomLibrary('js', '/vendor/bower/jquery-validation/dist/jquery.validate.min.js'); |
175
|
|
|
if (App::$Request->getLanguage() !== 'en') { |
176
|
|
|
$localeFile = '/vendor/bower/jquery-validation/src/localization/messages_' . App::$Request->getLanguage() . '.js'; |
177
|
|
|
if (File::exist($localeFile)) { |
178
|
|
|
App::$Alias->setCustomLibrary('js', $localeFile); |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
// if model is not empty - add js error color notification |
182
|
|
|
if ($this->model !== null) { |
183
|
|
|
$badAttr = $this->model->getBadAttribute(); |
184
|
|
|
$formName = $this->model->getFormName(); |
185
|
|
|
if (Obj::isArray($badAttr) && count($badAttr) > 0) { |
186
|
|
|
$jsError = $this->structureJSError; |
187
|
|
|
foreach ($badAttr as $attr) { |
|
|
|
|
188
|
|
|
$itemId = $formName . '-' . $attr; |
189
|
|
|
App::$Alias->addPlainCode('js', Str::replace('%itemId%', $itemId, $jsError)); |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
return '</form>'; |
195
|
|
|
} |
196
|
|
|
} |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.