Completed
Push — master ( 9394c7...856cc5 )
by Mihail
06:05
created

Form::start()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
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);
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
135
        $constructor = new Constructor($this->model, $this->name, $type);
0 ignored issues
show
Documentation introduced by
$this->model is of type object<Ffcms\Core\Arch\Model>, but the function expects a object<Ffcms\Core\Helper...\Ffcms\Core\Arch\Model>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$this->name is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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) {
0 ignored issues
show
Bug introduced by
The expression $badAttr of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
188
                        $itemId = $formName . '-' . $attr;
189
                        App::$Alias->addPlainCode('js', Str::replace('%itemId%', $itemId, $jsError));
190
                    }
191
                }
192
            }
193
        }
194
        return '</form>';
195
    }
196
}