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

Group::wrapWithFloatingLabel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
namespace Former\Form;
3
4
use BadMethodCallException;
5
use Former\Helpers;
6
use HtmlObject\Element;
7
use HtmlObject\Traits\Tag;
8
use Illuminate\Container\Container;
9
use Illuminate\Support\Arr;
10
11
/**
12
 * Helper class to build groups
13
 */
14
class Group extends Tag
15
{
16
	/**
17
	 * The Container
18
	 *
19
	 * @var Container
20
	 */
21
	protected $app;
22
23
	/**
24
	 * The current state of the group
25
	 *
26
	 * @var string
27
	 */
28
	protected $state = null;
29
30
	/**
31
	 * Whether the field should be displayed raw or not
32
	 *
33
	 * @var boolean
34
	 */
35
	protected $raw = false;
36
37
	/**
38
	 * The group label
39
	 *
40
	 * @var Element
41
	 */
42
	protected $label;
43
44
	/**
45
	 * The group help
46
	 *
47
	 * @var array
48
	 */
49
	protected $help = array();
50
51
	/**
52
	 * An array of elements to preprend the field
53
	 *
54
	 * @var array
55
	 */
56
	protected $prepend = array();
57
58
	/**
59
	 * An array of elements to append the field
60
	 *
61
	 * @var array
62
	 */
63
	protected $append = array();
64
65
	/**
66
	 * The field validations to be checked for errors
67
	 *
68
	 * @var array
69
	 */
70
	protected $validations = array();
71
72
	/**
73
	 * The group's element
74
	 *
75
	 * @var string
76
	 */
77
	protected $element = 'div';
78
79
	/**
80
	 * Whether a custom group is opened or not
81
	 *
82
	 * @var boolean
83
	 */
84
	public static $opened = false;
85
86
	/**
87
	 * The custom group that is open
88
	 *
89
	 * @var Former\Form\Group
90
	 */
91
	public static $openGroup = null;
92
93
	////////////////////////////////////////////////////////////////////
94
	/////////////////////////// CORE METHODS ///////////////////////////
95
	////////////////////////////////////////////////////////////////////
96
97
	/**
98
	 * Creates a group
99
	 *
100
	 * @param string $label Its label
101
	 */
102
	public function __construct(Container $app, $label, $validations = null)
103
	{
104
		// Get special classes
105
		$this->app = $app;
106
		$this->addClass($this->app['former.framework']->getGroupClasses());
107
108
		// Invisible if Nude
109
		if ($this->app['former.framework']->is('Nude')) {
110
			$this->element = '';
111
		}
112
113
		// Set group label
114
		if ($label) {
115
			$this->setLabel($label);
116
		}
117
118
		// Set validations used to override groups own conclusions
119
		$this->validations = (array) $validations;
120
	}
121
122
	/**
123
	 * Prints out the opening of the Control Group
124
	 *
125
	 * @return string A control group opening tag
126
	 */
127
	public function __toString()
128
	{
129
		return $this->open().$this->getFormattedLabel();
130
	}
131
132
	/**
133
	 * Opens a group
134
	 *
135
	 * @return string Opening tag
136
	 */
137
	public function open()
138
	{
139
		if ($this->getErrors()) {
140
			$this->state($this->app['former.framework']->errorState());
141
		}
142
143
		// Retrieve state and append it to classes
144
		if ($this->state) {
145
			$this->addClass($this->state);
146
		}
147
148
		// Required state
149
		if ($this->app->bound('former.field') and $this->app['former.field']->isRequired()) {
150
			$this->addClass($this->app['former']->getOption('required_class'));
151
		}
152
153
		return parent::open();
154
	}
155
156
	/**
157
	 * Set the contents of the current group
158
	 *
159
	 * @param string $contents The group contents
160
	 *
161
	 * @return string A group
162
	 */
163
	public function contents($contents)
164
	{
165
		return $this->wrap($contents, $this->getFormattedLabel());
0 ignored issues
show
Documentation introduced by
$this->getFormattedLabel() is of type false|object<HtmlObject\Element>, but the function expects a string|null.

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...
166
	}
167
168
	/**
169
	 * Wrap a Field with the current group
170
	 *
171
	 * @param  \Former\Traits\Field $field A Field instance
172
	 *
173
	 * @return string        A group
174
	 */
175
	public function wrapField($field)
176
	{
177
		$label = $this->getLabel($field);
178
		$help = $this->getHelp();
179
		if ($field->isCheckable() &&
180
			$this->app['former']->framework() === 'TwitterBootstrap4'
181
		) {
182
			$wrapperClass = $field->isInline() ? 'form-check form-check-inline' : 'form-check';
0 ignored issues
show
Documentation Bug introduced by
The method isInline 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...
183
			if ($this->app['former']->getErrors($field->getName())) {
184
				$hiddenInput = Element::create('input', null, ['type' => 'hidden'])->class('form-check-input is-invalid');
185
				$help = $hiddenInput.$help;
186
			}
187
			$help = Element::create('div', $help)->class($wrapperClass);
188
		}
189
		$withFloatingLabel = $field->withFloatingLabel();
190
191
		$field = $this->prependAppend($field);
0 ignored issues
show
Documentation introduced by
$field is of type object<Former\Traits\Field>, but the function expects a object<Former\Form\Field>.

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...
192
		$field .= $help;
193
194
		if ($withFloatingLabel &&
195
			$this->app['former']->framework() === 'TwitterBootstrap5'
196
		) {
197
			return $this->wrapWithFloatingLabel($field, $label);
198
		}
199
200
		return $this->wrap($field, $label);
201
	}
202
203
	////////////////////////////////////////////////////////////////////
204
	//////////////////////////// FIELD METHODS /////////////////////////
205
	////////////////////////////////////////////////////////////////////
206
207
	/**
208
	 * Set the state of the group
209
	 *
210
	 * @param  string $state A Bootstrap state class
211
	 */
212
	public function state($state)
213
	{
214
		// Filter state
215
		$state = $this->app['former.framework']->filterState($state);
216
217
		$this->state = $state;
218
	}
219
220
	/**
221
	 * Set a class on the Group
222
	 *
223
	 * @param string $class The class(es) to add on the Group
224
	 */
225
	public function addGroupClass($class)
226
	{
227
		$this->addClass($class);
228
	}
229
230
	/**
231
	 * Remove one or more classes on the Group
232
	 *
233
	 * @param string $class The class(es) to remove on the Group
234
	 */
235
	public function removeGroupClass($class)
236
	{
237
		$this->removeClass($class);
238
	}
239
240
	/**
241
	 * Set a class on the Label
242
	 *
243
	 * @param string $class The class(es) to add on the Label
244
	 */
245
	public function addLabelClass($class)
246
	{
247
		// Don't add a label class if it isn't an Element instance
248
		if (!$this->label instanceof Element) {
249
			return $this;
250
		}
251
252
		$this->label->addClass($class);
253
254
		return $this;
255
	}
256
257
	/**
258
	 * Remove one or more classes on the Label
259
	 *
260
	 * @param string $class The class(es) to remove on the Label
261
	 */
262
	public function removeLabelClass($class)
263
	{
264
		// Don't remove a label class if it isn't an Element instance
265
		if (!$this->label instanceof Element) {
266
			return $this;
267
		}
268
269
		$this->label->removeClass($class);
270
271
		return $this;
272
	}
273
274
	/**
275
	 * Adds a label to the group
276
	 *
277
	 * @param  string $label A label
278
	 */
279
	public function setLabel($label)
280
	{
281
		if (!$label instanceof Element) {
282
			$label = Helpers::translate($label);
283
			$label = Element::create('label', $label)->for($label);
284
		}
285
286
		$this->label = $label;
287
	}
288
289
	/**
290
	 * Get the formatted group label
291
	 *
292
	 * @return string|null
293
	 */
294
	public function getFormattedLabel()
295
	{
296
		if (!$this->label) {
297
			return false;
298
		}
299
300
		return $this->label->addClass($this->app['former.framework']->getLabelClasses());
301
	}
302
303
	/**
304
	 * Disables the control group for the current field
305
	 */
306
	public function raw()
307
	{
308
		$this->raw = true;
309
	}
310
311
	/**
312
	 * Check if the current group is to be displayed or not
313
	 *
314
	 * @return boolean
315
	 */
316
	public function isRaw()
317
	{
318
		return (bool) $this->raw;
319
	}
320
321
	////////////////////////////////////////////////////////////////////
322
	///////////////////////////// HELP BLOCKS //////////////////////////
323
	////////////////////////////////////////////////////////////////////
324
325
	/**
326
	 * Alias for inlineHelp
327
	 *
328
	 * @param  string $help       The help text
329
	 * @param  array  $attributes Facultative attributes
330
	 */
331
	public function help($help, $attributes = array())
332
	{
333
		return $this->inlineHelp($help, $attributes);
334
	}
335
336
	/**
337
	 * Add an inline help
338
	 *
339
	 * @param  string $help       The help text
340
	 * @param  array  $attributes Facultative attributes
341
	 */
342
	public function inlineHelp($help, $attributes = array())
343
	{
344
		// If no help text, do nothing
345
		if (!$help) {
346
			return false;
347
		}
348
349
		$this->help['inline'] = $this->app['former.framework']->createHelp($help, $attributes);
350
	}
351
352
	/**
353
	 * Add an block help
354
	 *
355
	 * @param  string $help       The help text
356
	 * @param  array  $attributes Facultative attributes
357
	 */
358
	public function blockHelp($help, $attributes = array())
359
	{
360
		// Reserved method
361
		if ($this->app['former.framework']->isnt('TwitterBootstrap') &&
362
			$this->app['former.framework']->isnt('TwitterBootstrap3') &&
363
			$this->app['former.framework']->isnt('TwitterBootstrap4') &&
364
			$this->app['former.framework']->isnt('TwitterBootstrap5')
365
		) {
366
			throw new BadMethodCallException('This method is only available on the Bootstrap framework');
367
		}
368
369
		// If no help text, do nothing
370
		if (!$help) {
371
			return false;
372
		}
373
374
		$this->help['block'] = $this->app['former.framework']->createBlockHelp($help, $attributes);
375
	}
376
377
	////////////////////////////////////////////////////////////////////
378
	///////////////////////// PREPEND/APPEND METHODS ///////////////////
379
	////////////////////////////////////////////////////////////////////
380
381
	/**
382
	 * Prepend elements to the field
383
	 */
384
	public function prepend()
385
	{
386
		$this->placeAround(func_get_args(), 'prepend');
387
	}
388
389
	/**
390
	 * Append elements to the field
391
	 */
392
	public function append()
393
	{
394
		$this->placeAround(func_get_args(), 'append');
395
	}
396
397
	/**
398
	 * Prepends an icon to a field
399
	 *
400
	 * @param string $icon       The icon to prepend
401
	 * @param array  $attributes Its attributes
402
	 */
403
	public function prependIcon($icon, $attributes = array(), $iconSettings = array())
404
	{
405
		$icon = $this->app['former.framework']->createIcon($icon, $attributes, $iconSettings);
406
407
		$this->prepend($icon);
408
	}
409
410
	/**
411
	 * Append an icon to a field
412
	 *
413
	 * @param string $icon       The icon to prepend
414
	 * @param array  $attributes Its attributes
415
	 */
416
	public function appendIcon($icon, $attributes = array(), $iconSettings = array())
417
	{
418
		$icon = $this->app['former.framework']->createIcon($icon, $attributes, $iconSettings);
419
420
		$this->append($icon);
421
	}
422
423
	////////////////////////////////////////////////////////////////////
424
	//////////////////////////////// HELPERS ///////////////////////////
425
	////////////////////////////////////////////////////////////////////
426
427
	/**
428
	 * Get the errors for the group
429
	 *
430
	 * @return string
431
	 */
432
	public function getErrors()
433
	{
434
		$errors = '';
435
436
		if (!self::$opened) {
437
438
			// for non-custom groups, normal error handling applies
439
			$errors = $this->app['former']->getErrors();
440
		} elseif (!empty($this->validations)) {
441
442
			// error handling only when validations specified for custom groups
443
			foreach ($this->validations as $validation) {
444
				$errors .= $this->app['former']->getErrors($validation);
445
			}
446
		}
447
448
		return $errors;
449
	}
450
451
	/**
452
	 * Wraps content in a group
453
	 *
454
	 * @param string $contents The content
455
	 * @param string $label    The label to add
456
	 *
457
	 * @return string A group
458
	 */
459
	public function wrap($contents, $label = null)
460
	{
461
		$group = $this->open();
462
		$group .= $label;
463
		$group .= $this->app['former.framework']->wrapField($contents);
464
		$group .= $this->close();
465
466
		return $group;
467
	}
468
469
	/**
470
	 * Wraps content in a group with floating label
471
	 *
472
	 * @param string $contents The content
473
	 * @param string $label    The label to add
474
	 *
475
	 * @return string A group
476
	 */
477
	public function wrapWithFloatingLabel($contents, $label = null)
478
	{
479
		$floatingLabelClass = $this->app['former.framework']->getFloatingLabelClass();
480
		if ($floatingLabelClass) {
481
			$this->addClass($floatingLabelClass);
482
		}
483
		return $this->wrap($label, $contents);
484
	}
485
486
	/**
487
	 * Prints out the current label
488
	 *
489
	 * @param  string $field The field to create a label for
490
	 *
491
	 * @return string        A <label> tag
492
	 */
493
	 protected function getLabel($field = null)
494
 	{
495
 		// Don't create a label if none exist
496
 		if (!$field or !$this->label) {
497
 			return null;
498
 		}
499
500
 		// Wrap label in framework classes
501
 		$labelClasses = $this->app['former.framework']->getLabelClasses();
502
 		if ($field->isCheckable() &&
0 ignored issues
show
Bug introduced by
The method isCheckable cannot be called on $field (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...
503
 			in_array($this->app['former']->framework(), ['TwitterBootstrap4', 'TwitterBootstrap5']) &&
504
 			$this->app['former.form']->isOfType('horizontal')
505
 		) {
506
 			$labelClasses = array_merge($labelClasses, array('pt-0'));
507
 		}
508
 		$this->label->addClass($labelClasses);
509
 		$this->label = $this->app['former.framework']->createLabelOf($field, $this->label);
510
 		$this->label = $this->app['former.framework']->wrapLabel($this->label);
511
512
 		return $this->label;
513
 	}
514
515
	/**
516
	 * Prints out the current help
517
	 *
518
	 * @return string A .help-block or .help-inline
519
	 */
520
	protected function getHelp()
521
	{
522
		$inline = Arr::get($this->help, 'inline');
523
		$block  = Arr::get($this->help, 'block');
524
525
		// Replace help text with error if any found
526
		$errors = $this->app['former']->getErrors();
527
		if ($errors and $this->app['former']->getOption('error_messages')) {
528
			$inline = $this->app['former.framework']->createValidationError($errors);
529
		}
530
531
		return join(null, array($inline, $block));
532
	}
533
534
	/**
535
	 * Format the field with prepended/appended elements
536
	 *
537
	 * @param  Field $field The field to format
538
	 *
539
	 * @return string        Field plus supplementary elements
540
	 */
541
	protected function prependAppend($field)
542
	{
543
		if (!$this->prepend and !$this->append) {
544
			return $field->render();
545
		}
546
547
		return $this->app['former.framework']->prependAppend($field, $this->prepend, $this->append);
548
	}
549
550
	/**
551
	 * Place elements around the field
552
	 *
553
	 * @param  array  $items An array of items to place
554
	 * @param  string $place Where they should end up (prepend|append)
555
	 */
556
	protected function placeAround($items, $place)
557
	{
558
		// Iterate over the items and place them where they should
559
		foreach ((array) $items as $item) {
560
			$item             = $this->app['former.framework']->placeAround($item, $place);
561
			$this->{$place}[] = $item;
562
		}
563
	}
564
}
565