Field::__toString()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 4
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
namespace Rocket\UI\Forms\Fields;
3
4
use Rocket\UI\Forms\Forms;
5
6
/**
7
 * Creates a form field
8
 *
9
 * @author Stéphane Goetz
10
 *
11
 * @method $this value(string $value) the value in the field
12
 * @method $this inline(bool $enabled)
13
 * @method $this id(string $id)
14
 * @method $this width(string $width)
15
 * @method $this height(string $height)
16
 * @method $this tip(string $tip)
17
 * @method $this label_style(string $label)
18
 * @method $this writable(boolean $writable)
19
 * @method $this validate(boolean $validate)
20
 * @method $this maxlength(int $max)
21
 */
22
class Field
23
{
24
    /**
25
     * The field name
26
     *
27
     * @var string
28
     */
29
    protected $name;
30
31
    /**
32
     * Options for the field
33
     *
34
     * @var array
35
     */
36
    protected $params;
37
38
    /**
39
     * Unique ID for the field
40
     * @var string
41
     */
42
    protected $id;
43
44
    /**
45
     * Show the label or not ?
46
     * @var bool
47
     */
48
    protected $show_label = true;
49
50
    /**
51
     * The attributes that will be applied to the <input>
52
     * @var array
53
     */
54
    protected $input_attributes = [
55
        'class' => ['elm', 'form-control'],
56
    ];
57
58
    /**
59
     * The attributes that will be applied to the <label>
60
     * @var array
61
     */
62
    protected $label_attributes = [
63
        'class' => ['elm'],
64
    ];
65
66
    /**
67
     * The attributes that will be applied to the <span> with the label
68
     * @var array
69
     */
70
    protected $span_attributes = [
71
        'class' => ['field-label', 'control-label'],
72
    ];
73
74
    /**
75
     * The final content of the field
76
     * @var string
77
     */
78
    protected $result;
79
80
    /**
81
     * Is the field required or not ?
82
     *
83
     * @var bool
84
     */
85
    protected $required = '';
86
87
    /**
88
     * The fields type, will be added in <input type="" ...
89
     *
90
     * @var string
91
     */
92
    protected $type = 'text';
93
94
    /**
95
     * The javascript events bound to the field
96
     *
97
     * @var array
98
     */
99
    protected $events = [];
100
101
    /**
102
     * The javascript events bound to the label
103
     * @var array
104
     */
105
    protected $events_label = [];
106
107
    protected static $templates;
108
109
    protected static $config;
110
111
    /**
112
     * @var \Rocket\UI\Script\JS
113
     */
114
    protected static $js;
115
116
    /**
117
     * @var callable
118
     */
119
    protected static $js_resolver;
120
121
    /**
122
     * Initializes the field
123
     * @param string $name
124
     * @param array $data
125
     */
126
    public function __construct($name, $data = [])
127
    {
128
        $this->name = $name;
129
130
        $required = false;
131
        $value = '';
132
        if ($validator = $this->getValidator()) {
133
            $required = $validator->isRequired($name);
134
            $value = $validator->getValue($name, array_key_exists('default', $data) ? $data['default'] : '');
135
        }
136
137
        // Default configuration
138
        $default = $this->getDefaults() + ['value' => $value, 'required' => $required];
139
140
        // Final configuration
141
        $this->params = array_replace_recursive($default, $data);
142
    }
143
144
    /**
145
     * Set the javascript queueing instance callback
146
     *
147
     * @param $callable callable
148
     */
149
    public static function setJSResolver($callable)
150
    {
151
        self::$js_resolver = $callable;
152
    }
153
154
    /**
155
     * Get the Javascript queueing instance
156
     *
157
     * @throws \Exception
158
     * @return \Rocket\UI\Script\JS
159
     */
160
    protected function getJS()
161
    {
162
        if (null === self::$js) {
163
            if (!self::$js = call_user_func(self::$js_resolver)) {
164
                throw new \Exception('No javascript queueing instance can be found');
165
            }
166
        }
167
168
        return self::$js;
169
    }
170
171
    /**
172
     * Get the Javascript queueing instance
173
     *
174
     * @throws \Exception
175
     * @return \Rocket\UI\Forms\ValidatorAdapters\ValidatorInterface
176
     */
177
    protected function getValidator()
178
    {
179
        return Forms::getFormValidator();
180
    }
181
182
    /**
183
     * Get the default values array
184
     *
185
     * @return array
186
     */
187
    protected function getDefaults()
188
    {
189
        $defaults = [
190
            'title' => '',
191
            'live' => 'blur',
192
            'validate' => true,
193
            'label_position' => 'before',
194
            'inline' => false, //afficher le label sur la même ligne que le champ
195
            'type' => $this->type,
196
            'width' => 0,
197
            'height' => 12,
198
            'margins' => 12, //width in px
199
            'data_attributes' => [],
200
            'class' => '',
201
            'multifield' => false,
202
            'maxlength' => [
203
                'slider' => true,
204
            ],
205
        ];
206
207
        return $defaults;
208
    }
209
210
    /**
211
     * Proper destructor
212
     */
213
    public function __destruct()
214
    {
215
        unset($this->name);
216
        unset($this->input_attributes);
217
        unset($this->label_attributes);
218
        unset($this->params);
219
        unset($this->required);
220
        unset($this->result);
221
        unset($this->show_label);
222
        unset($this->span_attributes);
223
    }
224
225
    /**
226
     * Adds a jquery event to the field
227
     * @param string $name
228
     * @param string $action
229
     * @param bool $bind_to_attributes
230
     * @return $this
231
     */
232
    public function event($name, $action, $bind_to_attributes = false)
233
    {
234
        if ($bind_to_attributes) {
235
            $this->input_attributes[$name] = $action;
236
        } else {
237
            $this->events[$name] = $action;
238
        }
239
240
        return $this;
241
    }
242
243
    /**
244
     * Adds a jquery event to the field
245
     * @param string $name
246
     * @param string $action
247
     * @return $this
248
     */
249
    public function eventLabel($name, $action)
250
    {
251
        $this->events_label[$name] = $action;
252
253
        return $this;
254
    }
255
256
    /**
257
     * Change parameters
258
     *
259
     * @param $method
260
     * @param $arguments
261
     * @throws \Exception
262
     * @return $this
263
     */
264
    public function __call($method, $arguments)
265
    {
266
        if (strlen(ltrim($method, '_')) != strlen($method)) {
267
            throw new \Exception('Invalid method');
268
        } else {
269
            if (is_array($arguments[0])) {
270
                if (array_key_exists($method, $this->params)) {
271
                    $this->params[$method] =
272
                        array_merge($this->params[$method], $arguments[0]);
273
                } else {
274
                    $this->params[$method] = $arguments[0];
275
                }
276
            } else {
277
                $this->params[$method] = $arguments[0];
278
            }
279
280
            return $this;
281
        }
282
    }
283
284
    /**
285
     * Set data attributes to the field
286
     *
287
     * @param $key string
288
     * @param $value string|array
289
     * @return $this
290
     */
291
    public function data($key, $value)
292
    {
293
        $this->params['data_attributes'][$key] = $value;
294
295
        return $this;
296
    }
297
298
    /**
299
     * Checks if it's an error
300
     */
301
    protected function hasError()
302
    {
303
        $validator = $this->getValidator();
304
305
        if ($validator && $validator->hasError($this->name)) {
306
            $this->label_attributes['class'][] = 'has-error';
307
        }
308
    }
309
310
    /**
311
     * Is required ?
312
     */
313
    protected function isRequired()
314
    {
315
        if ($this->params['required']
316
            && $this->show_label
317
            && $this->params['title'] != ''
318
        ) {
319
            $this->required = ' *';
0 ignored issues
show
Documentation Bug introduced by
The property $required was declared of type boolean, but ' *' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
320
        }
321
    }
322
323
    /**
324
     * Adds default css classes
325
     */
326
    protected function classes()
327
    {
328
        //Label
329
        if (array_key_exists('label_style', $this->params)) {
330
            $this->label_attributes['style'] = $this->params['label_style'];
331
        }
332
333
        if (array_key_exists('label_id', $this->params)) {
334
            $this->label_attributes['id'] = $this->params['label_id'];
335
        }
336
337
        $this->label_attributes['class']['form_type'] = 'form_' . $this->params['type'];
338
339
        //Input
340
        if (array_key_exists('input_style', $this->params)) {
341
            $this->input_attributes['style'] = $this->params['input_style'];
342
        }
343
344
        if (array_key_exists('class', $this->params)) {
345
            $this->input_attributes['class'][] = $this->params['class'];
346
        }
347
348
        $this->input_attributes['class']['type'] = 'form_' . $this->params['type'];
349
    }
350
351
    /**
352
     * Adds the width to the field
353
     */
354
    protected function applyWidth()
355
    {
356
        if (strpos($this->params['width'], '%') or
357
            strpos($this->params['width'], 'em') or
358
            strpos($this->params['width'], 'px')
359
        ) {
360
            $this->label_attributes['style']['width'] = $this->params['width'];
361
362
            if (strpos($this->params['width'], 'px')) {
363
                $w = str_replace('px', '', $this->params['width']);
364
                $w -= $this->params['margins'];
365
                $this->params['width'] = $w . 'px';
366
            }
367
            $this->input_attributes['style']['width'] = $this->params['width'];
368
369
            return;
370
        }
371
372
        if ($this->params['width'] != 0) {
373
            $this->label_attributes['class'][] = 'col-xs-' . $this->params['width'];
374
375
            return;
376
        }
377
378
        if ($this->params['width'] == 0) {
379
            $this->label_attributes['class'][] = 'col-full';
380
381
            return;
382
        }
383
    }
384
385
    /**
386
     * Extracts the validation rules to make a javascript validation
387
     */
388
    protected function inputValidation()
389
    {
390
        if (!$this->params['validate']) {
391
            return;
392
        }
393
394
        //TODO :: finish integration with validation libraries
395
396
        /*$validator = $this->getValidator();
397
        if ($validator) {
398
            $field = $validator->getClientRules($this->name);
399
            if (!empty($field)) {
400
                $this->input_attributes['data-rules'] = $field;
401
                $this->input_attributes['data-display'] = strip_tags($this->params['title']);
402
                $this->input_attributes['data-live'] = $this->params['live'];
403
            }
404
        }*/
405
    }
406
407
    /**
408
     * generate an unique id to use in the field
409
     */
410
    protected function generateId()
411
    {
412
        $s = strtoupper(md5(uniqid(rand(), true)));
413
414
        return substr($s, 0, 8) . '_' . substr($s, 8, 4) . '_' . substr($s, 12, 4) . '_' . substr($s, 16, 4);
415
    }
416
417
    public function template($string, array $args = [])
418
    {
419
        $class = \Rocket\UI\Forms\Templates\Bootstrap::class;
420
        if (null == self::$templates) {
421
            self::$templates[$class] = get_class_vars($class);
422
        }
423
424
        //if there are some arguments
425
        if (empty($args)) {
426
            return self::$templates[$class][$string];
427
        }
428
429
        return strtr(self::$templates[$class][$string], $args);
430
    }
431
432
    /**
433
     * Renders the field
434
     * @return string
435
     */
436
    public function render()
437
    {
438
        if (!array_key_exists('id', $this->params) || empty($this->params['id'])) {
439
            $this->params['id'] = $this->generateId();
440
        }
441
442
        $this->id = $this->params['id'];
443
444
        //Run the scripts and events before (they may add some parameters)
445
        $this->renderEvents();
446
        $this->renderScript();
447
448
        $this->input_attributes['value'] = $this->params['value'];
449
450
        $this->classes();
451
        $this->applyWidth();
452
453
        $this->isRequired();
454
        $this->hasError();
455
456
        $this->inputAttributes();
457
        $this->inputDataAttributes();
458
        $this->inputValidation();
0 ignored issues
show
Unused Code introduced by
The call to the method Rocket\UI\Forms\Fields\Field::inputValidation() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
459
        $this->inputTip();
460
461
        if ($this->show_label) {
462
            $tag = $this->params['multifield'] ? 'div' : 'label';
463
            $this->result .= "<$tag " . $this->renderAttributes($this->label_attributes) . '>';
464
        }
465
466
        if ($this->params['label_position'] == 'before') {
467
            $this->renderTitle();
468
        }
469
470
        $this->renderInner();
471
472
        if ($this->params['label_position'] != 'before') {
473
            $this->renderTitle();
474
        }
475
476
        $validator = $this->getValidator();
477
        if ($validator && $validator->hasError($this->name)) {
478
            $this->result .= $this->template('HELP_BLOCK', ['!help' => $this->formatErrors($validator->getErrors($this->name))]);
479
        }
480
481
        if ($this->show_label) {
482
            $this->result .= "</$tag>";
0 ignored issues
show
Bug introduced by
The variable $tag does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
483
        }
484
485
        return $this->result;
486
    }
487
488
    protected function formatErrors($errors)
489
    {
490
        if (is_string($errors)) {
491
            return $errors;
492
        }
493
494
        if (count($errors) == 1) {
495
            return $errors[0];
496
        }
497
498
        return '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>';
499
    }
500
501
    /**
502
     * Auto render the field
503
     * @return string
504
     */
505
    public function __toString()
506
    {
507
        return $this->render();
508
    }
509
510
    /**
511
     * Renders the script
512
     */
513
    protected function renderScript()
514
    {
515
        if (!empty($this->params['maxlength']['maxCharacters'])) {
516
            $this->getJS()->ready('$("#' . $this->id . '").maxlength(' . json_encode($this->params['maxlength']) . ');');
517
        }
518
    }
519
520
    /**
521
     * Renders the javascript events
522
     */
523
    protected function renderEvents()
524
    {
525
        if (count($this->events) or count($this->events_label)) {
526
            if (count($this->events)) {
527
                foreach ($this->events as $event => $action) {
528
                    $this->getJS()->ready('$("#' . $this->id . '").' . $event . '(function(){ ' . $action . ' });');
529
                }
530
            }
531
532
            if (count($this->events_label)) {
533
                foreach ($this->events_label as $event => $action) {
534
                    $this->getJS()->ready('$("#' . $this->id . '").' . $event . '(function(){ ' . $action . ' });');
535
                }
536
            }
537
        }
538
    }
539
540
    /**
541
     * Renders the title
542
     */
543
    protected function renderTitle()
544
    {
545
        if ($this->show_label) {
546
            $this->result .= '<span' . $this->renderAttributes($this->span_attributes) . '>';
547
            if (array_key_exists('label_style', $this->params) && $this->params['label_style'] == 'small') {
548
                $this->result .= '<small>';
549
            }
550
            $this->result .= $this->params['title'] . $this->required;
551
            if (array_key_exists('label_style', $this->params) && $this->params['label_style'] == 'small') {
552
                $this->result .= '</small>';
553
            }
554
            $this->result .= '</span>';
555
556
            if ($this->params['inline'] == false && $this->params['title'] != ''
557
                && $this->params['label_position'] == 'before'
558
            ) {
559
                $this->result .= '<br />';
560
            }
561
        }
562
    }
563
564
    /**
565
     * Render the inner field
566
     */
567
    protected function renderInner()
568
    {
569
        $this->result .= '<input' . $this->renderAttributes($this->input_attributes) . ' />';
570
    }
571
572
    /**
573
     * Renders the input attributes
574
     */
575
    protected function inputAttributes()
576
    {
577
        $this->input_attributes['name'] = $this->name;
578
        $this->input_attributes['id'] = $this->id;
579
        $this->input_attributes['type'] = $this->params['type'];
580
581
        if (array_key_exists('enabled', $this->params)
582
            && $this->params['enabled'] == false
583
        ) {
584
            $this->input_attributes['disabled'] = 'disabled';
585
            $this->input_attributes['class'][] = 'disabled';
586
        }
587
588
        if (array_key_exists('writable', $this->params)
589
            && $this->params['writable'] == false
590
        ) {
591
            $this->input_attributes['readonly'] = 'readonly';
592
            $this->input_attributes['class'][] = 'readonly';
593
        }
594
595
        if (array_key_exists('onchange', $this->params)) {
596
            $this->input_attributes['onchange'] = $this->params['onchange'];
597
        }
598
599
        if (array_key_exists('placeholder', $this->params)) {
600
            $this->input_attributes['placeholder'] = $this->params['placeholder'];
601
        }
602
    }
603
604
    protected function labelAttributes()
605
    {
606
        $this->label_attributes['for'] = $this->id;
607
    }
608
609
    /**
610
     * Take all data attributes and render them to the input item
611
     */
612
    protected function inputDataAttributes()
613
    {
614
        foreach ($this->params['data_attributes'] as $key => $value) {
615
            $this->input_attributes['data-' . $key] = $value;
616
        }
617
    }
618
619
    /**
620
     * Renders the input tip
621
     */
622
    protected function inputTip()
623
    {
624
        if (isset($this->params['tip']) && $this->params['tip'] != '') {
625
            $this->input_attributes['title'] = json_encode($this->params['tip']);
626
        }
627
    }
628
629
    /**
630
     * Renders the special attributes
631
     *
632
     * @param  array $attr
633
     * @return string
634
     */
635
    protected function renderAttributes($attr)
636
    {
637
        if (array_key_exists('class', $attr) && is_array($attr['class'])) {
638
            $attr['class'] = implode(' ', $attr['class']);
639
        }
640
641
        if (array_key_exists('style', $attr) && is_array($attr['style'])) {
642
            $out = '';
643
            foreach ($attr['style'] as $key => $value) {
644
                $out .= $key . ':' . $value . '; ';
645
            }
646
            $attr['style'] = $out;
647
        }
648
649
        $out = ' ';
650
        foreach ($attr as $key => $value) {
651
            $out .= $key . '="' . $value . '" ';
652
        }
653
654
        return $out;
655
    }
656
}
657