Completed
Push — master ( f2cd13...82e8ab )
by Mihail
03:03
created

Model::rules()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
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\Arch;
4
5
use Ffcms\Core\App;
6
use Ffcms\Core\Exception\SyntaxException;
7
use Dflydev\DotAccessData\Data as DotData;
8
use Ffcms\Core\Helper\Type\Obj;
9
use Ffcms\Core\Helper\Type\Str;
10
use Ffcms\Core\Traits\DynamicGlobal;
11
use Ffcms\Core\Traits\ModelValidator;
12
13
/**
14
 * Class Model. Classic constructor of models in MVC architecture with algorithm of passing attributes from user input data.
15
 * @package Ffcms\Core\Arch
16
 */
17
class Model
18
{
19
    use DynamicGlobal, ModelValidator {
20
        ModelValidator::initialize as private validatorConstructor;
21
    }
22
23
    public $_csrf_token;
24
25
    /**
26
     * Model constructor. Initialize before() method for extended objects and run validator initialization
27
     * @param bool $csrf
28
     */
29
    public function __construct($csrf = false)
30
    {
31
        $this->before();
32
        $this->validatorConstructor($csrf);
33
    }
34
35
    /**
36
     * Make any things before model is initialized
37
     */
38
    public function before() {}
39
40
    /**
41
     * Get label value by variable name
42
     * @param string $param
43
     * @return mixed
44
     */
45
    final public function getLabel($param)
46
    {
47
        $labels = $this->labels();
48
        $response = null;
0 ignored issues
show
Unused Code introduced by
$response is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
49
        // maybe array-dotted declaration?
50
        if (Str::contains('.', $param)) {
51
            // not defined for all array-dotted nesting?
52
            if (Str::likeEmpty($labels[$param])) {
53
                // lets set default array label (before first dot-separation)
54
                $response = $labels[Str::firstIn($param, '.')];
55
            } else {
56
                $response = $labels[$param];
57
            }
58
        } else {
59
            $response = $labels[$param];
60
        }
61
62
        return (Str::likeEmpty($response) ? Str::replace('.', ' ', Str::splitCamelCase($param)) : $response);
63
    }
64
65
    /**
66
     * Set attribute labels for model variables
67
     * @return array
68
     */
69
    public function labels()
70
    {
71
        return [];
72
    }
73
74
    /**
75
     * Set of model validation rules
76
     * @return array
77
     */
78
    public function rules()
79
    {
80
        return [];
81
    }
82
83
    /**
84
     * Set model data sources for input data
85
     * @return array
86
     */
87
    public function sources()
88
    {
89
        return [];
90
    }
91
92
    /**
93
     * Validate defined rules in app
94
     * @return bool
95
     * @throws SyntaxException
96
     */
97
    final public function validate()
98
    {
99
        // validate csrf token if required
100
        if ($this->_tokenRequired && !$this->_tokenOk) {
101
            App::$Session->getFlashBag()->add('warning', __('Hack attention: security token is wrong!'));
102
            return false;
103
        }
104
        // get all rules as array from method rules()
105
        $rules = $this->rules();
106
        // get default values of attributes
107
        $defaultAttr = $this->getAllProperties();
108
109
        // start validation: on this step class attribute values will be changed to input data if it valid
110
        $success = $this->runValidate($rules);
111
112
        // get not-passed validation fields as array
113
        $badAttributes = $this->getBadAttribute();
114
        // prevent warnings
115
        if (Obj::isArray($badAttributes) && count($badAttributes) > 0) {
116
            foreach ($badAttributes as $attr) {
0 ignored issues
show
Bug introduced by
The expression $badAttributes 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...
117
                if (Str::contains('.', $attr)) { // sounds like dot-separated array attr
118
                    $attrName = strstr($attr, '.', true); // get attr name
119
                    $attrArray = trim(strstr($attr, '.'), '.'); // get dot-based array path
120
121
                    $defaultValue = new DotData($defaultAttr); // load default attr
122
123
                    $dotData = new DotData($this->{$attrName}); // load local attr variable
124
                    $dotData->set($attrArray, $defaultValue->get($attr)); // set to local prop. variable default value
125
126
                    $this->{$attrName} = $dotData->export(); // export to model
127
                } else {
128
                    $this->{$attr} = $defaultAttr[$attr]; // just set ;)
129
                }
130
                // add message about wrong attribute to session holder, later display it
131
                $attrLabel = $attr;
132
                if ($this->getLabel($attr) !== null) {
133
                    $attrLabel = $this->getLabel($attr);
134
                }
135
                App::$Session->getFlashBag()->add('warning', __('Field "%field%" is incorrect', ['field' => $attrLabel]));
136
            }
137
        }
138
139
        return $success;
140
    }
141
142
    /**
143
     * Filter model fields as text, html or secure obscured
144
     * @param array|null $fields
145
     * @return $this
146
     */
147
    public function filter(array $fields = null)
148
    {
149
        // list all model fields
150
        $allFields = $this->getAllProperties();
151
        if ($allFields !== null && Obj::isArray($allFields)) {
152
            foreach ($allFields as $f => $v) {
153
                // if attr is not passed - set from global as plaintext
154
                if (!isset($fields[$f])) {
155
                    $fields[$f] = 'text';
156
                }
157
            }
158
        }
159
160
        // if no fields is found - return
161
        if (!Obj::isArray($fields)) {
162
            return $this;
163
        }
164
165
        // each all properties
166
        foreach ($fields as $field => $security) {
0 ignored issues
show
Bug introduced by
The expression $fields 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...
167
            // get property value
168
            $fieldValue = $this->{$field};
169
            // switch security levels
170
            switch ($security) {
171
                case '!secure': // is full security obscured field, skip it
172
                    break;
173
                case 'html':
174
                    $this->{$field} = App::$Security->secureHtml($fieldValue);
175
                    break;
176
                default: // text/plaintext
177
                    $this->{$field} = App::$Security->strip_tags($fieldValue);
178
                    break;
179
            }
180
        }
181
182
        return $this;
183
    }
184
185
    /**
186
     * Export model values for safe-using in HTML pages.
187
     * @deprecated
188
     * @return $this
189
     */
190
    final public function export()
191
    {
192
        return $this->filter(null);
193
    }
194
195
196
    /**
197
     * Get all properties for current model in key=>value array
198
     * @return array|null
199
     */
200
    public function getAllProperties()
201
    {
202
        $properties = null;
203
        // list all properties here, array_walk sucks on performance!
204
        foreach ($this as $property => $value) {
0 ignored issues
show
Bug introduced by
The expression $this of type this<Ffcms\Core\Arch\Model> is not traversable.
Loading history...
205
            if (Str::startsWith('_', $property)) {
206
                continue;
207
            }
208
            $properties[$property] = $value;
209
        }
210
        return $properties;
211
    }
212
213
    /**
214
     * Cleanup all public model properties
215
     */
216
    public function clearProperties()
217
    {
218
        foreach ($this as $property => $value) {
0 ignored issues
show
Bug introduced by
The expression $this of type this<Ffcms\Core\Arch\Model> is not traversable.
Loading history...
219
            if (!Str::startsWith('_', $property)) {
220
                $this->{$property} = null;
221
            }
222
        }
223
    }
224
225
    /**
226
     * Get validation rules for field
227
     * @param string $field
228
     * @return array
229
     */
230
    final public function getValidationRule($field)
231
    {
232
        $rules = $this->rules();
233
        $response = [];
234
235
        foreach ($rules as $rule) {
236
            if (Obj::isArray($rule[0])) { // 2 or more rules [['field1', 'field2'], 'filter', 'filter_argv']
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% 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...
237
                foreach ($rule[0] as $tfield) {
238
                    if ($tfield == $field) {
239
                        $response[$rule[1]] = $rule[2]; // ['min_length' => 1, 'required' => null]
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% 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...
240
                    }
241
                }
242
            } else { // 1 rule ['field1', 'filter', 'filter_argv']
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
243
                if ($rule[0] === $field) {
244
                    $response[$rule[1]] = $rule[2];
245
                }
246
            }
247
        }
248
249
        return $response;
250
    }
251
}