Issues (164)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Form/Component.php (12 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace BootPress\Form;
4
5
use BootPress\Page\Component as Page;
6
use BootPress\Validator\Component as Validator;
7
8
class Component
9
{
10
    /**
11
     * @var \BootPress\Validator\Component 
12
     */
13
    public $validator;
14
    
15
    /**
16
     * If a form is submitted successfully then you should ``$page->eject()`` them using this value.
17
     * 
18
     * @var string
19
     */
20
    public $eject = '';
21
    
22
    /**
23
     * You should set this ``array($field => $value)`` to all of the default values for the form you are going to create.  Once the form has been submitted and passed validation, then these will be all of your filtered and validated values.
24
     * 
25
     * @var string[]
26
     */
27
    public $values = array();
28
    
29
    /**
30
     * An array of attributes and their values that will be included in the opening ``<form>`` tag.
31
     * 
32
     * @var string[]
33
     */
34
    public $header = array();
35
    
36
    /**
37
     * Any additional html that you want to be included just before the ``</form>`` tag.
38
     * 
39
     * @var string[]
40
     */
41
    public $footer = array();
42
    
43
    /**
44
     * All of your hidden form inputs that we put after ``$this->footer``.
45
     * 
46
     * @var string[]
47
     */
48
    public $hidden = array();
49
    
50
    /**
51
     * Used by select menus to prepend a default value at the beginning eg. &nbsp;
52
     * 
53
     * @var array
54
     */
55
    protected $prepend = array();
56
    
57
    /**
58
     * Stores all of the values you submitted in ``$this->menu()`` for radio, checkbox, and select menus.
59
     * 
60
     * @var array
61
     */
62
    protected $menus = array();
63
    
64
    /**
65
     * @var \BootPress\Page\Component 
66
     */
67
    protected $page;
68
69
    /**
70
     * Creates a BootPress\Form\Component object.
71
     * 
72
     * @param string $name    The name of your form.
73
     * @param string $method  How you would like the form to be sent ie. '**post**' or '**get**'
74
     */
75 16
    public function __construct($name = 'form', $method = 'post')
76
    {
77 16
        $this->page = Page::html();
78 16
        $headers = (is_array($name)) ? $name : array('name' => $name, 'method' => $method);
79 16
        $this->header['name'] = (isset($headers['name'])) ? $headers['name'] : 'form';
80 16
        if (isset($headers['method']) && strtolower($headers['method']) == 'get') {
81 4
            $this->header['method'] = 'get';
82 4
            $this->header['action'] = (isset($headers['action'])) ? $headers['action'] : $this->page->url();
83 4
            $this->eject = $this->header['action'];
84 4
            $values = (strpos($this->header['action'], $this->page->url()) === 0) ? $this->page->request->query->all() : array();
85 4
        } else {
86 14
            $this->header['method'] = 'post';
87 14
            $this->header['action'] = $this->page->url('add', '', 'submitted', $name);
88 14
            $this->eject = $this->page->url('delete', $this->header['action'], 'submitted');
89 14
            $values = (strpos($this->header['action'], $this->page->url()) === 0) ? $this->page->request->request->all() : array();
90
        }
91 16
        $this->header['accept-charset'] = $this->page->charset;
92 16
        $this->header['autocomplete'] = 'off';
93 16
        $this->header = array_merge($this->header, $headers);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($this->header, $headers) of type array is incompatible with the declared type array<integer,string> of property $header.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
94 16
        $this->validator = new Validator($values);
95 16
    }
96
97
    /**
98
     * This establishes the options for a checkbox, radio, or select menu field.
99
     * 
100
     * @param string $field    The name of the field.
101
     * @param array  $menu     An ``array($value => $options), ...)`` of options to display in the menu.
102
     * @param string $prepend  An optional non-value to prepend to the menu eg. '&nbsp;'.  This is used for select menus when you would like a blank option up top.
103
     * 
104
     * @return string  A comma-separated list of values from your menu that is useful for using inList Validation.
105
     * 
106
     * ```php
107
     * $form->menu('save[]', array(
108
     *     4 => 'John Locke',
109
     *     8 => 'Hugo Reyes',
110
     *     15 => 'James Ford',
111
     *     16 => 'Sayid Jarrah',
112
     *     23 => 'Jack Shephard',
113
     *     42 => 'Jin & Sun Kwon'
114
     * )); // A multiselect menu
115
     * 
116
     * $form->menu('transport', array(1=>'Airplane', 2=>'Boat', 3=>'Submarine'), '&nbsp;'); // A select menu
117
     * 
118
     * $form->menu('vehicle', array(
119
     *     'hier' => 'transport',
120
     *     1 => array('Boeing'=>array(4=>'777', 5=>'737'), 'Lockheed'=>array(6=>'L-1011', 7=>'HC-130'), 8=>'Douglas DC-3', 9=>'Beechcraft'),
121
     *     2 => array(11=>'Black Rock', 12=>'Kahana', 13=>'Elizabeth', 14=>'Searcher'),
122
     *     3 => array(15=>'Galaga', '16'=>'Yushio')
123
     * ), '&nbsp;'); // A hierselect menu
124
     * 
125
     * $gender = $form->menu('gender', array('M'=>'Male', 'F'=>'Female')); // A radio menu
126
     * $form->validator->set('gender', "required|inList[{$gender}]");
127
     * 
128
     * $form->menu('remember', array('Y'=>'Remember Me')); // A checkbox
129
     * ```
130
     */
131 5
    public function menu($field, array $menu = array(), $prepend = null)
0 ignored issues
show
The parameter $field is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $menu is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $prepend is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
132
    {
133 5
        $args = func_get_args();
134 5
        $field = array_shift($args);
135 5
        if (empty($args)) {
136 5
            return (isset($this->menus[$field])) ? $this->menus[$field] : array();
137
        }
138 5
        $this->menus[$field] = array_shift($args);
139 5
        if (!empty($args)) {
140 2
            $this->prepend[$field] = array_shift($args);
141 2
        }
142
143 5
        return implode(',', array_keys($this->flatten($this->menus[$field])));
144
    }
145
146
    /**
147
     * This will begin the form with all of the attributes you have established in ``$this->headers`` array.  The values we have already set (but may be overridden) are:
148
     *
149
     * - '**name**' - The name of your form.
150
     * - '**method**' - Either 'get' or 'post'.
151
     * - '**action**' - If 'post' then the current page with a 'submitted' query parameter added.  If 'get' then the current page with all it's query parameters moved to hidden input fields.
152
     * - '**accept-charset**' - ``$this->page->charset``
153
     * - '**autocomplete**' - Set to 'off'.
154
     * 
155
     * If you add a numeric (megabytes) 'upload' field then then we convert the megabytes to bytes, add the enctype="multipart/form-data" to the header, and set a 'MAX_FILE_SIZE' hidden input with the number of bytes allowed.
156
     * 
157
     * @return string  The opening ``<form>`` tag.
158
     * 
159
     * ```php
160
     * echo $form->header();
161
     * ```
162
     */
163 4
    public function header()
164
    {
165 4
        if (isset($this->header['upload']) && is_numeric($this->header['upload'])) {
166 1
            $this->header['enctype'] = 'multipart/form-data';
167 1
            if ($this->header['upload'] <= 100) {
168 1
                $this->header['upload'] *= 1048576; // megabytes to bytes
169 1
            }
170 1
            $this->hidden['MAX_FILE_SIZE'] = $this->header['upload'];
171 1
            unset($this->header['upload']);
172 1
        }
173 4
        if ($this->header['method'] == 'get') {
174 3
            $params = $this->page->url('params', $this->header['action']);
175 3
            foreach ($params as $key => $value) {
0 ignored issues
show
The expression $params of type string|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...
176 1
                $this->hidden[$key] = $value;
177 3
            }
178 3
            $this->header['action'] = $this->page->url('delete', $this->header['action'], '?');
179 3
        }
180
181 4
        return "\n".$this->page->tag('form', $this->header);
182
    }
183
184
    /**
185
     * This will wrap a ``<fieldset>`` around the included $html, and place a nice ``<legend>`` up top.  This is not very difficult to do by hand, but it does look nice with all of the $html ``$form->field()``'s nicely indented and looking like they belong where they are.
186
     * 
187
     * @param string $legend  The fieldset's legend value.
188
     * @param string $html    The html you would like this fieldset to enclose (if any).  These args can go on forever, and they are all included as additional $html (strings) to place in the fieldset just after the legend.  If this is an array then we ``implode('', $html)`` and include that.
189
     * 
190
     * @return string
191
     * 
192
     * ```php
193
     * echo $form->fieldset('Sign In',
194
     *     $form->field('text', 'username'),
195
     *     $form->field('password', 'password')
196
     * );
197
     * ```
198
     */
199 1
    public function fieldset($legend, $html = '')
0 ignored issues
show
The parameter $legend is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $html is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
200
    {
201 1
        $args = func_get_args();
202 1
        $legend = array_shift($args);
203 1
        $html = array_shift($args);
204 1
        if (is_array($html)) {
205 1
            $html = implode('', $html);
206 1
        }
207 1
        if (!empty($args)) {
208 1
            $html .= implode('', $args);
209 1
        }
210
211 1
        return "\n<fieldset><legend>{$legend}</legend>{$html}\n</fieldset>";
212
    }
213
214
    /**
215
     * Retrieves an input's default value to display using the Validator::value method.  This is used internally when creating form fields using this class.
216
     * 
217
     * @param string      $field   The input's name.
218
     * @param false|mixed $escape  If set to anything but false, then we run the value(s) through ``htmlspecialchars``.
219
     * 
220
     * @return array|string  The field's default value.
221
     */
222 9
    public function defaultValue($field, $escape = false)
223
    {
224 9
        if (null === $value = $this->validator->value($field)) {
225 9
            $value = (isset($this->values[$field])) ? $this->values[$field] : '';
226 9
        }
227 9
        if ($escape === false) {
228 5
            return $value;
229
        }
230
231 6
        return (is_array($value)) ? array_map('htmlspecialchars', $value) : htmlspecialchars($value);
232
    }
233
234
    /**
235
     * This adds the jQuery Validation rules and messages set earlier to the input field's submitted attributes.  This is used internally when creating form fields using this class.
236
     * 
237
     * @param string $field       The input's name.
238
     * @param array  $attributes  The currently constituted attributes.
239
     * 
240
     * @return array  The submitted attributes with the data rules and messages applied.
241
     * 
242
     * @see http://johnnycode.com/2014/03/27/using-jquery-validate-plugin-html5-data-attribute-rules/
243
     * 
244
     * ```php
245
     * $form->validator->set('field', array('required' => 'Do this or else.'));
246
     * $attributes = $form->validate('field', array('name' => 'field'));
247
     * ```
248
     */
249 10
    public function validate($field, array $attributes = array())
250
    {
251 10
        foreach ($this->validator->rules($field) as $validate => $param) {
252 9
            $attributes["data-rule-{$validate}"] = htmlspecialchars($param);
253 10
        }
254 10
        foreach ($this->validator->messages($field) as $rule => $message) {
255 4
            $attributes["data-msg-{$rule}"] = htmlspecialchars($message);
256 10
        }
257
258 10
        return $attributes;
259
    }
260
261
    /**
262
     * Creates an input field from an array of attributes.  This is used internally when creating form fields using this class.
263
     * 
264
     * @param string   $type        The type of input.
265
     * @param string[] $attributes  The input's other attributes.
266
     * 
267
     * @return string  An html input tag.
268
     * 
269
     * ```php
270
     * $form->footer[] = $form->input('submit', array('name' => 'Submit'));
271
     * 
272
     * echo $form->input('hidden', array('name' => 'field', 'value' => 'default'));
273
     * ```
274
     */
275 9
    public function input($type, array $attributes)
276
    {
277 9
        unset($attributes['type']);
278
279 9
        return $this->page->tag('input type="'.$type.'"', $attributes);
280
    }
281
282
    /**
283
     * Creates a text input field.
284
     * 
285
     * @param string   $field       The text input's name.
286
     * @param string[] $attributes  Anything else you would like to add besides the 'name', 'id', 'value', and data validation attributes.
287
     * 
288
     * @return string  An ``<input type="text" ...>`` html tag.
289
     * 
290
     * ```php
291
     * $form->validator->set('name', 'required');
292
     * $form->validator->set('email', 'required|email');
293
     * 
294
     * echo $form->field('name');
295
     * echo $form->field('email');
296
     * ```
297
     */
298 4
    public function text($field, array $attributes = array())
299
    {
300 4
        $attributes['name'] = $field;
301 4
        $attributes['id'] = $this->validator->id($field);
302 4
        $attributes['value'] = $this->defaultValue($field, 'escape');
303
304 4
        return $this->input('text', $this->validate($field, $attributes));
305
    }
306
307
    /**
308
     * Creates a password input field.
309
     * 
310
     * @param string   $field       The password input's name.
311
     * @param string[] $attributes  Anything else you would like to add besides the 'name', 'id', 'value', and data validation attributes.
312
     * 
313
     * @return string  An ``<input type="password" ...>`` html tag.
314
     * 
315
     * ```php
316
     * $form->validator->set('password', 'required|alphaNumeric|minLength[5]|noWhiteSpace');
317
     * $form->validator->set('confirm', 'required|matches[password]');
318
     * 
319
     * echo $form->field('password');
320
     * echo $form->field('confirm');
321
     * ```
322
     */
323 1
    public function password($field, array $attributes = array())
324
    {
325 1
        $attributes['name'] = $field;
326 1
        $attributes['id'] = $this->validator->id($field);
327 1
        unset($attributes['value']);
328
329 1
        return $this->input('password', $this->validate($field, $attributes));
330
    }
331
332
    /**
333
     * Creates checkboxes from the ``$form->menu($field)`` you set earlier.
334
     * 
335
     * @param string   $field       The checkbox's name.
336
     * @param string[] $attributes  Anything else you would like to add besides the 'name', 'value', 'checked', and data validation attributes.
337
     * @param string   $wrap        The html that surrounds each checkbox.
338
     * 
339
     * @return string  A checkbox ``<label><input type="checkbox" ...></label>`` html tag.
340
     * 
341
     * ```php
342
     * $form->menu('remember', array('Y'=>'Remember Me'));
343
     * 
344
     * echo $form->checkbox('remember');
345
     * ```
346
     */
347 2
    public function checkbox($field, array $attributes = array(), $wrap = '<label>%s</label>')
348
    {
349 2
        $boxes = array();
350 2
        $checked = (array) $this->defaultValue($field);
351 2
        foreach ($this->menu($field) as $value => $description) {
0 ignored issues
show
The expression $this->menu($field) of type string is not traversable.
Loading history...
352 2
            $attributes['name'] = $field;
353 2
            $attributes['value'] = $value;
354 2
            unset($attributes['checked']);
355 2
            if (in_array($value, $checked)) {
356 2
                $attributes['checked'] = 'checked';
357 2
            }
358 2
            if (empty($boxes)) {
359 2
                $boxes[] = $this->input('checkbox', $this->validate($field, $attributes)).' '.$description;
360 2
            } else {
361 2
                $boxes[] = $this->input('checkbox', $attributes).' '.$description;
362
            }
363 2
        }
364 2
        if (is_array($wrap)) {
365 1
            return $boxes;
366
        }
367 2
        if (!empty($wrap)) {
368 2
            foreach ($boxes as $key => $value) {
369 2
                $boxes[$key] = sprintf($wrap, $value);
370 2
            }
371 2
        }
372
373 2
        return implode(' ', $boxes);
374
    }
375
376
    /**
377
     * Creates radio buttons from the ``$form->menu($field)`` you set earlier.
378
     * 
379
     * @param string   $field       The radio button's name.
380
     * @param string[] $attributes  Anything else you would like to add besides the 'name', 'value', 'checked', and data validation attributes.
381
     * @param string   $wrap        The html that surrounds each radio button.
382
     * 
383
     * @return string  Radio ``<label><input type="radio" ...></label>`` html tags.
384
     * 
385
     * ```php
386
     * $gender = $form->menu('gender', array('M'=>'Male', 'F'=>'Female')); // A radio menu
387
     * $form->validator->set('gender', "required|inList[{$gender}]");
388
     * 
389
     * echo $form->radio('gender');
390
     * ```
391
     */
392 2
    public function radio($field, array $attributes = array(), $wrap = '<label>%s</label>')
393
    {
394 2
        $radios = array();
395 2
        $checked = (array) $this->defaultValue($field);
396 2
        foreach ($this->menu($field) as $value => $description) {
0 ignored issues
show
The expression $this->menu($field) of type string is not traversable.
Loading history...
397 2
            $attributes['name'] = $field;
398 2
            $attributes['value'] = $value;
399 2
            unset($attributes['checked']);
400 2
            if (in_array($value, $checked)) {
401 2
                $attributes['checked'] = 'checked';
402 2
            }
403 2
            if (empty($radios)) {
404 2
                $radios[] = $this->input('radio', $this->validate($field, $attributes)).' '.$description;
405 2
            } else {
406 2
                $radios[] = $this->input('radio', $attributes).' '.$description;
407
            }
408 2
        }
409 2
        if (is_array($wrap)) {
410 1
            return $radios;
411
        }
412 2
        if (!empty($wrap)) {
413 2
            foreach ($radios as $key => $value) {
414 2
                $radios[$key] = sprintf($wrap, $value);
415 2
            }
416 2
        }
417
418 2
        return implode(' ', $radios);
419
    }
420
421
    /**
422
     * Creates a select menu from the ``$form->menu($field)`` you set earlier.
423
     * 
424
     * If the $field is an array (identified by '[]' at the end), then this will be a multiple select menu unless you set ``$attributes['multiple'] = false``.  You can optionally include a 'size' attribute to override our sensible defaults.
425
     *
426
     * You can get fairly fancy with these creating optgroups and hier menus.  We'll let the examples speak for themselves.
427
     * 
428
     * @param string   $field       The select menu's name.
429
     * @param string[] $attributes  Anything else you would like to add besides the 'name', 'id', and data validation attributes.
430
     * 
431
     * @return string  A ``<select ...>`` tag with all it's ``<option>``'s.
432
     * 
433
     * ```php
434
     * $save = $form->menu('save[]', array(
435
     *     4 => 'John Locke',
436
     *     8 => 'Hugo Reyes',
437
     *     15 => 'James Ford',
438
     *     16 => 'Sayid Jarrah',
439
     *     23 => 'Jack Shephard',
440
     *     42 => 'Jin & Sun Kwon'
441
     * )); // A multiselect menu
442
     * 
443
     * $form->menu('transport', array(1=>'Airplane', 2=>'Boat', 3=>'Submarine'), '&nbsp;'); // A select menu
444
     * 
445
     * $vehicles = $form->menu('vehicle', array(
446
     *     'hier' => 'transport',
447
     *     1 => array('Boeing'=>array(4=>'777', 5=>'737'), 'Lockheed'=>array(6=>'L-1011', 7=>'HC-130'), 8=>'Douglas DC-3', 9=>'Beechcraft'),
448
     *     2 => array(11=>'Black Rock', 12=>'Kahana', 13=>'Elizabeth', 14=>'Searcher'),
449
     *     3 => array(15=>'Galaga', '16'=>'Yushio')
450
     * ), '&nbsp;'); // A hierselect menu
451
     * 
452
     * $form->validator->set(array(
453
     *     'save' => 'required|inList[{$save}]|minLength[2]',
454
     *     'vehicle' => "required|inList[{$vehicles}]",
455
     * ));
456
     * 
457
     * echo $form->fieldset('LOST',
458
     *     $form->select('save[]'),
459
     *     $form->select('transport'),
460
     *     $form->select('vehicle')
461
     * );
462
     * ```
463
     */
464 2
    public function select($field, array $attributes = array())
465
    {
466 2
        $select = $this->menu($field);
467 2
        $attributes['name'] = $field;
468 2
        $attributes['id'] = $this->validator->id($field);
469 2
        if (strpos($field, '[]') !== false) {
470 1
            if (isset($attributes['multiple']) && $attributes['multiple'] === false) {
471 1
                unset($attributes['multiple'], $attributes['size']);
472 1
            } else {
473 1
                $attributes['multiple'] = 'multiple';
474 1
                $max = (isset($attributes['size'])) ? $attributes['size'] : 15;
475 1
                $attributes['size'] = min(count($this->flatten($select)), $max);
0 ignored issues
show
$select is of type string, but the function expects a array.

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...
476
            }
477 1
        }
478 2
        if (isset($select['hier']) && !isset($attributes['multiple'])) {
479 1
            $hier = $select['hier'];
480 1
            $selected = $this->defaultValue($select['hier']);
481 1
            unset($select['hier']);
482 1
            $json = $select;
483 1
            if (isset($this->prepend[$field])) {
484 1
                foreach ($json as $key => $value) {
0 ignored issues
show
The expression $json of type string is not traversable.
Loading history...
485 1
                    array_unshift($json[$key], $this->prepend[$field]);
486 1
                }
487 1
            }
488 1
            $this->page->jquery('$("#'.$this->validator->id($hier).'").hierSelect("#'.$this->validator->id($field).'", '.json_encode($json).');');
489 1
            $this->page->script('
490
                (function($) {
491
                    $.fn.hierSelect = function(select, options) {
492
                        $(this).change(function() {
493
                            var id = $(this).val();
494
                            var hier = $(select);
495
                            var preselect = hier.val();
496
                            hier.each(function(){
497
                                hier.children().remove();
498
                                if (id != "") {
499
                                    $.each(options[id], function(key,value){
500
                                        if (typeof value === "object") {
501
                                            var optgroup = $("<optgroup />", {label:key});
502
                                            $.each(value, function(key,value){
503
                                                if (key == 0) key = "";
504
                                                var option = $("<option />").val(key).html(value);
505
                                                if (preselect == key) option.attr("selected", "selected");
506
                                                optgroup.append(option);
507
                                            });
508
                                            hier.append(optgroup);
509
                                        } else {
510
                                            if (key == 0) key = "";
511
                                            var option = $("<option />").val(key).html(value);
512
                                            if (preselect == key) option.attr("selected", "selected");
513
                                            hier.append(option);
514
                                        }
515
                                    });
516
                                } // end if id
517
                            }); // end each hier
518
                        }); // end this change
519
                    };
520
                })(jQuery);
521 1
            ');
522 1
            $select = (isset($select[$selected])) ? $select[$selected] : array();
523 1
        }
524 2
        $values = '';
525 2
        if (!empty($select)) {
526 1
            $selected = (array) $this->defaultValue($field);
527 1
            if (isset($this->prepend[$field])) {
528 1
                $values .= '<option value="">'.$this->prepend[$field].'</option>';
529 1
            }
530 1
            foreach ($select as $key => $value) {
0 ignored issues
show
The expression $select of type string|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...
531 1
                if (is_array($value)) {
532 1
                    $values .= '<optgroup label="'.htmlspecialchars($key).'">';
533 1
                    foreach ($value as $optgroup_key => $optgroup_value) {
534 1
                        $values .= '<option value="'.$optgroup_key.'"';
535 1
                        if (in_array($optgroup_key, $selected)) {
536 1
                            $values .= ' selected="selected"';
537 1
                        }
538 1
                        $values .= '>'.$optgroup_value.'</option>';
539 1
                    }
540 1
                    $values .= '</optgroup>';
541 1
                } else {
542 1
                    $values .= '<option value="'.$key.'"';
543 1
                    if (in_array($key, $selected)) {
544 1
                        $values .= ' selected="selected"';
545 1
                    }
546 1
                    $values .= '>'.$value.'</option>';
547
                }
548 1
            }
549 1
        }
550
        
551 2
        return $this->page->tag('select', $this->validate($field, $attributes), $values);
552
    }
553
554
    /**
555
     * Creates a textarea field.
556
     * 
557
     * @param string   $field       The textarea's name.
558
     * @param string[] $attributes  Anything else you would like to add besides the 'name', 'id', and data validation attributes.  If you don't set the 'cols' and 'rows' then we will.
559
     * 
560
     * @return string  A ``<textarea ...>`` html tag.
561
     * 
562
     * ```php
563
     * $form->values['description'] = '"default"';
564
     * 
565
     * echo $form->textarea('description');
566
     * ```
567
     */
568 2
    public function textarea($field, array $attributes = array())
569
    {
570 2
        $attributes['name'] = $field;
571 2
        $attributes['id'] = $this->validator->id($field);
572 2
        if (!isset($attributes['cols'])) {
573 2
            $attributes['cols'] = 40;
574 2
        }
575 2
        if (!isset($attributes['rows'])) {
576 2
            $attributes['rows'] = 10;
577 2
        }
578
579 2
        return $this->page->tag('textarea', $this->validate($field, $attributes), $this->defaultValue($field, 'escape'));
580
    }
581
582
    /**
583
     * Closes and cleans up shop.
584
     * 
585
     * @return string  The closing ``</form>`` tag with ``$this->footer`` and ``$this->hidden`` form fields preceding it.
586
     * 
587
     * ```php
588
     * echo $form->close();
589
     * ```
590
     */
591 4
    public function close()
592
    {
593 4
        $html = implode('', $this->footer);
594 4
        foreach ($this->hidden as $key => $value) {
595 1
            $html .= "\n\t".$this->input('hidden', array(
596 1
                'name' => $key,
597 1
                'value' => htmlspecialchars((string) $value),
598 1
            ));
599 4
        }
600
601 4
        return $html."\n</form>";
602
    }
603
604
    /**
605
     * This is used with menus for getting to the bottom of multi-dimensional arrays, and determining it's root keys and values
606
     * 
607
     * @param array $array 
608
     * 
609
     * @return array  A single-dimensional ``array($key => $value, ...)``'s.
610
     */
611 5
    private function flatten(array $array)
612
    {
613 5
        $single = array();
614 5
        if (isset($array['hier'])) {
615 1
            unset($array['hier']);
616 1
        }
617 5
        foreach ($array as $key => $value) {
618 5
            if (is_array($value)) {
619 2
                foreach ($this->flatten($value) as $key => $value) {
620 2
                    $single[$key] = $value;
621 2
                }
622 2
            } else {
623 5
                $single[$key] = $value;
624
            }
625 5
        }
626
627 5
        return $single;
628
    }
629
}
630