Completed
Push — master ( a68940...ed2b3e )
by Mikael
02:38
created

FormElement::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 1
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace Anax\HTMLForm;
4
5
/**
6
 * A utility class to easy creating and handling of forms
7
 */
8
class FormElement implements \ArrayAccess
9
{
10
11
    /**
12
     * Properties
13
     */
14
    public $attributes;
15
    public $characterEncoding;
16
17
18
19
    /**
20
     * Constructor creating a form element.
21
     *
22
     * @param string $name       of the element.
23
     * @param array  $attributes to set to the element. Default is an empty
24
     *                           array.
25
     */
26 21
    public function __construct($name, $attributes = [])
27
    {
28 21
        $this->attributes = $attributes;
29 21
        $this['name'] = $name;
30
        //$this['key'] = $name;
31
        //$this['name'] = isset($this['name']) ? $this['name'] : $name;
32
33 21
        $this->characterEncoding = 'UTF-8';
34 21
    }
35
  
36
  
37
38
    /**
39
     * Implementing ArrayAccess for this->attributes
40
     *
41
     * @return void
42
     */
43 21
    public function offsetSet($offset, $value)
44
    {
45 21 View Code Duplication
        if (is_null($offset)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
46
            $this->attributes[] = $value;
47
        } else {
48 21
            $this->attributes[$offset] = $value;
49
        }
50 21
    }
51
    
52
53
54
    /**
55
     * Implementing ArrayAccess for this->attributes
56
     */
57 17
    public function offsetExists($offset)
58
    {
59 17
        return isset($this->attributes[$offset]);
60
    }
61
    
62
63
64
    /**
65
     * Implementing ArrayAccess for this->attributes
66
     */
67
    public function offsetUnset($offset)
68
    {
69
        unset($this->attributes[$offset]);
70
    }
71
    
72
73
74
    /**
75
     * Implementing ArrayAccess for this->attributes
76
     */
77 20
    public function offsetGet($offset)
78
    {
79 20
        return isset($this->attributes[$offset]) ? $this->attributes[$offset] : null;
80
    }
81
82
83
84
    /**
85
     * Create a formelement from an array, factory returns the correct
86
     * instance.
87
     *
88
     * @param string $name       name of the element.
89
     * @param array  $attributes to use when creating the element.
90
     *
91
     * @return the instance of the form element.
92
     */
93
    public static function create($name, $attributes)
94
    {
95
        // Not supported is type=image, <button>, list, output, select-optgroup
96
        $types = [
97
98
            // Standard HTML 4.01
99
            'text'              => '\Anax\HTMLForm\FormElementText',
100
            'file'              => '\Anax\HTMLForm\FormElementFile',
101
            'password'          => '\Anax\HTMLForm\FormElementPassword',
102
            'hidden'            => '\Anax\HTMLForm\FormElementHidden',
103
            'textarea'          => '\Anax\HTMLForm\FormElementTextarea',
104
            'radio'             => '\Anax\HTMLForm\FormElementRadio',
105
            'checkbox'          => '\Anax\HTMLForm\FormElementCheckbox',
106
            'select'            => '\Anax\HTMLForm\FormElementSelect',
107
            'select-multiple'   => '\Anax\HTMLForm\FormElementSelectMultiple',
108
            'submit'            => '\Anax\HTMLForm\FormElementSubmit',
109
            'reset'             => '\Anax\HTMLForm\FormElementReset',
110
            'button'            => '\Anax\HTMLForm\FormElementButton',
111
112
            // HTML5
113
            'color'             => '\Anax\HTMLForm\FormElementColor',
114
            'date'              => '\Anax\HTMLForm\FormElementDate',
115
            'number'            => '\Anax\HTMLForm\FormElementNumber',
116
            'range'             => '\Anax\HTMLForm\FormElementRange',
117
            'tel'               => '\Anax\HTMLForm\FormElementTel',
118
            'email'             => '\Anax\HTMLForm\FormElementEmail',
119
            'url'               => '\Anax\HTMLForm\FormElementUrl',
120
            'search'            => '\Anax\HTMLForm\FormElementSearch',
121
            'file-multiple'     => '\Anax\HTMLForm\FormElementFileMultiple',
122
            'datetime'          => '\Anax\HTMLForm\FormElementDatetime',
123
            'datetime-local'    => '\Anax\HTMLForm\FormElementDatetimeLocal',
124
            'month'             => '\Anax\HTMLForm\FormElementMonth',
125
            'time'              => '\Anax\HTMLForm\FormElementTime',
126
            'week'              => '\Anax\HTMLForm\FormElementWeek',
127
128
            // Custom
129
            'search-widget'     => '\Anax\HTMLForm\FormElementSearchWidget',
130
            'checkbox-multiple' => '\Anax\HTMLForm\FormElementCheckboxMultiple',
131
            // Address
132
        ];
133
134
        // $attributes['type'] must contain a valid type creating an object to succeed.
135
        $type = isset($attributes['type']) ? $attributes['type'] : null;
136
137
        if ($type && isset($types[$type])) {
138
            return new $types[$type]($name, $attributes);
139
        } else {
140
            throw new Exception("Form element does not exists and can not be created: $name - $type");
141
        }
142
    }
143
144
145
146
    /**
147
     * Get id of an element.
148
     *
149
     * @return HTML code for the element.
150
     */
151 9
    public function getElementId()
152
    {
153 9
        return ($this['id'] = isset($this['id']) ? $this['id'] : 'form-element-' . $this['name']);
154
    }
155
156
157
158
    /**
159
     * Get alll validation messages.
160
     *
161
     * @return HTML code for the element.
162
     */
163 9
    public function getValidationMessages()
164
    {
165 9
        $messages = null;
166 9
        if (isset($this['validation-messages'])) {
167
            $message = null;
168
            foreach ($this['validation-messages'] as $val) {
169
                $message .= "<li>{$val}</li>\n";
170
            }
171
            $messages = "<ul class='validation-message'>\n{$message}</ul>\n";
172
        }
173 9
        return $messages;
174
    }
175
176
177
178
    /**
179
     * Get details for a HTML element, prepare for creating HTML code for it.
180
     *
181
     * @return HTML code for the element.
182
     */
183 9
    public function getHTMLDetails()
184
    {
185
        // Add disabled to be able to disable a form element
186
        // Add maxlength
187 9
        $id =  $this->GetElementId();
188
189 9
        $class = isset($this['class'])
190 9
            ? "{$this['class']}"
191 9
            : null;
192
193 9
        $validates = (isset($this['validation-pass']) && $this['validation-pass'] === false)
194 9
            ? ' validation-failed'
195 9
            : null;
196
            
197 9
        $class = (isset($class) || isset($validates))
198 9
            ? " class='{$class}{$validates}'"
199 9
            : null;
200
            
201 9
        $name = " name='{$this['name']}'";
202
203 9
        $label = isset($this['label'])
204 9
            ? ($this['label'] . (isset($this['required']) && $this['required']
205 5
                ? "<span class='form-element-required'>*</span>"
206 5
                : null))
207 9
            : null;
208
            
209 9
        $autofocus = isset($this['autofocus']) && $this['autofocus']
210 9
            ? " autofocus='autofocus'"
211 9
            : null;
212
            
213 9
        $required = isset($this['required']) && $this['required']
214 9
            ? " required='required'"
215 9
            : null;
216
            
217 9
        $readonly = isset($this['readonly']) && $this['readonly']
218 9
            ? " readonly='readonly'"
219 9
            : null;
220
            
221 9
        $placeholder = isset($this['placeholder']) && $this['placeholder']
222 9
            ? " placeholder='{$this['placeholder']}'"
223 9
            : null;
224
            
225 9
        $multiple = isset($this['multiple']) && $this['multiple']
226 9
            ? " multiple"
227 9
            : null;
228
            
229 9
        $max = isset($this['max'])
230 9
            ? " max='{$this['max']}'"
231 9
            : null;
232
            
233 9
        $min = isset($this['min'])
234 9
            ? " min='{$this['min']}'"
235 9
            : null;
236
            
237 9
        $low = isset($this['low'])
238 9
            ? " low='{$this['low']}'"
239 9
            : null;
240
            
241 9
        $high = isset($this['high'])
242 9
            ? " high='{$this['high']}'"
243 9
            : null;
244
            
245 9
        $optimum = isset($this['optimum'])
246 9
            ? " optimum='{$this['optimum']}'"
247 9
            : null;
248
            
249 9
        $step = isset($this['step'])
250 9
            ? " step='{$this['step']}'"
251 9
            : null;
252
            
253 9
        $size = isset($this['size'])
254 9
            ? " size='{$this['size']}'"
255 9
            : null;
256
            
257 9
        $text = isset($this['text'])
258 9
            ? htmlentities($this['text'], ENT_QUOTES, $this->characterEncoding)
259 9
            : null;
260
            
261 9
        $checked = isset($this['checked']) && $this['checked']
262 9
            ? " checked='checked'"
263 9
            : null;
264
            
265 9
        $type = isset($this['type'])
266 9
            ? " type='{$this['type']}'"
267 9
            : null;
268
            
269 9
        $title = isset($this['title'])
270 9
            ? " title='{$this['title']}'"
271 9
            : null;
272
            
273 9
        $pattern = isset($this['pattern'])
274 9
            ? " pattern='{$this['pattern']}'"
275 9
            : null;
276
            
277 9
        $description = isset($this['description'])
278 9
            ? $this['description']
279 9
            : null;
280
281 9
        $novalidate = isset($this['formnovalidate'])
282 9
            ? " formnovalidate='formnovalidate'"
283 9
            : null;
284
285 9
        $onlyValue = isset($this['value'])
286 9
            ? htmlentities($this['value'], ENT_QUOTES, $this->characterEncoding)
287 9
            : null;
288
            
289 9
        $value = isset($this['value'])
290 9
            ? " value='{$onlyValue}'"
291 9
            : null;
292
293 9
        $messages = $this->getValidationMessages();
294
        
295
        return [
296 9
            'id'            => $id,
297 9
            'class'         => $class,
298 9
            'name'          => $name,
299 9
            'label'         => $label,
300 9
            'autofocus'     => $autofocus,
301 9
            'required'      => $required,
302 9
            'readonly'      => $readonly,
303 9
            'placeholder'   => $placeholder,
304 9
            'multiple'      => $multiple,
305 9
            'min'           => $min,
306 9
            'max'           => $max,
307 9
            'low'           => $low,
308 9
            'high'          => $high,
309 9
            'step'          => $step,
310 9
            'optimum'       => $optimum,
311 9
            'size'          => $size,
312 9
            'text'          => $text,
313 9
            'checked'       => $checked,
314 9
            'type'          => $type,
315 9
            'title'         => $title,
316 9
            'pattern'       => $pattern,
317 9
            'description'   => $description,
318 9
            'novalidate'    => $novalidate,
319 9
            'onlyValue'     => $onlyValue,
320 9
            'value'         => $value,
321 9
            'messages'      => $messages,
322 9
        ];
323
    }
324
325
326
327
    /**
328
     * Get HTML code for a element.
329
     *
330
     * @return HTML code for the element.
331
     */
332 3
    public function getHTML()
333
    {
334 3
        $details = $this->getHTMLDetails();
335 3
        extract($details);
336
        
337
        // Create HTML for the element
338 3
        if (in_array($this['type'], ['submit', 'reset', 'button'])) {
339
            // type=submit || reset || button
340
            return <<<EOD
341
<span>
342 2
<input id='$id'{$type}{$class}{$name}{$value}{$autofocus}{$readonly}{$novalidate}{$title} />
343 2
</span>
344 2
EOD;
345 1
        } elseif ($this['type'] == 'search-widget') {
346
            // custom search-widget with type=search and type=submit
347
            $label = isset($this['label']) ? " value='{$this['label']}'" : null;
348
            $classSubmit = isset($this['class-submit']) ? " class='{$this['class-submit']}'" : null;
349
          
350
            return <<<EOD
351
<p>
352
<input id='$id' type='search'{$class}{$name}{$value}{$autofocus}{$required}{$readonly}{$placeholder}/>
353
<input id='do-{$id}' type='submit'{$classSubmit}{$label}{$readonly}{$title}/>
354
</p>
355
<p class='cf-desc'>{$description}</p>
356
EOD;
357 1
        } elseif ($this['type'] == 'textarea') {
358
            // textarea
359
            return <<<EOD
360
<p>
361
<label for='$id'>$label</label><br/>
362
<textarea id='$id'{$class}{$name}{$autofocus}{$required}{$readonly}{$placeholder}{$title}>{$onlyValue}</textarea>
363
</p>
364
<p class='cf-desc'>{$description}</p>
365
EOD;
366 1
        } elseif ($this['type'] == 'hidden') {
367
            // type=hidden
368
            return "<input id='$id'{$type}{$class}{$name}{$value} />\n";
369 1
        } elseif ($this['type'] == 'checkbox') {
370
            // checkbox
371
            return <<<EOD
372
<p>
373
<input id='$id'{$type}{$class}{$name}{$value}{$autofocus}{$required}{$readonly}{$checked}{$title} />
374
<label for='$id'>$label</label>
375
{$messages}
376
</p>
377
<p class='cf-desc'>{$description}</p>
378
EOD;
379 1
        } elseif ($this['type'] == 'radio') {
380
            // radio
381
            $ret = null;
382
            foreach ($this['values'] as $val) {
383
                $id .= $val;
384
                $item = $onlyValue  = htmlentities($val, ENT_QUOTES, $this->characterEncoding);
385
                $value = " value='{$onlyValue}'";
386
                $checked = isset($this['checked']) && $val === $this['checked']
387
                    ? " checked='checked'"
388
                    : null;
389
                $ret .= <<<EOD
390
<p>
391
<input id='$id'{$type}{$class}{$name}{$value}{$autofocus}{$readonly}{$checked}{$title} />
392
<label for='$id'>$item</label>
393
{$messages}
394
</p>
395
EOD;
396
            }
397
            return <<<EOD
398
<div>
399
<p class='cf-label'>{$label}</p>
400
{$ret}
401
<p class='cf-desc'>{$description}</p>
402
</div>
403
EOD;
404 1
        } elseif ($this['type'] == 'file-multiple') {
405
            // file-multiple
406
            // @codingStandardsIgnoreStart
407
            return <<<EOD
408
<p>
409
<label for='$id'>$label</label>
410
<br/>
411
<input id='$id' type='file' multiple{$class}{$name}{$value}{$autofocus}{$required}{$readonly}{$placeholder}{$title}{$multiple}{$pattern}{$max}{$min}{$step}/>
412
{$messages}
413
</p>
414
<p class='cf-desc'>{$description}</p>
415
EOD;
416
            // @codingStandardsIgnoreEnd
417
        } else {
418
            // Everything else
419
            // @codingStandardsIgnoreStart
420
            return <<<EOD
421
<p>
422 1
<label for='$id'>$label</label>
423
<br/>
424 1
<input id='$id'{$type}{$class}{$name}{$value}{$autofocus}{$required}{$readonly}{$placeholder}{$title}{$multiple}{$pattern}{$max}{$min}{$step}/>
425 1
{$messages}
426
</p>
427 1
<p class='cf-desc'>{$description}</p>
428 1
EOD;
429
            // @codingStandardsIgnoreEnd
430
        }
431
    }
432
433
434
435
    /**
436
     * Validate the form element value according a ruleset.
437
     *
438
     * @param array     $rules validation rules.
439
     * @param Form|null $form  the parent form.
440
     *
441
     * @return boolean true if all rules pass, else false.
442
     */
443 2
    public function validate($rules, $form = null)
0 ignored issues
show
Unused Code introduced by
The parameter $form 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...
444
    {
445 2
        $regExpEmailAddress = '/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b/i';
0 ignored issues
show
Unused Code introduced by
$regExpEmailAddress 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...
446
        $tests = [
447
            'fail' => [
448 2
                'message' => 'Will always fail.',
449
                'test' => 'return false;'
450 2
            ],
451
452
            'pass' => [
453 2
                'message' => 'Will always pass.',
454
                'test' => 'return true;'
455 2
            ],
456
457
            'not_empty' => [
458 2
                'message' => 'Can not be empty.',
459
                'test' => 'return $value != "";'
460 2
            ],
461
462
            'not_equal' => [
463 2
                'message' => 'Value not valid.',
464
                'test' => 'return $value != $arg;'
465 2
            ],
466
467
            'numeric' => [
468 2
                'message' => 'Must be numeric.',
469
                'test' => 'return is_numeric($value);'
470 2
            ],
471
472
            'email_adress' => [
473 2
                'message' => 'Must be an email adress.',
474 1
                'test' => function ($value) {
475 1
                    return preg_match('/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b/i', $value) === 1;
476
                }
477 2
            ],
478
479
            'match' => [
480 2
                'message' => 'The field does not match.',
481
                'test' => 'return $value == $form[$arg]["value"] ;'
482 2
            ],
483
              
484
            'must_accept' => [
485 2
                'message' => 'You must accept this.',
486
                'test' => 'return $checked;'
487 2
            ],
488
489 2
            'custom_test' => true,
490 2
        ];
491
492
        // max tecken, min tecken, datum, tid, datetime, mysql datetime
493
494 2
        $pass = true;
495 2
        $messages = array();
496 2
        $value = $this['value'];
497 2
        $checked = $this['checked'];
0 ignored issues
show
Unused Code introduced by
$checked 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...
498
499 2
        foreach ($rules as $key => $val) {
500 2
            $rule = is_numeric($key) ? $val : $key;
501 2
            if (!isset($tests[$rule])) {
502 1
                throw new Exception("Validation of form element failed, no such validation rule exists: $rule");
503
            }
504 1
            $arg = is_numeric($key) ? null : $val;
505
506 1
            $test = ($rule == 'custom_test') ? $arg : $tests[$rule];
507 1
            $status = null;
0 ignored issues
show
Unused Code introduced by
$status 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...
508 1
            if (is_callable($test['test'])) {
509 1
                $status = $test['test']($value);
510 1
            } else {
511
                $status = eval($test['test']);
512
            }
513
514 1
            if ($status === false) {
515
                $messages[] = $test['message'];
516
                $pass = false;
517
            }
518 1
        }
519
520 1
        if (!empty($messages)) {
521
            $this['validation-messages'] = $messages;
522
        }
523 1
        return $pass;
524
    }
525
526
527
528
    /**
529
     * Use the element name as label if label is not set.
530
     *
531
     * @param string $append a colon as default to the end of the label.
532
     *
533
     * @return void
534
     */
535 10
    public function useNameAsDefaultLabel($append = ':')
536
    {
537 10 View Code Duplication
        if (!isset($this['label'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
538 10
            $this['label'] = ucfirst(strtolower(str_replace(array('-','_'), ' ', $this['name']))).$append;
539 10
        }
540 10
    }
541
542
543
544
    /**
545
     * Use the element name as value if value is not set.
546
     *
547
     * @return void
548
     */
549 3
    public function useNameAsDefaultValue()
550
    {
551 3 View Code Duplication
        if (!isset($this['value'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
552 3
            $this['value'] = ucfirst(strtolower(str_replace(array('-','_'), ' ', $this['name'])));
553 3
        }
554 3
    }
555
556
557
558
    /**
559
     * Get the value of the form element.
560
     *
561
     * @return mixed the value of the form element.
562
     */
563 6
    public function getValue()
564
    {
565 6
        return $this['value'];
566
    }
567
568
569
570
    /**
571
     * Get the value of the form element.
572
     *
573
     * @return mixed the value of the form element.
574
     */
575
    public function value()
576
    {
577
        return $this['value'];
578
    }
579
580
581
582
    /**
583
     * Get the status of the form element if it is checked or not.
584
     *
585
     * @return mixed the value of the form element.
586
     */
587
    public function checked()
588
    {
589
        return $this['checked'];
590
    }
591
592
593
594
    /**
595
     * Get the value of the form element, if value is empty return null.
596
     *
597
     * @return mixed the value of the form element. Null if the value is empty.
598
     */
599
    public function getValueNullIfEmpty()
600
    {
601
        return empty($this['value']) ? null : $this['value'];
602
    }
603
}
604