Completed
Push — master ( ffc510...3e9600 )
by Mihail
14:10
created

ModelValidator::getFile()   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 1
1
<?php
2
3
namespace Ffcms\Core\Traits;
4
5
6
use Ffcms\Core\App;
7
use Ffcms\Core\Exception\SyntaxException;
8
use Ffcms\Core\Filter\Native;
9
use Ffcms\Core\Helper\Type\Arr;
10
use Ffcms\Core\Helper\Type\Obj;
11
use Ffcms\Core\Helper\Type\Str;
12
use Dflydev\DotAccessData\Data as DotData;
13
14
trait ModelValidator
15
{
16
    protected $_badAttr;
17
    protected $_sendMethod = 'POST';
18
19
    protected $_formName;
20
21
    public function runValidate(array $rules = null)
22
    {
23
        // skip validation on empty rules
24
        if ($rules === null) {
25
            return true;
26
        }
27
28
        $success = true;
29
        // each
30
        foreach ($rules as $rule) {
31
            // 0 = name, 1 = filter name, 2 = filter value
32
            if ($rule[0] === null || $rule[1] === null) {
33
                continue;
34
            }
35
36
            if (Obj::isArray($rule[0])) {
37
                $validate_foreach = true;
38
                foreach ($rule[0] as $field_name) {
39
                    // end false condition
40
                    if (!$this->validateRecursive($field_name, $rule[1], $rule[2], $rule[3], $rule[4])) {
41
                        $validate_foreach = false;
42
                    }
43
                }
44
                // assign total
45
                $validate = $validate_foreach;
46
            } else {
47
                $validate = $this->validateRecursive($rule[0], $rule[1], $rule[2], $rule[3], $rule[4]);
48
            }
49
50
            // do not change condition on "true" check's (end-false-condition)
51
            if ($validate === false) {
52
                $success = false;
53
            }
54
        }
55
56
        return $success;
57
    }
58
59
    /**
60
     * @param $field_name
61
     * @param $filter_name
62
     * @param $filter_argv
63
     * @param bool $html
64
     * @param bool $secure
65
     * @return bool
66
     * @throws SyntaxException
67
     */
68
    public function validateRecursive($field_name, $filter_name, $filter_argv, $html = false, $secure = false)
69
    {
70
        // check if we got it from form defined request method
71
        if (App::$Request->getMethod() !== $this->_sendMethod) {
72
            return false;
73
        }
74
75
        // get field value from user input data
76
        $field_value = $this->getFieldValue($field_name, $html, $secure);
77
78
        $check = false;
0 ignored issues
show
Unused Code introduced by
$check 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...
79
        // maybe no filter required?
80
        if ($filter_name === 'used') {
81
            $check = true;
82
        } elseif (Str::contains('::', $filter_name)) { // sounds like a callback class::method::method
83
            // string to array via delimiter ::
84
            $callbackArray = explode('::', $filter_name);
85
            // first item is a class name
86
            $class = array_shift($callbackArray);
87
            // last item its a function
88
            $method = array_pop($callbackArray);
89
            // left any items? maybe post-static callbacks?
90
            if (count($callbackArray) > 0) {
91
                foreach ($callbackArray as $obj) {
92
                    if (Str::startsWith('$', $obj) && property_exists($class, ltrim($obj, '$'))) { // sounds like a variable
93
                        $obj = ltrim($obj, '$'); // trim variable symbol '$'
94
                        $class = $class::$$obj; // make magic :)
95
                    } elseif (method_exists($class, $obj)) { // maybe its a function?
96
                        $class = $class::$obj; // call function
97
                    } else {
98
                        throw new SyntaxException('Filter callback execution failed: ' . $filter_name);
99
                    }
100
101
                }
102
            }
103
104
            // check is endpoint method exist
105
            if (method_exists($class, $method)) {
106
                $check = @$class::$method($field_value, $filter_argv);
107
            } else {
108
                throw new SyntaxException('Filter callback execution failed: ' . $filter_name);
109
            }
110
        } elseif (method_exists('Ffcms\Core\Filter\Native', $filter_name)) { // only full namespace\class path based :(
111
            if ($filter_argv != null) {
112
                $check = Native::$filter_name($field_value, $filter_argv);
113
            } else {
114
                $check = Native::$filter_name($field_value);
115
            }
116
        } else {
117
            throw new SyntaxException('Filter "' . $filter_name . '" is not exist');
118
        }
119
        if ($check !== true) { // switch only on fail check.
120
            $this->_badAttr[] = $field_name;
121
        } else {
122
            $field_set_name = $field_name;
123
            // prevent array-type setting
124
            if (Str::contains('.', $field_set_name)) {
125
                $field_set_name = strstr($field_set_name, '.', true);
126
            }
127
            if (property_exists($this, $field_set_name)) {
128
                if ($field_name !== $field_set_name) { // array-based property
129
                    $dot_path = trim(strstr($field_name, '.'), '.');
130
                    // prevent throws any exceptions for null and false objects
131
                    if (!Obj::isArray($this->{$field_set_name})) {
132
                        $this->{$field_set_name} = [];
133
                    }
134
                    // use dot-data provider to compile output array
135
                    $dotData = new DotData($this->{$field_set_name});
136
                    $dotData->set($dot_path, $field_value); // todo: check me!!! bug here
137
                    // export data from dot-data lib to model property
138
                    $this->{$field_set_name} = $dotData->export();
139
                } else { // just single property
140
                    $this->{$field_name} = $field_value; // refresh model property's from post data
141
                }
142
            }
143
        }
144
        return $check;
145
    }
146
147
    /**
148
     * @param string $field_name
149
     * @param bool $html
150
     * @param bool $secure
151
     * @return array|null|string
152
     */
153
    private function getFieldValue($field_name, $html = false, $secure = false)
154
    {
155
        // get type of input data (where we must look it up)
156
        $sources = [];
157
        $inputType = Str::lowerCase($this->_sendMethod);
158
        // check input data type. Maybe file or input (text)
159
        if (method_exists($this, 'sources')) {
160
            $sources = $this->sources();
0 ignored issues
show
Bug introduced by
It seems like sources() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
161
        }
162
163
        if (isset($sources[$field_name])) {
164
            $inputType = Str::lowerCase($sources[$field_name]);
165
        }
166
167
        $field_value = $this->getRequest($field_name, $inputType);
168
        // apply html security filter for input data
169
        if ($inputType !== 'file') {
170
            if ($html !== true) {
171
                $field_value = App::$Security->strip_tags($field_value);
0 ignored issues
show
Bug introduced by
It seems like $field_value can also be of type null; however, Ffcms\Core\Helper\Security::strip_tags() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
172
            } elseif($secure !== true) {
173
                $field_value = App::$Security->secureHtml($field_value);
0 ignored issues
show
Bug introduced by
It seems like $field_value defined by \Ffcms\Core\App::$Securi...ecureHtml($field_value) on line 173 can also be of type null; however, Ffcms\Core\Helper\Security::secureHtml() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
174
            }
175
        }
176
177
        return $field_value;
178
    }
179
180
    /**
181
     * Get fail validation attributes as array if exist
182
     * @return null|array
183
     */
184
    public function getBadAttribute()
185
    {
186
        return $this->_badAttr;
187
    }
188
189
    /**
190
     * Set model send method type. Allowed: post, get
191
     * @param string $acceptMethod
192
     */
193
    final public function setSubmitMethod($acceptMethod)
194
    {
195
        $this->_sendMethod = Str::upperCase($acceptMethod);
196
    }
197
198
    /**
199
     * Get model submit method. Allowed: post, get
200
     * @return string
201
     */
202
    final public function getSubmitMethod()
203
    {
204
        return $this->_sendMethod;
205
    }
206
207
    /**
208
     * Check if model get POST-based request as submit of SEND data
209
     * @return bool
210
     */
211
    final public function send()
212
    {
213
        if (App::$Request->getMethod() !== $this->_sendMethod) {
214
            return false;
215
        }
216
217
        return $this->getRequest('submit', $this->_sendMethod) !== null;
218
    }
219
220
    /**
221
     * Form default name (used in field building)
222
     * @return string
223
     */
224
    public function getFormName()
225
    {
226
        if ($this->_formName === null) {
227
            $cname = get_class($this);
228
            $this->_formName = substr($cname, strrpos($cname, '\\') + 1);
229
        }
230
231
        return $this->_formName;
232
    }
233
234
    /**
235
     * @deprecated
236
     * Get input params GET/POST/PUT method
237
     * @param string $param
238
     * @return string|null
239
     */
240
    public function getInput($param)
241
    {
242
        return $this->getRequest($param, $this->_sendMethod);
243
    }
244
245
    /**
246
     * @deprecated
247
     * Get uploaded file from user via POST request
248
     * @param string $param
249
     * @return \Symfony\Component\HttpFoundation\File\UploadedFile|null
250
     */
251
    public function getFile($param)
252
    {
253
        return $this->getRequest($param, 'file');
254
    }
255
256
    /**
257
     * Get input param for current model form based on param name and request method
258
     * @param string $param
259
     * @param string $method
260
     * @return string|null|array
261
     */
262
    public function getRequest($param, $method = 'get')
263
    {
264
        // build param query for http foundation request
265
        $paramQuery = $this->getFormName();
266
        if (Str::contains('.', $param)) {
267
            foreach (explode('.', $param) as $item) {
268
                $paramQuery .= '[' . $item . ']';
269
            }
270
        } else {
271
            $paramQuery .= '[' . $param . ']';
272
        }
273
274
        // get request based on method and param query
275
        $method = Str::lowerCase($method);
276
        switch ($method) {
277
            case 'get':
278
                return App::$Request->query->get($paramQuery, null, true);
279
            case 'post':
280
                return App::$Request->request->get($paramQuery, null, true);
281
            case 'file':
282
                return App::$Request->files->get($paramQuery, null, true);
283
            default:
284
                return App::$Request->get($paramQuery, null, true);
285
286
        }
287
    }
288
}