Completed
Push — master ( 9e9cc4...48f3cc )
by Tortue
12s queued 11s
created

TwitterBootstrap4::getInlineLabelClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 1
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 TwitterBootstrap4 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 select fields
235
		// As text-type is open-ended we instead exclude those that shouldn't receive the class
236
		if (!$field->isCheckable() and !$field->isButton() and !in_array($field->getType(), array(
237
					'file',
238
					'plaintext',
239
				)) and !in_array('form-control', $classes)
240
		) {
241
			$classes[] = 'form-control';
242
		}
243
244
		if ($this->app['former']->getErrors($field->getName())) {
245
			$classes[] = $this->errorState();
246
		}
247
248
		return $this->addClassesToField($field, $classes);
249
	}
250
251
	/**
252
	 * Add group classes
253
	 *
254
	 * @return string A list of group classes
255
	 */
256
	public function getGroupClasses()
257
	{
258
		if ($this->app['former.form']->isOfType('horizontal')) {
259
			return 'form-group row';
260
		} else {
261
			return 'form-group';
262
		}
263
	}
264
265
	/**
266
	 * Add label classes
267
	 *
268
	 * @return string[] An array of attributes with the label class
269
	 */
270
	public function getLabelClasses()
271
	{
272
		if ($this->app['former.form']->isOfType('horizontal')) {
273
			return array('control-label', $this->labelWidth);
274
		} elseif ($this->app['former.form']->isOfType('inline')) {
275
			return array('sr-only');
276
		} else {
277
			return array('control-label');
278
		}
279
	}
280
281
	/**
282
	 * Add uneditable field classes
283
	 *
284
	 * @return string An array of attributes with the uneditable class
285
	 */
286
	public function getUneditableClasses()
287
	{
288
		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...
289
	}
290
291
	/**
292
	 * Add plain text field classes
293
	 *
294
	 * @return string An array of attributes with the plain text class
295
	 */
296
	public function getPlainTextClasses()
297
	{
298
		return 'form-control-static';
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 'form-control-static'; (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...
299
	}
300
301
	/**
302
	 * Add form class
303
	 *
304
	 * @param  string $type The type of form to add
305
	 *
306
	 * @return string|null
307
	 */
308
	public function getFormClasses($type)
309
	{
310
		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 310 which is incompatible with the return type declared by the interface Former\Interfaces\Framew...terface::getFormClasses of type array.
Loading history...
311
	}
312
313
	/**
314
	 * Add actions block class
315
	 *
316
	 * @return string|null
317
	 */
318
	public function getActionClasses()
319
	{
320
		if ($this->app['former.form']->isOfType('horizontal') || $this->app['former.form']->isOfType('inline')) {
321
			return 'form-group row';
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 'form-group 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...
322
		}
323
324
		return null;
325
	}
326
327
	////////////////////////////////////////////////////////////////////
328
	//////////////////////////// RENDER BLOCKS /////////////////////////
329
	////////////////////////////////////////////////////////////////////
330
331
	/**
332
	 * Render an help text
333
	 *
334
	 * @param string $text
335
	 * @param array  $attributes
336
	 *
337
	 * @return Element
338
	 */
339
	public function createHelp($text, $attributes = array())
340
	{
341
		return Element::create('small', $text, $attributes)->addClass('text-muted');
342
	}
343
344
	/**
345
	 * Render an validation error text
346
	 *
347
	 * @param string $text
348
	 * @param array  $attributes
349
	 *
350
	 * @return string
351
	 */
352
	public function createValidationError($text, $attributes = array())
353
	{
354
		return Element::create('div', $text, $attributes)->addClass('invalid-feedback');
355
	}
356
357
	/**
358
	 * Render an help text
359
	 *
360
	 * @param string $text
361
	 * @param array  $attributes
362
	 *
363
	 * @return Element
364
	 */
365
	public function createBlockHelp($text, $attributes = array())
366
	{
367
		return Element::create('small', $text, $attributes)->addClass('form-text text-muted');
368
	}
369
370
	/**
371
	 * Render a disabled field
372
	 *
373
	 * @param Field $field
374
	 *
375
	 * @return Element
376
	 */
377
	public function createDisabledField(Field $field)
378
	{
379
		return Element::create('span', $field->getValue(), $field->getAttributes());
380
	}
381
382
	/**
383
	 * Render a plain text field
384
	 *
385
	 * @param Field $field
386
	 *
387
	 * @return Element
388
	 */
389
	public function createPlainTextField(Field $field)
390
	{
391
		$label = $field->getLabel();
392
		if ($label) {
393
			$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...
394
		}
395
396
		return Element::create('div', $field->getValue(), $field->getAttributes());
397
	}
398
399
	////////////////////////////////////////////////////////////////////
400
	//////////////////////////// WRAP BLOCKS ///////////////////////////
401
	////////////////////////////////////////////////////////////////////
402
403
	/**
404
	 * Wrap an item to be prepended or appended to the current field
405
	 *
406
	 * @return Element A wrapped item
407
	 */
408
	public function placeAround($item, $place = null)
409
	{
410
		// Render object
411
		if (is_object($item) and method_exists($item, '__toString')) {
412
			$item = $item->__toString();
413
		}
414
415
		$items = (array) $item;
416
		$element = '';
417
		foreach ($items as $item) {
418
			$hasButtonTag = strpos(ltrim($item), '<button') === 0;
419
420
			// Get class to use
421
			$class = $hasButtonTag ? '' : 'input-group-text';
422
423
			$element .= $hasButtonTag ? $item : Element::create('span', $item)->addClass($class);
424
		}
425
426
		return Element::create('div', $element)->addClass('input-group-'.$place);
427
	}
428
429
	/**
430
	 * Wrap a field with prepended and appended items
431
	 *
432
	 * @param  Field $field
433
	 * @param  array $prepend
434
	 * @param  array $append
435
	 *
436
	 * @return string A field concatented with prepended and/or appended items
437
	 */
438
	public function prependAppend($field, $prepend, $append)
439
	{
440
		$return = '<div class="input-group">';
441
		$return .= join(null, $prepend);
442
		$return .= $field->render();
443
		$return .= join(null, $append);
444
		$return .= '</div>';
445
446
		return $return;
447
	}
448
449
	/**
450
	 * Wrap a field with potential additional tags
451
	 *
452
	 * @param  Field $field
453
	 *
454
	 * @return Element A wrapped field
455
	 */
456
	public function wrapField($field)
457
	{
458
		if ($this->app['former.form']->isOfType('horizontal')) {
459
			return Element::create('div', $field)->addClass($this->fieldWidth);
460
		}
461
462
		return $field;
463
	}
464
465
	/**
466
	 * Wrap actions block with potential additional tags
467
	 *
468
	 * @param  Actions $actions
469
	 *
470
	 * @return string A wrapped actions block
471
	 */
472
	public function wrapActions($actions)
473
	{
474
		// For horizontal forms, we wrap the actions in a div
475
		if ($this->app['former.form']->isOfType('horizontal')) {
476
			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...
477
		}
478
479
		return $actions;
480
	}
481
}
482