Completed
Push — master ( 13a7d4...45d37a )
by Tortue
14s queued 12s
created

Group::help()   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 2
1
<?php
2
namespace Former\Form;
3
4
use BadMethodCallException;
5
use Former\Helpers;
6
use HtmlObject\Input;
7
use HtmlObject\Element;
8
use HtmlObject\Traits\Tag;
9
use Illuminate\Container\Container;
10
use Illuminate\Support\Arr;
11
12
/**
13
 * Helper class to build groups
14
 */
15
class Group extends Tag
16
{
17
	/**
18
	 * The Container
19
	 *
20
	 * @var Container
21
	 */
22
	protected $app;
23
24
	/**
25
	 * The current state of the group
26
	 *
27
	 * @var string
28
	 */
29
	protected $state = null;
30
31
	/**
32
	 * Whether the field should be displayed raw or not
33
	 *
34
	 * @var boolean
35
	 */
36
	protected $raw = false;
37
38
	/**
39
	 * The group label
40
	 *
41
	 * @var Element
42
	 */
43
	protected $label;
44
45
	/**
46
	 * The group help
47
	 *
48
	 * @var array
49
	 */
50
	protected $help = array();
51
52
	/**
53
	 * An array of elements to preprend the field
54
	 *
55
	 * @var array
56
	 */
57
	protected $prepend = array();
58
59
	/**
60
	 * An array of elements to append the field
61
	 *
62
	 * @var array
63
	 */
64
	protected $append = array();
65
66
	/**
67
	 * The field validations to be checked for errors
68
	 *
69
	 * @var array
70
	 */
71
	protected $validations = array();
72
73
	/**
74
	 * The group's element
75
	 *
76
	 * @var string
77
	 */
78
	protected $element = 'div';
79
80
	/**
81
	 * Whether a custom group is opened or not
82
	 *
83
	 * @var boolean
84
	 */
85
	public static $opened = false;
86
87
	/**
88
	 * The custom group that is open
89
	 *
90
	 * @var Former\Form\Group
91
	 */
92
	public static $openGroup = null;
93
94
	////////////////////////////////////////////////////////////////////
95
	/////////////////////////// CORE METHODS ///////////////////////////
96
	////////////////////////////////////////////////////////////////////
97
98
	/**
99
	 * Creates a group
100
	 *
101
	 * @param string $label Its label
102
	 */
103
	public function __construct(Container $app, $label, $validations = null)
104
	{
105
		// Get special classes
106
		$this->app = $app;
107
		$this->addClass($this->app['former.framework']->getGroupClasses());
108
109
		// Invisible if Nude
110
		if ($this->app['former.framework']->is('Nude')) {
111
			$this->element = '';
112
		}
113
114
		// Set group label
115
		if ($label) {
116
			$this->setLabel($label);
117
		}
118
119
		// Set validations used to override groups own conclusions
120
		$this->validations = (array) $validations;
121
	}
122
123
	/**
124
	 * Prints out the opening of the Control Group
125
	 *
126
	 * @return string A control group opening tag
127
	 */
128
	public function __toString()
129
	{
130
		return $this->open().$this->getFormattedLabel();
131
	}
132
133
	/**
134
	 * Opens a group
135
	 *
136
	 * @return string Opening tag
137
	 */
138
	public function open()
139
	{
140
		if ($this->getErrors()) {
141
			$this->state($this->app['former.framework']->errorState());
142
		}
143
144
		// Retrieve state and append it to classes
145
		if ($this->state) {
146
			$this->addClass($this->state);
147
		}
148
149
		// Required state
150
		if ($this->app->bound('former.field') and $this->app['former.field']->isRequired()) {
151
			$this->addClass($this->app['former']->getOption('required_class'));
152
		}
153
154
		return parent::open();
155
	}
156
157
	/**
158
	 * Set the contents of the current group
159
	 *
160
	 * @param string $contents The group contents
161
	 *
162
	 * @return string A group
163
	 */
164
	public function contents($contents)
165
	{
166
		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...
167
	}
168
169
	/**
170
	 * Wrap a Field with the current group
171
	 *
172
	 * @param  \Former\Traits\Field $field A Field instance
173
	 *
174
	 * @return string        A group
175
	 */
176
	public function wrapField($field)
177
	{
178
		$label = $this->getLabel($field);
179
		$help = $this->getHelp();
180
		if ($field->isCheckable() &&
181
			in_array($this->app['former']->framework(), ['TwitterBootstrap4', 'TwitterBootstrap5'])
182
		) {
183
			$wrapperClass = null;
184
			if ($this->app['former']->framework() === 'TwitterBootstrap4') {
185
				$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...
186
			}
187
			if ($this->app['former']->getErrors($field->getName())) {
188
				$hiddenInput = Input::create('hidden')->addClass('form-check-input is-invalid');
189
				$help = $hiddenInput.$help;
190
			}
191
			$help = $help ? Element::create('div', $help)->addClass($wrapperClass) : '';
192
		}
193
		$withFloatingLabel = $field->withFloatingLabel();
194
195
		$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...
196
		$field .= $help;
197
198
		if ($withFloatingLabel &&
199
			$this->app['former']->framework() === 'TwitterBootstrap5'
200
		) {
201
			return $this->wrapWithFloatingLabel($field, $label);
202
		}
203
204
		return $this->wrap($field, $label);
205
	}
206
207
	////////////////////////////////////////////////////////////////////
208
	//////////////////////////// FIELD METHODS /////////////////////////
209
	////////////////////////////////////////////////////////////////////
210
211
	/**
212
	 * Set the state of the group
213
	 *
214
	 * @param  string $state A Bootstrap state class
215
	 */
216
	public function state($state)
217
	{
218
		// Filter state
219
		$state = $this->app['former.framework']->filterState($state);
220
221
		$this->state = $state;
222
	}
223
224
	/**
225
	 * Set a class on the Group
226
	 *
227
	 * @param string $class The class(es) to add on the Group
228
	 */
229
	public function addGroupClass($class)
230
	{
231
		$this->addClass($class);
232
	}
233
234
	/**
235
	 * Remove one or more classes on the Group
236
	 *
237
	 * @param string $class The class(es) to remove on the Group
238
	 */
239
	public function removeGroupClass($class)
240
	{
241
		$this->removeClass($class);
242
	}
243
244
	/**
245
	 * Set a class on the Label
246
	 *
247
	 * @param string $class The class(es) to add on the Label
248
	 */
249
	public function addLabelClass($class)
250
	{
251
		// Don't add a label class if it isn't an Element instance
252
		if (!$this->label instanceof Element) {
253
			return $this;
254
		}
255
256
		$this->label->addClass($class);
257
258
		return $this;
259
	}
260
261
	/**
262
	 * Remove one or more classes on the Label
263
	 *
264
	 * @param string $class The class(es) to remove on the Label
265
	 */
266
	public function removeLabelClass($class)
267
	{
268
		// Don't remove a label class if it isn't an Element instance
269
		if (!$this->label instanceof Element) {
270
			return $this;
271
		}
272
273
		$this->label->removeClass($class);
274
275
		return $this;
276
	}
277
278
	/**
279
	 * Adds a label to the group
280
	 *
281
	 * @param  string $label A label
282
	 */
283
	public function setLabel($label)
284
	{
285
		if (!$label instanceof Element) {
286
			$label = Helpers::translate($label);
287
			$label = Element::create('label', $label)->for($label);
288
		}
289
290
		$this->label = $label;
291
	}
292
293
	/**
294
	 * Get the formatted group label
295
	 *
296
	 * @return string|null
297
	 */
298
	public function getFormattedLabel()
299
	{
300
		if (!$this->label) {
301
			return false;
302
		}
303
304
		return $this->label->addClass($this->app['former.framework']->getLabelClasses());
305
	}
306
307
	/**
308
	 * Disables the control group for the current field
309
	 */
310
	public function raw()
311
	{
312
		$this->raw = true;
313
	}
314
315
	/**
316
	 * Check if the current group is to be displayed or not
317
	 *
318
	 * @return boolean
319
	 */
320
	public function isRaw()
321
	{
322
		return (bool) $this->raw;
323
	}
324
325
	////////////////////////////////////////////////////////////////////
326
	///////////////////////////// HELP BLOCKS //////////////////////////
327
	////////////////////////////////////////////////////////////////////
328
329
	/**
330
	 * Alias for inlineHelp
331
	 *
332
	 * @param  string $help       The help text
333
	 * @param  array  $attributes Facultative attributes
334
	 */
335
	public function help($help, $attributes = array())
336
	{
337
		return $this->inlineHelp($help, $attributes);
338
	}
339
340
	/**
341
	 * Add an inline help
342
	 *
343
	 * @param  string $help       The help text
344
	 * @param  array  $attributes Facultative attributes
345
	 */
346
	public function inlineHelp($help, $attributes = array())
347
	{
348
		// If no help text, do nothing
349
		if (!$help) {
350
			return false;
351
		}
352
353
		$this->help['inline'] = $this->app['former.framework']->createHelp($help, $attributes);
354
	}
355
356
	/**
357
	 * Add an block help
358
	 *
359
	 * @param  string $help       The help text
360
	 * @param  array  $attributes Facultative attributes
361
	 */
362
	public function blockHelp($help, $attributes = array())
363
	{
364
		// Reserved method
365
		if ($this->app['former.framework']->isnt('TwitterBootstrap') &&
366
			$this->app['former.framework']->isnt('TwitterBootstrap3') &&
367
			$this->app['former.framework']->isnt('TwitterBootstrap4') &&
368
			$this->app['former.framework']->isnt('TwitterBootstrap5')
369
		) {
370
			throw new BadMethodCallException('This method is only available on the Bootstrap framework');
371
		}
372
373
		// If no help text, do nothing
374
		if (!$help) {
375
			return false;
376
		}
377
378
		$this->help['block'] = $this->app['former.framework']->createBlockHelp($help, $attributes);
379
	}
380
381
	////////////////////////////////////////////////////////////////////
382
	///////////////////////// PREPEND/APPEND METHODS ///////////////////
383
	////////////////////////////////////////////////////////////////////
384
385
	/**
386
	 * Prepend elements to the field
387
	 */
388
	public function prepend()
389
	{
390
		$this->placeAround(func_get_args(), 'prepend');
391
	}
392
393
	/**
394
	 * Append elements to the field
395
	 */
396
	public function append()
397
	{
398
		$this->placeAround(func_get_args(), 'append');
399
	}
400
401
	/**
402
	 * Prepends an icon to a field
403
	 *
404
	 * @param string $icon       The icon to prepend
405
	 * @param array  $attributes Its attributes
406
	 */
407
	public function prependIcon($icon, $attributes = array(), $iconSettings = array())
408
	{
409
		$icon = $this->app['former.framework']->createIcon($icon, $attributes, $iconSettings);
410
411
		$this->prepend($icon);
412
	}
413
414
	/**
415
	 * Append an icon to a field
416
	 *
417
	 * @param string $icon       The icon to prepend
418
	 * @param array  $attributes Its attributes
419
	 */
420
	public function appendIcon($icon, $attributes = array(), $iconSettings = array())
421
	{
422
		$icon = $this->app['former.framework']->createIcon($icon, $attributes, $iconSettings);
423
424
		$this->append($icon);
425
	}
426
427
	////////////////////////////////////////////////////////////////////
428
	//////////////////////////////// HELPERS ///////////////////////////
429
	////////////////////////////////////////////////////////////////////
430
431
	/**
432
	 * Get the errors for the group
433
	 *
434
	 * @return string
435
	 */
436
	public function getErrors()
437
	{
438
		$errors = '';
439
440
		if (!self::$opened) {
441
442
			// for non-custom groups, normal error handling applies
443
			$errors = $this->app['former']->getErrors();
444
		} elseif (!empty($this->validations)) {
445
446
			// error handling only when validations specified for custom groups
447
			foreach ($this->validations as $validation) {
448
				$errors .= $this->app['former']->getErrors($validation);
449
			}
450
		}
451
452
		return $errors;
453
	}
454
455
	/**
456
	 * Wraps content in a group
457
	 *
458
	 * @param string $contents The content
459
	 * @param string $label    The label to add
460
	 *
461
	 * @return string A group
462
	 */
463
	public function wrap($contents, $label = null)
464
	{
465
		$group = $this->open();
466
		$group .= $label;
467
		$group .= $this->app['former.framework']->wrapField($contents);
468
		$group .= $this->close();
469
470
		return $group;
471
	}
472
473
	/**
474
	 * Wraps content in a group with floating label
475
	 *
476
	 * @param string $contents The content
477
	 * @param string $label    The label to add
478
	 *
479
	 * @return string A group
480
	 */
481
	public function wrapWithFloatingLabel($contents, $label = null)
482
	{
483
		$floatingLabelClass = $this->app['former.framework']->getFloatingLabelClass();
484
		if ($floatingLabelClass) {
485
			$this->addClass($floatingLabelClass);
486
		}
487
		return $this->wrap($label, $contents);
488
	}
489
490
	/**
491
	 * Prints out the current label
492
	 *
493
	 * @param  string $field The field to create a label for
494
	 *
495
	 * @return string        A <label> tag
496
	 */
497
	 protected function getLabel($field = null)
498
 	{
499
 		// Don't create a label if none exist
500
 		if (!$field or !$this->label) {
501
 			return null;
502
 		}
503
504
 		// Wrap label in framework classes
505
 		$labelClasses = $this->app['former.framework']->getLabelClasses();
506
 		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...
507
 			in_array($this->app['former']->framework(), ['TwitterBootstrap4', 'TwitterBootstrap5']) &&
508
 			$this->app['former.form']->isOfType('horizontal')
509
 		) {
510
 			$labelClasses = array_merge($labelClasses, array('pt-0'));
511
 		}
512
 		$this->label->addClass($labelClasses);
513
 		$this->label = $this->app['former.framework']->createLabelOf($field, $this->label);
514
 		$this->label = $this->app['former.framework']->wrapLabel($this->label);
515
516
 		return $this->label;
517
 	}
