Component::select()   D
last analyzed

Complexity

Conditions 17
Paths 60

Size

Total Lines 89
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 53
CRAP Score 17

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 89
ccs 53
cts 53
cp 1
rs 4.8361
cc 17
eloc 42
nc 60
nop 2
crap 17

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
Unused Code introduced by
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...
Unused Code introduced by
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...
Unused Code introduced by
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
Bug introduced by
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
Unused Code introduced by
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...
Unused Code introduced by
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
Bug introduced by
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
Bug introduced by
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
Documentation introduced by
$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
Bug introduced by
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
Bug introduced by
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