Completed
Pull Request — master (#607)
by Tortue
01:41
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 .= " col-$viewports[$viewport]-offset-$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
	//////////////////////////// RENDER BLOCKS /////////////////////////
334
	////////////////////////////////////////////////////////////////////
335
336
	/**
337
	 * Render an help text
338
	 *
339
	 * @param string $text
340
	 * @param array  $attributes
341
	 *
342
	 * @return Element
343
	 */
344
	public function createHelp($text, $attributes = array())
345
	{
346
		return Element::create('small', $text, $attributes)->addClass('text-muted');
347
	}
348
349
	/**
350
	 * Render an validation error text
351
	 *
352
	 * @param string $text
353
	 * @param array  $attributes
354
	 *
355
	 * @return string
356
	 */
357
	public function createValidationError($text, $attributes = array())
358
	{
359
		return Element::create('div', $text, $attributes)->addClass('invalid-feedback');
360
	}
361
362
	/**
363
	 * Render an help text
364
	 *
365
	 * @param string $text
366
	 * @param array  $attributes
367
	 *
368
	 * @return Element
369
	 */
370
	public function createBlockHelp($text, $attributes = array())
371
	{
372
		return Element::create('small', $text, $attributes)->addClass('form-text text-muted');
373
	}
374
375
	/**
376
	 * Render a disabled field
377
	 *
378
	 * @param Field $field
379
	 *
380
	 * @return Element
381
	 */
382
	public function createDisabledField(Field $field)
383
	{
384
		return Element::create('span', $field->getValue(), $field->getAttributes());
385
	}
386
387
	/**
388
	 * Render a plain text field
389
	 *
390
	 * @param Field $field
391
	 *
392
	 * @return Element
393
	 */
394
	public function createPlainTextField(Field $field)
395
	{
396
		$label = $field->getLabel();
397
		if ($label) {
398
			$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...
399
		}
400
401
		return Element::create('div', $field->getValue(), $field->getAttributes());
402
	}
403
404
	////////////////////////////////////////////////////////////////////
405
	//////////////////////////// WRAP BLOCKS ///////////////////////////
406
	////////////////////////////////////////////////////////////////////
407
408
	/**
409
	 * Wrap an item to be prepended or appended to the current field
410
	 *
411
	 * @return Element A wrapped item
412
	 */
413
	public function placeAround($item, $place = null)
414
	{
415
		// Render object
416
		if (is_object($item) and method_exists($item, '__toString')) {
417
			$item = $item->__toString();
418
		}
419
420
		$items = (array) $item;
421
		$element = '';
422
		foreach ($items as $item) {
423
			$hasButtonTag = strpos(ltrim($item), '<button') === 0;
424
425
			// Get class to use
426
			$class = $hasButtonTag ? '' : 'input-group-text';
427
428
			$element .= $hasButtonTag ? $item : Element::create('span', $item)->addClass($class);
429
		}
430
431
		return $element;
432
	}
433
434
	/**
435
	 * Wrap a field with prepended and appended items
436
	 *
437
	 * @param  Field $field
438
	 * @param  array $prepend
439
	 * @param  array $append
440
	 *
441
	 * @return string A field concatented with prepended and/or appended items
442
	 */
443
	public function prependAppend($field, $prepend, $append)
444
	{
445
		$return = '<div class="input-group">';
446
		$return .= join(null, $prepend);
447
		$return .= $field->render();
448
		$return .= join(null, $append);
449
		$return .= '</div>';
450
451
		return $return;
452
	}
453
454
	/**
455
	 * Wrap a field with potential additional tags
456
	 *
457
	 * @param  Field $field
458
	 *
459
	 * @return Element A wrapped field
460
	 */
461
	public function wrapField($field)
462
	{
463
		if ($this->app['former.form']->isOfType('horizontal')) {
464
			return Element::create('div', $field)->addClass($this->fieldWidth);
465
		}
466
467
		return $field;
468
	}
469
470
	/**
471
	 * Wrap actions block with potential additional tags
472
	 *
473
	 * @param  Actions $actions
474
	 *
475
	 * @return string A wrapped actions block
476
	 */
477
	public function wrapActions($actions)
478
	{
479
		// For horizontal forms, we wrap the actions in a div
480
		if ($this->app['former.form']->isOfType('horizontal')) {
481
			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...
482
		}
483
484
		return $actions;
485
	}
486
}
487