Completed
Pull Request — master (#607)
by Tortue
03:28
created

TwitterBootstrap5::getUneditableClasses()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
namespace Former\Framework;
3
4
use Former\Interfaces\FrameworkInterface;
5
use Former\Traits\Field;
6
use Former\Traits\Framework;
7
use HtmlObject\Element;
8
use Illuminate\Container\Container;
9
use Illuminate\Support\Str;
10
11
/**
12
 * The Twitter Bootstrap form framework
13
 */
14
class TwitterBootstrap5 extends Framework implements FrameworkInterface
15
{
16
	/**
17
	 * Form types that trigger special styling for this Framework
18
	 *
19
	 * @var array
20
	 */
21
	protected $availableTypes = array('horizontal', 'vertical', 'inline');
22
23
	/**
24
	 * The button types available
25
	 *
26
	 * @var array
27
	 */
28
	private $buttons = array(
29
		'lg',
30
		'sm',
31
		'xs',
32
		'block',
33
		'link',
34
		'primary',
35
		'secondary',
36
		'warning',
37
		'danger',
38
		'success',
39
		'info',
40
		'light',
41
		'dark',
42
	);
43
44
	/**
45
	 * The field sizes available
46
	 *
47
	 * @var array
48
	 */
49
	private $fields = array(
50
		'lg',
51
		'sm',
52
		// 'col-xs-1', 'col-xs-2', 'col-xs-3', 'col-xs-4', 'col-xs-5', 'col-xs-6',
53
		// 'col-xs-7', 'col-xs-8', 'col-xs-9', 'col-xs-10', 'col-xs-11', 'col-xs-12',
54
		// 'col-sm-1', 'col-sm-2', 'col-sm-3', 'col-sm-4', 'col-sm-5', 'col-sm-6',
55
		// 'col-sm-7', 'col-sm-8', 'col-sm-9', 'col-sm-10', 'col-sm-11', 'col-sm-12',
56
		// 'col-md-1', 'col-md-2', 'col-md-3', 'col-md-4', 'col-md-5', 'col-md-6',
57
		// 'col-md-7', 'col-md-8', 'col-md-9', 'col-md-10', 'col-md-11', 'col-md-12',
58
		// 'col-lg-1', 'col-lg-2', 'col-lg-3', 'col-lg-4', 'col-lg-5', 'col-lg-6',
59
		// 'col-lg-7', 'col-lg-8', 'col-lg-9', 'col-lg-10', 'col-lg-11', 'col-lg-12',
60
	);
61
62
	/**
63
	 * The field states available
64
	 *
65
	 * @var array
66
	 */
67
	protected $states = array(
68
		'is-invalid',
69
	);
70
71
	/**
72
	 * The default HTML tag used for icons
73
	 *
74
	 * @var string
75
	 */
76
	protected $iconTag = 'i';
77
78
	/**
79
	 * The default set for icon fonts
80
	 * By default Bootstrap 4 offers no fonts, but we'll add Font Awesome
81
	 *
82
	 * @var string
83
	 */
84
	protected $iconSet = 'fa';
85
86
	/**
87
	 * The default prefix icon names
88
	 * Using Font Awesome 5, this can be 'fa' or 'fas' for solid, 'far' for regular
89
	 *
90
	 * @var string
91
	 */
92
	protected $iconPrefix = 'fa';
93
94
	/**
95
	 * Create a new TwitterBootstrap instance
96
	 *
97
	 * @param \Illuminate\Container\Container $app
98
	 */
99
	public function __construct(Container $app)
100
	{
101
		$this->app = $app;
0 ignored issues
show
Documentation Bug introduced by
It seems like $app of type object<Illuminate\Container\Container> is incompatible with the declared type object<Former\Traits\Container> of property $app.

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...
102
		$this->setFrameworkDefaults();
103
	}
104
105
	////////////////////////////////////////////////////////////////////
106
	/////////////////////////// FILTER ARRAYS //////////////////////////
107
	////////////////////////////////////////////////////////////////////
108
109
	/**
110
	 * Filter buttons classes
111
	 *
112
	 * @param  array $classes An array of classes
113
	 *
114
	 * @return string[] A filtered array
115
	 */
116
	public function filterButtonClasses($classes)
117
	{
118
		// Filter classes
119
		// $classes = array_intersect($classes, $this->buttons);
120
121
		// Prepend button type
122
		$classes   = $this->prependWith($classes, 'btn-');
123
		$classes[] = 'btn';
124
125
		return $classes;
126
	}
127
128
	/**
129
	 * Filter field classes
130
	 *
131
	 * @param  array $classes An array of classes
132
	 *
133
	 * @return array A filtered array
134
	 */
135
	public function filterFieldClasses($classes)
136
	{
137
		// Filter classes
138
		$classes = array_intersect($classes, $this->fields);
139
140
		// Prepend field type
141
		$classes = array_map(function ($class) {
142
			return Str::startsWith($class, 'col') ? $class : 'input-'.$class;
143
		}, $classes);
144
145
		return $classes;
146
	}
147
148
	////////////////////////////////////////////////////////////////////
149
	///////////////////// EXPOSE FRAMEWORK SPECIFICS ///////////////////
150
	////////////////////////////////////////////////////////////////////
151
152
	/**
153
	 * Framework error state
154
	 *
155
	 * @return string
156
	 */
157
	public function errorState()
158
	{
159
		return 'is-invalid';
160
	}
161
162
	/**
163
	 * Returns corresponding inline class of a field
164
	 *
165
	 * @param Field $field
166
	 *
167
	 * @return string
168
	 */
169
	public function getInlineLabelClass($field)
170
	{
171
		$inlineClass = parent::getInlineLabelClass($field);
172
		if ($field->isOfType('checkbox', 'checkboxes', 'radio', 'radios')) {
173
			$inlineClass = 'form-check-label';
174
		}
175
176
		return $inlineClass;
177
	}
178
179
	/**
180
	 * Set the fields width from a label width
181
	 *
182
	 * @param array $labelWidths
183
	 */
184
	protected function setFieldWidths($labelWidths)
185
	{
186
		$labelWidthClass = $fieldWidthClass = $fieldOffsetClass = '';
187
188
		$viewports = $this->getFrameworkOption('viewports');
189
		foreach ($labelWidths as $viewport => $columns) {
190
			if ($viewport) {
191
				$labelWidthClass .= " col-$viewports[$viewport]-$columns";
192
				$fieldWidthClass .= " col-$viewports[$viewport]-".(12 - $columns);
193
				$fieldOffsetClass .= " offset-$viewports[$viewport]-$columns";
194
			}
195
		}
196
197
		$this->labelWidth  = ltrim($labelWidthClass);
198
		$this->fieldWidth  = ltrim($fieldWidthClass);
199
		$this->fieldOffset = ltrim($fieldOffsetClass);
200
	}
201
202
	////////////////////////////////////////////////////////////////////
203
	///////////////////////////// ADD CLASSES //////////////////////////
204
	////////////////////////////////////////////////////////////////////
205
206
	/**
207
	 * Add classes to a field
208
	 *
209
	 * @param Field $field
210
	 * @param array $classes The possible classes to add
211
	 *
212
	 * @return Field
213
	 */
214
	public function getFieldClasses(Field $field, $classes)
215
	{
216
		// Add inline class for checkables
217
		if ($field->isCheckable()) {
218
			// Adds correct checkbox input class when is a checkbox (or radio)
219
			$field->addClass('form-check-input');
220
			$classes[] = 'form-check';
221
222
			if (in_array('inline', $classes)) {
223
				$field->inline();
0 ignored issues
show
Documentation Bug introduced by
The method inline does not exist on object<Former\Traits\Field>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
224
			}
225
		}
226
227
		// Filter classes according to field type
228
		if ($field->isButton()) {
229
			$classes = $this->filterButtonClasses($classes);
230
		} else {
231
			$classes = $this->filterFieldClasses($classes);
232
		}
233
234
		// Add form-control class for text-type, textarea and file fields
235
		// As text-type is open-ended we instead exclude those that shouldn't receive the class
236
		if (!$field->isCheckable() && !$field->isButton() && !in_array($field->getType(), [
237
					'plaintext',
238
					'select',
239
				]) && !in_array('form-control', $classes)
240
		) {
241
			$classes[] = 'form-control';
242
		}
243
244
		// Add form-select class for select fields
245
		if ($field->getType() === 'select' && !in_array('form-select', $classes)) {
246
			$classes[] = 'form-select';
247
		}
248
249
		if ($this->app['former']->getErrors($field->getName())) {
250
			$classes[] = $this->errorState();
251
		}
252
253
		return $this->addClassesToField($field, $classes);
254
	}
255
256
	/**
257
	 * Add group classes
258
	 *
259
	 * @return string A list of group classes
260
	 */
261
	public function getGroupClasses()
262
	{
263
		if ($this->app['former.form']->isOfType('horizontal')) {
264
			return 'mb-3 row';
265
		} else {
266
			return 'mb-3';
267
		}
268
	}
269
270
	/**
271
	 * Add label classes
272
	 *
273
	 * @return string[] An array of attributes with the label class
274
	 */
275
	public function getLabelClasses()
276
	{
277
		if ($this->app['former.form']->isOfType('horizontal')) {
278
			return array('col-form-label', $this->labelWidth);
279
		} elseif ($this->app['former.form']->isOfType('inline')) {
280
			return array('visually-hidden');
281
		} else {
282
			return array('form-label');
283
		}
284
	}
285
286
	/**
287
	 * Add uneditable field classes
288
	 *
289
	 * @return string An array of attributes with the uneditable class
290
	 */
291
	public function getUneditableClasses()
292
	{
293
		return '';
0 ignored issues
show
Bug Best Practice introduced by
The return type of return ''; (string) is incompatible with the return type declared by the interface Former\Interfaces\Framew...e::getUneditableClasses of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
294
	}
295
296
	/**
297
	 * Add plain text field classes
298
	 *
299
	 * @return string An array of attributes with the plain text class
300
	 */
301
	public function getPlainTextClasses()
302
	{
303
		return 'form-control-plaintext';
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 'form-control-plaintext'; (string) is incompatible with the return type declared by the interface Former\Interfaces\Framew...ce::getPlainTextClasses of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
304
	}
305
306
	/**
307
	 * Add form class
308
	 *
309
	 * @param  string $type The type of form to add
310
	 *
311
	 * @return string|null
312
	 */
313
	public function getFormClasses($type)
314
	{
315
		return $type ? 'form-'.$type : null;
0 ignored issues
show
Bug Compatibility introduced by
The expression $type ? 'form-' . $type : null; of type string|null adds the type string to the return on line 315 which is incompatible with the return type declared by the interface Former\Interfaces\Framew...terface::getFormClasses of type array.
Loading history...
316
	}
317
318
	/**
319
	 * Add actions block class
320
	 *
321
	 * @return string|null
322
	 */
323
	public function getActionClasses()
324
	{
325
		if ($this->app['former.form']->isOfType('horizontal') || $this->app['former.form']->isOfType('inline')) {
326
			return 'mb-3 row';
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 'mb-3 row'; (string) is incompatible with the return type declared by the interface Former\Interfaces\Framew...rface::getActionClasses of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
327
		}
328
329
		return null;
330
	}
331
332
	/**
333
	 * Add floating label class
334
	 *
335
	 * @return string Get the floating label class
336
	 */
337
	public function getFloatingLabelClass()
338
	{
339
		return 'form-floating';
340
	}
341
342
	////////////////////////////////////////////////////////////////////
343
	//////////////////////////// RENDER BLOCKS /////////////////////////
344
	////////////////////////////////////////////////////////////////////
345
346
	/**
347
	 * Render an help text
348
	 *
349
	 * @param string $text
350
	 * @param array  $attributes
351
	 *
352
	 * @return Element
353
	 */
354
	public function createHelp($text, $attributes = array())
355
	{
356
		return Element::create('span', $text, $attributes)->addClass('form-text');
357
	}
358
359
	/**
360
	 * Render an validation error text
361
	 *
362
	 * @param string $text
363
	 * @param array  $attributes
364
	 *
365
	 * @return string
366
	 */
367
	public function createValidationError($text, $attributes = array())
368
	{
369
		return Element::create('div', $text, $attributes)->addClass('invalid-feedback');
370
	}
371
372
	/**
373
	 * Render an help text
374
	 *
375
	 * @param string $text
376
	 * @param array  $attributes
377
	 *
378
	 * @return Element
379
	 */
380
	public function createBlockHelp($text, $attributes = array())
381
	{
382
		return Element::create('div', $text, $attributes)->addClass('form-text');
383
	}
384
385
	/**
386
	 * Render a disabled field
387
	 *
388
	 * @param Field $field
389
	 *
390
	 * @return Element
391
	 */
392
	public function createDisabledField(Field $field)
393
	{
394
		return Element::create('span', $field->getValue(), $field->getAttributes());
395
	}
396
397
	/**
398
	 * Render a plain text field
399
	 *
400
	 * @param Field $field
401
	 *
402
	 * @return Element
403
	 */
404
	public function createPlainTextField(Field $field)
405
	{
406
		$label = $field->getLabel();
407
		if ($label) {
408
			$label->for('');
0 ignored issues
show
Bug introduced by
The method for cannot be called on $label (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
409
		}
410
411
		return Element::create('div', $field->getValue(), $field->getAttributes());
412
	}
413
414
	////////////////////////////////////////////////////////////////////
415
	//////////////////////////// WRAP BLOCKS ///////////////////////////
416
	////////////////////////////////////////////////////////////////////
417
418
	/**
419
	 * Wrap an item to be prepended or appended to the current field
420
	 *
421
	 * @return Element A wrapped item
422
	 */
423
	public function placeAround($item, $place = null)
424
	{
425
		// Render object
426
		if (is_object($item) and method_exists($item, '__toString')) {
427
			$item = $item->__toString();
428
		}
429
430
		$items = (array) $item;
431
		$element = '';
432
		foreach ($items as $item) {
433
			$hasButtonTag = strpos(ltrim($item), '<button') === 0;
434
435
			// Get class to use
436
			$class = $hasButtonTag ? '' : 'input-group-text';
437
438
			$element .= $hasButtonTag ? $item : Element::create('span', $item)->addClass($class);
439
		}
440
441
		return $element;
442
	}
443
444
	/**
445
	 * Wrap a field with prepended and appended items
446
	 *
447
	 * @param  Field $field
448
	 * @param  array $prepend
449
	 * @param  array $append
450
	 *
451
	 * @return string A field concatented with prepended and/or appended items
452
	 */
453
	public function prependAppend($field, $prepend, $append)
454
	{
455
		$return = '<div class="input-group">';
456
		$return .= join(null, $prepend);
457
		$return .= $field->render();
458
		$return .= join(null, $append);
459
		$return .= '</div>';
460
461
		return $return;
462
	}
463
464
	/**
465
	 * Wrap a field with potential additional tags
466
	 *
467
	 * @param  Field $field
468
	 *
469
	 * @return Element A wrapped field
470
	 */
471
	public function wrapField($field)
472
	{
473
		if ($this->app['former.form']->isOfType('horizontal')) {
474
			return Element::create('div', $field)->addClass($this->fieldWidth);
475
		}
476
477
		return $field;
478
	}
479
480
	/**
481
	 * Wrap actions block with potential additional tags
482
	 *
483
	 * @param  Actions $actions
484
	 *
485
	 * @return string A wrapped actions block
486
	 */
487
	public function wrapActions($actions)
488
	{
489
		// For horizontal forms, we wrap the actions in a div
490
		if ($this->app['former.form']->isOfType('horizontal')) {
491
			return Element::create('div', $actions)->addClass(array($this->fieldOffset, $this->fieldWidth));
0 ignored issues
show
Documentation introduced by
array($this->fieldOffset, $this->fieldWidth) is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a string.

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...
492
		}
493
494
		return $actions;
495
	}
496
}
497