518
519
	/**
520
	 * Prints out the current help
521
	 *
522
	 * @return string A .help-block or .help-inline
523
	 */
524
	protected function getHelp()
525
	{
526
		$inline = Arr::get($this->help, 'inline');
527
		$block  = Arr::get($this->help, 'block');
528
529
		// Replace help text with error if any found
530
		$errors = $this->app['former']->getErrors();
531
		if ($errors and $this->app['former']->getOption('error_messages')) {
532
			$inline = $this->app['former.framework']->createValidationError($errors);
533
		}
534
535
		return join(null, array($inline, $block));
536
	}
537
538
	/**
539
	 * Format the field with prepended/appended elements
540
	 *
541
	 * @param  Field $field The field to format
542
	 *
543
	 * @return string        Field plus supplementary elements
544
	 */
545
	protected function prependAppend($field)
546
	{
547
		if (!$this->prepend and !$this->append) {
548
			return $field->render();
549
		}
550
551
		return $this->app['former.framework']->prependAppend($field, $this->prepend, $this->append);
552
	}
553
554
	/**
555
	 * Place elements around the field
556
	 *
557
	 * @param  array  $items An array of items to place
558
	 * @param  string $place Where they should end up (prepend|append)
559
	 */
560
	protected function placeAround($items, $place)
561
	{
562
		// Iterate over the items and place them where they should
563
		foreach ((array) $items as $item) {
564
			$item             = $this->app['former.framework']->placeAround($item, $place);
565
			$this->{$place}[] = $item;
566
		}
567
	}
568
}
569