Completed
Push — master ( 48f3cc...264ea0 )
by Tortue
15s queued 13s
created
src/Former/Traits/Checkable.php 2 patches
Indentation   +567 added lines, -567 removed lines patch added patch discarded remove patch
@@ -14,571 +14,571 @@
 block discarded – undo
14 14
  */
15 15
 abstract class Checkable extends Field
16 16
 {
17
-	/**
18
-	 * Renders the checkables as inline
19
-	 *
20
-	 * @var boolean
21
-	 */
22
-	protected $inline = false;
23
-
24
-	/**
25
-	 * Add a text to a single element
26
-	 *
27
-	 * @var string
28
-	 */
29
-	protected $text = null;
30
-
31
-	/**
32
-	 * Renders the checkables as grouped
33
-	 *
34
-	 * @var boolean
35
-	 */
36
-	protected $grouped = false;
37
-
38
-	/**
39
-	 * The checkable items currently stored
40
-	 *
41
-	 * @var array
42
-	 */
43
-	protected $items = array();
44
-
45
-	/**
46
-	 * The type of checkable item
47
-	 *
48
-	 * @var string
49
-	 */
50
-	protected $checkable = null;
51
-
52
-	/**
53
-	 * An array of checked items
54
-	 *
55
-	 * @var array
56
-	 */
57
-	protected $checked = array();
58
-
59
-	/**
60
-	 * The checkable currently being focused on
61
-	 *
62
-	 * @var integer
63
-	 */
64
-	protected $focus = null;
65
-
66
-	/**
67
-	 * Whether this particular checkable is to be pushed
68
-	 *
69
-	 * @var boolean
70
-	 */
71
-	protected $isPushed = null;
72
-
73
-	////////////////////////////////////////////////////////////////////
74
-	//////////////////////////// CORE METHODS //////////////////////////
75
-	////////////////////////////////////////////////////////////////////
76
-
77
-	/**
78
-	 * Build a new checkable
79
-	 *
80
-	 * @param Container $app
81
-	 * @param string    $type
82
-	 * @param array     $name
83
-	 * @param           $label
84
-	 * @param           $value
85
-	 * @param           $attributes
86
-	 */
87
-	public function __construct(Container $app, $type, $name, $label, $value, $attributes)
88
-	{
89
-		// Unify auto and chained methods of grouping checkboxes
90
-		if (Str::endsWith($name, '[]')) {
91
-			$name = substr($name, 0, -2);
92
-			$this->grouped();
93
-		}
94
-		parent::__construct($app, $type, $name, $label, $value, $attributes);
95
-
96
-		if (is_array($this->value)) {
97
-			$this->items($this->value);
98
-		}
99
-	}
100
-
101
-	/**
102
-	 * Apply methods to focused checkable
103
-	 *
104
-	 * @param string $method
105
-	 * @param array  $parameters
106
-	 *
107
-	 * @return $this
108
-	 */
109
-	public function __call($method, $parameters)
110
-	{
111
-		$focused = $this->setOnFocused('attributes.'.$method, Arr::get($parameters, 0));
112
-		if ($focused) {
113
-			return $this;
114
-		}
115
-
116
-		return parent::__call($method, $parameters);
117
-	}
118
-
119
-	/**
120
-	 * Prints out the currently stored checkables
121
-	 */
122
-	public function render()
123
-	{
124
-		$html = null;
125
-
126
-		$this->setFieldClasses();
127
-
128
-		// Multiple items
129
-		if ($this->items) {
130
-			unset($this->app['former']->labels[array_search($this->name, $this->app['former']->labels)]);
131
-			foreach ($this->items as $key => $item) {
132
-				$value = $this->isCheckbox() && !$this->isGrouped() ? 1 : $key;
133
-				$html .= $this->createCheckable($item, $value);
134
-			}
135
-
136
-			return $html;
137
-		}
138
-
139
-		// Single item
140
-		return $this->createCheckable(array(
141
-			'name'  => $this->name,
142
-			'label' => $this->text,
143
-			'value' => $this->value,
144
-			'attributes' => $this->attributes,
145
-		));
146
-	}
147
-
148
-	////////////////////////////////////////////////////////////////////
149
-	////////////////////////// FIELD METHODS ///////////////////////////
150
-	////////////////////////////////////////////////////////////////////
151
-
152
-	/**
153
-	 * Focus on a particular checkable
154
-	 *
155
-	 * @param integer $on The checkable to focus on
156
-	 *
157
-	 * @return $this
158
-	 */
159
-	public function on($on)
160
-	{
161
-		if (!isset($this->items[$on])) {
162
-			return $this;
163
-		} else {
164
-			$this->focus = $on;
165
-		}
166
-
167
-		return $this;
168
-	}
169
-
170
-	/**
171
-	 * Set the checkables as inline
172
-	 */
173
-	public function inline($isInline = true)
174
-	{
175
-		$this->inline = $isInline;
176
-
177
-		return $this;
178
-	}
179
-
180
-	/**
181
-	 * Set the checkables as stacked
182
-	 */
183
-	public function stacked($isStacked = true)
184
-	{
185
-		$this->inline = !$isStacked;
186
-
187
-		return $this;
188
-	}
189
-
190
-	/**
191
-	 * Set the checkables as grouped
192
-	 */
193
-	public function grouped($isGrouped = true)
194
-	{
195
-		$this->grouped = $isGrouped;
196
-
197
-		return $this;
198
-	}
199
-
200
-	/**
201
-	 * Add text to a single checkable
202
-	 *
203
-	 * @param  string $text The checkable label
204
-	 *
205
-	 * @return $this
206
-	 */
207
-	public function text($text)
208
-	{
209
-		// Translate and format
210
-		$text = Helpers::translate($text);
211
-
212
-		// Apply on focused if any
213
-		$focused = $this->setOnFocused('label', $text);
214
-		if ($focused) {
215
-			return $this;
216
-		}
217
-
218
-		$this->text = $text;
219
-
220
-		return $this;
221
-	}
222
-
223
-	/**
224
-	 * Push this particular checkbox
225
-	 *
226
-	 * @param boolean $pushed
227
-	 *
228
-	 * @return $this
229
-	 */
230
-	public function push($pushed = true)
231
-	{
232
-		$this->isPushed = $pushed;
233
-
234
-		return $this;
235
-	}
236
-
237
-	/**
238
-	 * Check a specific item
239
-	 *
240
-	 * @param bool|string $checked The checkable to check, or an array of checked items
241
-	 *
242
-	 * @return $this
243
-	 */
244
-	public function check($checked = true)
245
-	{
246
-		// If we're setting all the checked items at once
247
-		if (is_array($checked)) {
248
-			$this->checked = $checked;
249
-			// Checking an item in particular
250
-		} elseif (is_string($checked) or is_int($checked)) {
251
-			$this->checked[$checked] = true;
252
-			// Only setting a single item
253
-		} else {
254
-			$this->checked[$this->name] = (bool) $checked;
255
-		}
256
-
257
-		return $this;
258
-	}
259
-
260
-
261
-	/**
262
-	 * Check if the checkables are inline
263
-	 *
264
-	 * @return boolean
265
-	 */
266
-	public function isInline()
267
-	{
268
-		return $this->inline;
269
-	}
270
-
271
-	////////////////////////////////////////////////////////////////////
272
-	////////////////////////// INTERNAL METHODS ////////////////////////
273
-	////////////////////////////////////////////////////////////////////
274
-
275
-	/**
276
-	 * Creates a series of checkable items
277
-	 *
278
-	 * @param array $_items Items to create
279
-	 */
280
-	protected function items($_items)
281
-	{
282
-		// If passing an array
283
-		if (sizeof($_items) == 1 and
284
-			isset($_items[0]) and
285
-			is_array($_items[0])
286
-		) {
287
-			$_items = $_items[0];
288
-		}
289
-
290
-		// Fetch models if that's what we were passed
291
-		if (isset($_items[0]) and is_object($_items[0])) {
292
-			$_items = Helpers::queryToArray($_items);
293
-			$_items = array_flip($_items);
294
-		}
295
-
296
-		// Iterate through items, assign a name and a label to each
297
-		$count = 0;
298
-		foreach ($_items as $label => $name) {
299
-
300
-			// Define a fallback name in case none is found
301
-			$fallback = $this->isCheckbox()
302
-				? $this->name.'_'.$count
303
-				: $this->name;
304
-
305
-			// Grouped fields
306
-			if ($this->isGrouped()) {
307
-				$attributes['id'] = str_replace('[]', null, $fallback);
308
-				$fallback         = str_replace('[]', null, $this->name).'[]';
309
-			}
310
-
311
-			// If we haven't any name defined for the checkable, try to compute some
312
-			if (!is_string($label) and !is_array($name)) {
313
-				$label = $name;
314
-				$name  = $fallback;
315
-			}
316
-
317
-			// If we gave custom information on the item, add them
318
-			if (is_array($name)) {
319
-				$attributes = $name;
320
-				$name       = Arr::get($attributes, 'name', $fallback);
321
-				unset($attributes['name']);
322
-			}
323
-
324
-			// Store all informations we have in an array
325
-			$item = array(
326
-				'name'  => $name,
327
-				'label' => Helpers::translate($label),
328
-				'count' => $count,
329
-			);
330
-			if (isset($attributes)) {
331
-				$item['attributes'] = $attributes;
332
-			}
333
-
334
-			$this->items[] = $item;
335
-			$count++;
336
-		}
337
-	}
338
-
339
-	/**
340
-	 * Renders a checkable
341
-	 *
342
-	 * @param string|array $item          A checkable item
343
-	 * @param integer      $fallbackValue A fallback value if none is set
344
-	 *
345
-	 * @return string
346
-	 */
347
-	protected function createCheckable($item, $fallbackValue = 1)
348
-	{
349
-		// Extract informations
350
-		extract($item);
351
-
352
-		// Set default values
353
-		if (!isset($attributes)) {
354
-			$attributes = array();
355
-		}
356
-		if (isset($attributes['value'])) {
357
-			$value = $attributes['value'];
358
-		}
359
-		if (!isset($value) or $value === $this->app['former']->getOption('unchecked_value')) {
360
-			$value = $fallbackValue;
361
-		}
362
-
363
-		// If inline items, add class
364
-		$isInline = $this->inline ? ' '.$this->app['former.framework']->getInlineLabelClass($this) : null;
365
-
366
-		// In Bootsrap 3 or 4, don't append the the checkable type (radio/checkbox) as a class if
367
-		// rendering inline.
368
-		$class =  ($this->app['former']->framework() == 'TwitterBootstrap3' ||
369
-			$this->app['former']->framework() == 'TwitterBootstrap4') ? trim($isInline) : $this->checkable.$isInline;
370
-
371
-		// Merge custom attributes with global attributes
372
-		$attributes = array_merge($this->attributes, $attributes);
373
-		if (!isset($attributes['id'])) {
374
-			$attributes['id'] = $name.$this->unique($name);
375
-		}
376
-
377
-		// Create field
378
-		$field = Input::create($this->checkable, $name, Helpers::encode($value), $attributes);
379
-		if ($this->isChecked($item, $value)) {
380
-			$field->checked('checked');
381
-		}
382
-
383
-		// Add hidden checkbox if requested
384
-		if ($this->isOfType('checkbox', 'checkboxes')) {
385
-			if ($this->isPushed or ($this->app['former']->getOption('push_checkboxes') and $this->isPushed !== false)) {
386
-				$field = $this->app['former']->hidden($name)->forceValue($this->app['former']->getOption('unchecked_value')).$field->render();
387
-
388
-				// app['former.field'] was overwritten by Former::hidden() call in the line above, so here
389
-				// we reset it to $this to enable $this->app['former']->getErrors() to retrieve the correct object
390
-				$this->app->instance('former.field', $this);
391
-			}
392
-		}
393
-
394
-		// If no label to wrap, return plain checkable
395
-		if (!$label) {
396
-			$element = (is_object($field)) ? $field->render() : $field;
397
-		} elseif ($this->app['former']->framework() == 'TwitterBootstrap4') {
398
-			// Revised for Bootstrap 4, move the 'input' outside of the 'label'
399
-			$labelClass = 'form-check-label';
400
-			$element = $field . Element::create('label', $label)->for($attributes['id'])->class($labelClass)->render();
401
-
402
-			$wrapper_class = $this->inline ? 'form-check form-check-inline' : 'form-check';
403
-
404
-			$element = Element::create('div', $element)->class($wrapper_class)->render();
405
-		} else {
406
-			// Original way is to add the 'input' inside the 'label'
407
-			$element = Element::create('label', $field.$label)->for($attributes['id'])->class($class)->render();
408
-		}
409
-
410
-		// If BS3, if checkables are stacked, wrap them in a div with the checkable type
411
-		if (!$isInline && $this->app['former']->framework() == 'TwitterBootstrap3') {
412
-			$wrapper = Element::create('div', $element)->class($this->checkable);
413
-			if ($this->getAttribute('disabled')) {
414
-				$wrapper->addClass('disabled');
415
-			}
416
-			$element = $wrapper->render();
417
-		}
418
-
419
-		// Return the field
420
-		return $element;
421
-	}
422
-
423
-	////////////////////////////////////////////////////////////////////
424
-	///////////////////////////// HELPERS //////////////////////////////
425
-	////////////////////////////////////////////////////////////////////
426
-
427
-	/**
428
-	 * Generate an unique ID for a field
429
-	 *
430
-	 * @param string $name The field's name
431
-	 *
432
-	 * @return string A field number to use
433
-	 */
434
-	protected function unique($name)
435
-	{
436
-		$this->app['former']->labels[] = $name;
437
-
438
-		// Count number of fields with the same ID
439
-		$where  = array_filter($this->app['former']->labels, function ($label) use ($name) {
440
-			return $label == $name;
441
-		});
442
-		$unique = sizeof($where);
443
-
444
-		// In case the field doesn't need to be numbered
445
-		if ($unique < 2 or empty($this->items)) {
446
-			return false;
447
-		}
448
-
449
-		return $unique;
450
-	}
451
-
452
-	/**
453
-	 * Set something on the currently focused checkable
454
-	 *
455
-	 * @param string $attribute The key to set
456
-	 * @param string $value     Its value
457
-	 *
458
-	 * @return $this|bool
459
-	 */
460
-	protected function setOnFocused($attribute, $value)
461
-	{
462
-		if (is_null($this->focus)) {
463
-			return false;
464
-		}
465
-
466
-		$this->items[$this->focus] = Arr::set($this->items[$this->focus], $attribute, $value);
467
-
468
-		return $this;
469
-	}
470
-
471
-	/**
472
-	 * Check if a checkable is checked
473
-	 *
474
-	 * @return boolean Checked or not
475
-	 */
476
-	protected function isChecked($item = null, $value = null)
477
-	{
478
-		if (isset($item['name'])) {
479
-			$name = $item['name'];
480
-		}
481
-		if (empty($name)) {
482
-			$name = $this->name;
483
-		}
484
-
485
-		// If it's a checkbox, see if we marqued that one as checked in the array
486
-		// Or if it's a single radio, simply see if we called check
487
-		if ($this->isCheckbox() or
488
-			!$this->isCheckbox() and !$this->items
489
-		) {
490
-			$checked = Arr::get($this->checked, $name, false);
491
-
492
-			// If there are multiple, search for the value
493
-			// as the name are the same between radios
494
-		} else {
495
-			$checked = Arr::get($this->checked, $value, false);
496
-		}
497
-
498
-		// Check the values and POST array
499
-		if ($this->isGrouped()) {
500
-			// The group index. (e.g. 'bar' if the item name is foo[bar], or the item index for foo[])
501
-			$groupIndex = self::getGroupIndexFromItem($item);
502
-
503
-			// Search using the bare name, not the individual item name
504
-			$post   = $this->app['former']->getPost($this->name);
505
-			$static = $this->app['former']->getValue($this->bind ?: $this->name);
506
-
507
-			if (isset($post[$groupIndex])) {
508
-				$post = $post[$groupIndex];
509
-			}
510
-
511
-			/**
512
-			 * Support for Laravel Collection repopulating for grouped checkboxes. Note that the groupIndex must
513
-			 * match the value in order for the checkbox to be considered checked, e.g.:
514
-			 *
515
-			 *  array(
516
-			 *    'name' = 'roles[foo]',
517
-			 *    'value' => 'foo',
518
-			 *  )
519
-			 */
520
-			if ($static instanceof Collection) {
521
-				// If the repopulate value is a collection, search for an item matching the $groupIndex
522
-				foreach ($static as $staticItem) {
523
-					$staticItemValue = method_exists($staticItem, 'getKey') ? $staticItem->getKey() : $staticItem;
524
-					if ($staticItemValue == $groupIndex) {
525
-						$static = $staticItemValue;
526
-						break;
527
-					}
528
-				}
529
-			} else if (isset($static[$groupIndex])) {
530
-				$static = $static[$groupIndex];
531
-			}
532
-		} else {
533
-			$post   = $this->app['former']->getPost($name);
534
-			$static = $this->app['former']->getValue($this->bind ?: $name);
535
-		}
536
-
537
-		if (!is_null($post) and $post !== $this->app['former']->getOption('unchecked_value')) {
538
-			$isChecked = ($post == $value);
539
-		} elseif (!is_null($static)) {
540
-			$isChecked = ($static == $value);
541
-		} else {
542
-			$isChecked = $checked;
543
-		}
544
-
545
-		return $isChecked ? true : false;
546
-	}
547
-
548
-	/**
549
-	 * Check if the current element is a checkbox
550
-	 *
551
-	 * @return boolean Checkbox or radio
552
-	 */
553
-	protected function isCheckbox()
554
-	{
555
-		return $this->checkable == 'checkbox';
556
-	}
557
-
558
-	/**
559
-	 * Check if the checkables are grouped or not
560
-	 *
561
-	 * @return boolean
562
-	 */
563
-	protected function isGrouped()
564
-	{
565
-		return
566
-			$this->grouped == true or
567
-			strpos($this->name, '[]') !== false;
568
-	}
569
-
570
-	/**
571
-	 * @param array $item The item array, containing at least name and count keys.
572
-	 *
573
-	 * @return mixed The group index. (e.g. returns bar if the item name is foo[bar], or the item count for foo[])
574
-	 */
575
-	public static function getGroupIndexFromItem($item)
576
-	{
577
-		$groupIndex = preg_replace('/^.*?\[(.*)\]$/', '$1', $item['name']);
578
-		if (empty($groupIndex) or $groupIndex == $item['name']) {
579
-			return $item['count'];
580
-		}
581
-
582
-		return $groupIndex;
583
-	}
17
+    /**
18
+     * Renders the checkables as inline
19
+     *
20
+     * @var boolean
21
+     */
22
+    protected $inline = false;
23
+
24
+    /**
25
+     * Add a text to a single element
26
+     *
27
+     * @var string
28
+     */
29
+    protected $text = null;
30
+
31
+    /**
32
+     * Renders the checkables as grouped
33
+     *
34
+     * @var boolean
35
+     */
36
+    protected $grouped = false;
37
+
38
+    /**
39
+     * The checkable items currently stored
40
+     *
41
+     * @var array
42
+     */
43
+    protected $items = array();
44
+
45
+    /**
46
+     * The type of checkable item
47
+     *
48
+     * @var string
49
+     */
50
+    protected $checkable = null;
51
+
52
+    /**
53
+     * An array of checked items
54
+     *
55
+     * @var array
56
+     */
57
+    protected $checked = array();
58
+
59
+    /**
60
+     * The checkable currently being focused on
61
+     *
62
+     * @var integer
63
+     */
64
+    protected $focus = null;
65
+
66
+    /**
67
+     * Whether this particular checkable is to be pushed
68
+     *
69
+     * @var boolean
70
+     */
71
+    protected $isPushed = null;
72
+
73
+    ////////////////////////////////////////////////////////////////////
74
+    //////////////////////////// CORE METHODS //////////////////////////
75
+    ////////////////////////////////////////////////////////////////////
76
+
77
+    /**
78
+     * Build a new checkable
79
+     *
80
+     * @param Container $app
81
+     * @param string    $type
82
+     * @param array     $name
83
+     * @param           $label
84
+     * @param           $value
85
+     * @param           $attributes
86
+     */
87
+    public function __construct(Container $app, $type, $name, $label, $value, $attributes)
88
+    {
89
+        // Unify auto and chained methods of grouping checkboxes
90
+        if (Str::endsWith($name, '[]')) {
91
+            $name = substr($name, 0, -2);
92
+            $this->grouped();
93
+        }
94
+        parent::__construct($app, $type, $name, $label, $value, $attributes);
95
+
96
+        if (is_array($this->value)) {
97
+            $this->items($this->value);
98
+        }
99
+    }
100
+
101
+    /**
102
+     * Apply methods to focused checkable
103
+     *
104
+     * @param string $method
105
+     * @param array  $parameters
106
+     *
107
+     * @return $this
108
+     */
109
+    public function __call($method, $parameters)
110
+    {
111
+        $focused = $this->setOnFocused('attributes.'.$method, Arr::get($parameters, 0));
112
+        if ($focused) {
113
+            return $this;
114
+        }
115
+
116
+        return parent::__call($method, $parameters);
117
+    }
118
+
119
+    /**
120
+     * Prints out the currently stored checkables
121
+     */
122
+    public function render()
123
+    {
124
+        $html = null;
125
+
126
+        $this->setFieldClasses();
127
+
128
+        // Multiple items
129
+        if ($this->items) {
130
+            unset($this->app['former']->labels[array_search($this->name, $this->app['former']->labels)]);
131
+            foreach ($this->items as $key => $item) {
132
+                $value = $this->isCheckbox() && !$this->isGrouped() ? 1 : $key;
133
+                $html .= $this->createCheckable($item, $value);
134
+            }
135
+
136
+            return $html;
137
+        }
138
+
139
+        // Single item
140
+        return $this->createCheckable(array(
141
+            'name'  => $this->name,
142
+            'label' => $this->text,
143
+            'value' => $this->value,
144
+            'attributes' => $this->attributes,
145
+        ));
146
+    }
147
+
148
+    ////////////////////////////////////////////////////////////////////
149
+    ////////////////////////// FIELD METHODS ///////////////////////////
150
+    ////////////////////////////////////////////////////////////////////
151
+
152
+    /**
153
+     * Focus on a particular checkable
154
+     *
155
+     * @param integer $on The checkable to focus on
156
+     *
157
+     * @return $this
158
+     */
159
+    public function on($on)
160
+    {
161
+        if (!isset($this->items[$on])) {
162
+            return $this;
163
+        } else {
164
+            $this->focus = $on;
165
+        }
166
+
167
+        return $this;
168
+    }
169
+
170
+    /**
171
+     * Set the checkables as inline
172
+     */
173
+    public function inline($isInline = true)
174
+    {
175
+        $this->inline = $isInline;
176
+
177
+        return $this;
178
+    }
179
+
180
+    /**
181
+     * Set the checkables as stacked
182
+     */
183
+    public function stacked($isStacked = true)
184
+    {
185
+        $this->inline = !$isStacked;
186
+
187
+        return $this;
188
+    }
189
+
190
+    /**
191
+     * Set the checkables as grouped
192
+     */
193
+    public function grouped($isGrouped = true)
194
+    {
195
+        $this->grouped = $isGrouped;
196
+
197
+        return $this;
198
+    }
199
+
200
+    /**
201
+     * Add text to a single checkable
202
+     *
203
+     * @param  string $text The checkable label
204
+     *
205
+     * @return $this
206
+     */
207
+    public function text($text)
208
+    {
209
+        // Translate and format
210
+        $text = Helpers::translate($text);
211
+
212
+        // Apply on focused if any
213
+        $focused = $this->setOnFocused('label', $text);
214
+        if ($focused) {
215
+            return $this;
216
+        }
217
+
218
+        $this->text = $text;
219
+
220
+        return $this;
221
+    }
222
+
223
+    /**
224
+     * Push this particular checkbox
225
+     *
226
+     * @param boolean $pushed
227
+     *
228
+     * @return $this
229
+     */
230
+    public function push($pushed = true)
231
+    {
232
+        $this->isPushed = $pushed;
233
+
234
+        return $this;
235
+    }
236
+
237
+    /**
238
+     * Check a specific item
239
+     *
240
+     * @param bool|string $checked The checkable to check, or an array of checked items
241
+     *
242
+     * @return $this
243
+     */
244
+    public function check($checked = true)
245
+    {
246
+        // If we're setting all the checked items at once
247
+        if (is_array($checked)) {
248
+            $this->checked = $checked;
249
+            // Checking an item in particular
250
+        } elseif (is_string($checked) or is_int($checked)) {
251
+            $this->checked[$checked] = true;
252
+            // Only setting a single item
253
+        } else {
254
+            $this->checked[$this->name] = (bool) $checked;
255
+        }
256
+
257
+        return $this;
258
+    }
259
+
260
+
261
+    /**
262
+     * Check if the checkables are inline
263
+     *
264
+     * @return boolean
265
+     */
266
+    public function isInline()
267
+    {
268
+        return $this->inline;
269
+    }
270
+
271
+    ////////////////////////////////////////////////////////////////////
272
+    ////////////////////////// INTERNAL METHODS ////////////////////////
273
+    ////////////////////////////////////////////////////////////////////
274
+
275
+    /**
276
+     * Creates a series of checkable items
277
+     *
278
+     * @param array $_items Items to create
279
+     */
280
+    protected function items($_items)
281
+    {
282
+        // If passing an array
283
+        if (sizeof($_items) == 1 and
284
+            isset($_items[0]) and
285
+            is_array($_items[0])
286
+        ) {
287
+            $_items = $_items[0];
288
+        }
289
+
290
+        // Fetch models if that's what we were passed
291
+        if (isset($_items[0]) and is_object($_items[0])) {
292
+            $_items = Helpers::queryToArray($_items);
293
+            $_items = array_flip($_items);
294
+        }
295
+
296
+        // Iterate through items, assign a name and a label to each
297
+        $count = 0;
298
+        foreach ($_items as $label => $name) {
299
+
300
+            // Define a fallback name in case none is found
301
+            $fallback = $this->isCheckbox()
302
+                ? $this->name.'_'.$count
303
+                : $this->name;
304
+
305
+            // Grouped fields
306
+            if ($this->isGrouped()) {
307
+                $attributes['id'] = str_replace('[]', null, $fallback);
308
+                $fallback         = str_replace('[]', null, $this->name).'[]';
309
+            }
310
+
311
+            // If we haven't any name defined for the checkable, try to compute some
312
+            if (!is_string($label) and !is_array($name)) {
313
+                $label = $name;
314
+                $name  = $fallback;
315
+            }
316
+
317
+            // If we gave custom information on the item, add them
318
+            if (is_array($name)) {
319
+                $attributes = $name;
320
+                $name       = Arr::get($attributes, 'name', $fallback);
321
+                unset($attributes['name']);
322
+            }
323
+
324
+            // Store all informations we have in an array
325
+            $item = array(
326
+                'name'  => $name,
327
+                'label' => Helpers::translate($label),
328
+                'count' => $count,
329
+            );
330
+            if (isset($attributes)) {
331
+                $item['attributes'] = $attributes;
332
+            }
333
+
334
+            $this->items[] = $item;
335
+            $count++;
336
+        }
337
+    }
338
+
339
+    /**
340
+     * Renders a checkable
341
+     *
342
+     * @param string|array $item          A checkable item
343
+     * @param integer      $fallbackValue A fallback value if none is set
344
+     *
345
+     * @return string
346
+     */
347
+    protected function createCheckable($item, $fallbackValue = 1)
348
+    {
349
+        // Extract informations
350
+        extract($item);
351
+
352
+        // Set default values
353
+        if (!isset($attributes)) {
354
+            $attributes = array();
355
+        }
356
+        if (isset($attributes['value'])) {
357
+            $value = $attributes['value'];
358
+        }
359
+        if (!isset($value) or $value === $this->app['former']->getOption('unchecked_value')) {
360
+            $value = $fallbackValue;
361
+        }
362
+
363
+        // If inline items, add class
364
+        $isInline = $this->inline ? ' '.$this->app['former.framework']->getInlineLabelClass($this) : null;
365
+
366
+        // In Bootsrap 3 or 4, don't append the the checkable type (radio/checkbox) as a class if
367
+        // rendering inline.
368
+        $class =  ($this->app['former']->framework() == 'TwitterBootstrap3' ||
369
+            $this->app['former']->framework() == 'TwitterBootstrap4') ? trim($isInline) : $this->checkable.$isInline;
370
+
371
+        // Merge custom attributes with global attributes
372
+        $attributes = array_merge($this->attributes, $attributes);
373
+        if (!isset($attributes['id'])) {
374
+            $attributes['id'] = $name.$this->unique($name);
375
+        }
376
+
377
+        // Create field
378
+        $field = Input::create($this->checkable, $name, Helpers::encode($value), $attributes);
379
+        if ($this->isChecked($item, $value)) {
380
+            $field->checked('checked');
381
+        }
382
+
383
+        // Add hidden checkbox if requested
384
+        if ($this->isOfType('checkbox', 'checkboxes')) {
385
+            if ($this->isPushed or ($this->app['former']->getOption('push_checkboxes') and $this->isPushed !== false)) {
386
+                $field = $this->app['former']->hidden($name)->forceValue($this->app['former']->getOption('unchecked_value')).$field->render();
387
+
388
+                // app['former.field'] was overwritten by Former::hidden() call in the line above, so here
389
+                // we reset it to $this to enable $this->app['former']->getErrors() to retrieve the correct object
390
+                $this->app->instance('former.field', $this);
391
+            }
392
+        }
393
+
394
+        // If no label to wrap, return plain checkable
395
+        if (!$label) {
396
+            $element = (is_object($field)) ? $field->render() : $field;
397
+        } elseif ($this->app['former']->framework() == 'TwitterBootstrap4') {
398
+            // Revised for Bootstrap 4, move the 'input' outside of the 'label'
399
+            $labelClass = 'form-check-label';
400
+            $element = $field . Element::create('label', $label)->for($attributes['id'])->class($labelClass)->render();
401
+
402
+            $wrapper_class = $this->inline ? 'form-check form-check-inline' : 'form-check';
403
+
404
+            $element = Element::create('div', $element)->class($wrapper_class)->render();
405
+        } else {
406
+            // Original way is to add the 'input' inside the 'label'
407
+            $element = Element::create('label', $field.$label)->for($attributes['id'])->class($class)->render();
408
+        }
409
+
410
+        // If BS3, if checkables are stacked, wrap them in a div with the checkable type
411
+        if (!$isInline && $this->app['former']->framework() == 'TwitterBootstrap3') {
412
+            $wrapper = Element::create('div', $element)->class($this->checkable);
413
+            if ($this->getAttribute('disabled')) {
414
+                $wrapper->addClass('disabled');
415
+            }
416
+            $element = $wrapper->render();
417
+        }
418
+
419
+        // Return the field
420
+        return $element;
421
+    }
422
+
423
+    ////////////////////////////////////////////////////////////////////
424
+    ///////////////////////////// HELPERS //////////////////////////////
425
+    ////////////////////////////////////////////////////////////////////
426
+
427
+    /**
428
+     * Generate an unique ID for a field
429
+     *
430
+     * @param string $name The field's name
431
+     *
432
+     * @return string A field number to use
433
+     */
434
+    protected function unique($name)
435
+    {
436
+        $this->app['former']->labels[] = $name;
437
+
438
+        // Count number of fields with the same ID
439
+        $where  = array_filter($this->app['former']->labels, function ($label) use ($name) {
440
+            return $label == $name;
441
+        });
442
+        $unique = sizeof($where);
443
+
444
+        // In case the field doesn't need to be numbered
445
+        if ($unique < 2 or empty($this->items)) {
446
+            return false;
447
+        }
448
+
449
+        return $unique;
450
+    }
451
+
452
+    /**
453
+     * Set something on the currently focused checkable
454
+     *
455
+     * @param string $attribute The key to set
456
+     * @param string $value     Its value
457
+     *
458
+     * @return $this|bool
459
+     */
460
+    protected function setOnFocused($attribute, $value)
461
+    {
462
+        if (is_null($this->focus)) {
463
+            return false;
464
+        }
465
+
466
+        $this->items[$this->focus] = Arr::set($this->items[$this->focus], $attribute, $value);
467
+
468
+        return $this;
469
+    }
470
+
471
+    /**
472
+     * Check if a checkable is checked
473
+     *
474
+     * @return boolean Checked or not
475
+     */
476
+    protected function isChecked($item = null, $value = null)
477
+    {
478
+        if (isset($item['name'])) {
479
+            $name = $item['name'];
480
+        }
481
+        if (empty($name)) {
482
+            $name = $this->name;
483
+        }
484
+
485
+        // If it's a checkbox, see if we marqued that one as checked in the array
486
+        // Or if it's a single radio, simply see if we called check
487
+        if ($this->isCheckbox() or
488
+            !$this->isCheckbox() and !$this->items
489
+        ) {
490
+            $checked = Arr::get($this->checked, $name, false);
491
+
492
+            // If there are multiple, search for the value
493
+            // as the name are the same between radios
494
+        } else {
495
+            $checked = Arr::get($this->checked, $value, false);
496
+        }
497
+
498
+        // Check the values and POST array
499
+        if ($this->isGrouped()) {
500
+            // The group index. (e.g. 'bar' if the item name is foo[bar], or the item index for foo[])
501
+            $groupIndex = self::getGroupIndexFromItem($item);
502
+
503
+            // Search using the bare name, not the individual item name
504
+            $post   = $this->app['former']->getPost($this->name);
505
+            $static = $this->app['former']->getValue($this->bind ?: $this->name);
506
+
507
+            if (isset($post[$groupIndex])) {
508
+                $post = $post[$groupIndex];
509
+            }
510
+
511
+            /**
512
+             * Support for Laravel Collection repopulating for grouped checkboxes. Note that the groupIndex must
513
+             * match the value in order for the checkbox to be considered checked, e.g.:
514
+             *
515
+             *  array(
516
+             *    'name' = 'roles[foo]',
517
+             *    'value' => 'foo',
518
+             *  )
519
+             */
520
+            if ($static instanceof Collection) {
521
+                // If the repopulate value is a collection, search for an item matching the $groupIndex
522
+                foreach ($static as $staticItem) {
523
+                    $staticItemValue = method_exists($staticItem, 'getKey') ? $staticItem->getKey() : $staticItem;
524
+                    if ($staticItemValue == $groupIndex) {
525
+                        $static = $staticItemValue;
526
+                        break;
527
+                    }
528
+                }
529
+            } else if (isset($static[$groupIndex])) {
530
+                $static = $static[$groupIndex];
531
+            }
532
+        } else {
533
+            $post   = $this->app['former']->getPost($name);
534
+            $static = $this->app['former']->getValue($this->bind ?: $name);
535
+        }
536
+
537
+        if (!is_null($post) and $post !== $this->app['former']->getOption('unchecked_value')) {
538
+            $isChecked = ($post == $value);
539
+        } elseif (!is_null($static)) {
540
+            $isChecked = ($static == $value);
541
+        } else {
542
+            $isChecked = $checked;
543
+        }
544
+
545
+        return $isChecked ? true : false;
546
+    }
547
+
548
+    /**
549
+     * Check if the current element is a checkbox
550
+     *
551
+     * @return boolean Checkbox or radio
552
+     */
553
+    protected function isCheckbox()
554
+    {
555
+        return $this->checkable == 'checkbox';
556
+    }
557
+
558
+    /**
559
+     * Check if the checkables are grouped or not
560
+     *
561
+     * @return boolean
562
+     */
563
+    protected function isGrouped()
564
+    {
565
+        return
566
+            $this->grouped == true or
567
+            strpos($this->name, '[]') !== false;
568
+    }
569
+
570
+    /**
571
+     * @param array $item The item array, containing at least name and count keys.
572
+     *
573
+     * @return mixed The group index. (e.g. returns bar if the item name is foo[bar], or the item count for foo[])
574
+     */
575
+    public static function getGroupIndexFromItem($item)
576
+    {
577
+        $groupIndex = preg_replace('/^.*?\[(.*)\]$/', '$1', $item['name']);
578
+        if (empty($groupIndex) or $groupIndex == $item['name']) {
579
+            return $item['count'];
580
+        }
581
+
582
+        return $groupIndex;
583
+    }
584 584
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -365,7 +365,7 @@  discard block
 block discarded – undo
365 365
 
366 366
 		// In Bootsrap 3 or 4, don't append the the checkable type (radio/checkbox) as a class if
367 367
 		// rendering inline.
368
-		$class =  ($this->app['former']->framework() == 'TwitterBootstrap3' ||
368
+		$class = ($this->app['former']->framework() == 'TwitterBootstrap3' ||
369 369
 			$this->app['former']->framework() == 'TwitterBootstrap4') ? trim($isInline) : $this->checkable.$isInline;
370 370
 
371 371
 		// Merge custom attributes with global attributes
@@ -397,7 +397,7 @@  discard block
 block discarded – undo
397 397
 		} elseif ($this->app['former']->framework() == 'TwitterBootstrap4') {
398 398
 			// Revised for Bootstrap 4, move the 'input' outside of the 'label'
399 399
 			$labelClass = 'form-check-label';
400
-			$element = $field . Element::create('label', $label)->for($attributes['id'])->class($labelClass)->render();
400
+			$element = $field.Element::create('label', $label)->for($attributes['id'])->class($labelClass)->render();
401 401
 
402 402
 			$wrapper_class = $this->inline ? 'form-check form-check-inline' : 'form-check';
403 403
 
@@ -436,7 +436,7 @@  discard block
 block discarded – undo
436 436
 		$this->app['former']->labels[] = $name;
437 437
 
438 438
 		// Count number of fields with the same ID
439
-		$where  = array_filter($this->app['former']->labels, function ($label) use ($name) {
439
+		$where  = array_filter($this->app['former']->labels, function($label) use ($name) {
440 440
 			return $label == $name;
441 441
 		});
442 442
 		$unique = sizeof($where);
Please login to merge, or discard this patch.
src/Former/Form/Group.php 1 patch
Indentation   +469 added lines, -469 removed lines patch added patch discarded remove patch
@@ -13,473 +13,473 @@
 block discarded – undo
13 13
  */
14 14
 class Group extends Tag
15 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());
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() && $this->app['former']->framework() == 'TwitterBootstrap4') {
180
-			$wrapperClass = $field->isInline() ? 'form-check form-check-inline' : 'form-check';
181
-			if ($this->app['former']->getErrors($field->getName())) {
182
-				$hiddenInput = Element::create('input', null, ['type' => 'hidden'])->class('form-check-input is-invalid');
183
-				$help = $hiddenInput.$help;
184
-			}
185
-			$help = Element::create('div', $help)->class($wrapperClass);
186
-		}
187
-		$field = $this->prependAppend($field);
188
-		$field .= $help;
189
-
190
-		return $this->wrap($field, $label);
191
-	}
192
-
193
-	////////////////////////////////////////////////////////////////////
194
-	//////////////////////////// FIELD METHODS /////////////////////////
195
-	////////////////////////////////////////////////////////////////////
196
-
197
-	/**
198
-	 * Set the state of the group
199
-	 *
200
-	 * @param  string $state A Bootstrap state class
201
-	 */
202
-	public function state($state)
203
-	{
204
-		// Filter state
205
-		$state = $this->app['former.framework']->filterState($state);
206
-
207
-		$this->state = $state;
208
-	}
209
-
210
-	/**
211
-	 * Set a class on the Group
212
-	 *
213
-	 * @param string $class The class to add
214
-	 */
215
-	public function addGroupClass($class)
216
-	{
217
-		$this->addClass($class);
218
-	}
219
-
220
-	/**
221
-	 * Adds a label to the group
222
-	 *
223
-	 * @param  string $label A label
224
-	 */
225
-	public function setLabel($label)
226
-	{
227
-		if (!$label instanceof Element) {
228
-			$label = Helpers::translate($label);
229
-			$label = Element::create('label', $label)->for($label);
230
-		}
231
-
232
-		$this->label = $label;
233
-	}
234
-
235
-	/**
236
-	 * Get the formatted group label
237
-	 *
238
-	 * @return string|null
239
-	 */
240
-	public function getFormattedLabel()
241
-	{
242
-		if (!$this->label) {
243
-			return false;
244
-		}
245
-
246
-		return $this->label->addClass($this->app['former.framework']->getLabelClasses());
247
-	}
248
-
249
-	/**
250
-	 * Disables the control group for the current field
251
-	 */
252
-	public function raw()
253
-	{
254
-		$this->raw = true;
255
-	}
256
-
257
-	/**
258
-	 * Check if the current group is to be displayed or not
259
-	 *
260
-	 * @return boolean
261
-	 */
262
-	public function isRaw()
263
-	{
264
-		return (bool) $this->raw;
265
-	}
266
-
267
-	////////////////////////////////////////////////////////////////////
268
-	///////////////////////////// HELP BLOCKS //////////////////////////
269
-	////////////////////////////////////////////////////////////////////
270
-
271
-	/**
272
-	 * Alias for inlineHelp
273
-	 *
274
-	 * @param  string $help       The help text
275
-	 * @param  array  $attributes Facultative attributes
276
-	 */
277
-	public function help($help, $attributes = array())
278
-	{
279
-		return $this->inlineHelp($help, $attributes);
280
-	}
281
-
282
-	/**
283
-	 * Add an inline help
284
-	 *
285
-	 * @param  string $help       The help text
286
-	 * @param  array  $attributes Facultative attributes
287
-	 */
288
-	public function inlineHelp($help, $attributes = array())
289
-	{
290
-		// If no help text, do nothing
291
-		if (!$help) {
292
-			return false;
293
-		}
294
-
295
-		$this->help['inline'] = $this->app['former.framework']->createHelp($help, $attributes);
296
-	}
297
-
298
-	/**
299
-	 * Add an block help
300
-	 *
301
-	 * @param  string $help       The help text
302
-	 * @param  array  $attributes Facultative attributes
303
-	 */
304
-	public function blockHelp($help, $attributes = array())
305
-	{
306
-		// Reserved method
307
-		if ($this->app['former.framework']->isnt('TwitterBootstrap') &&
308
-		    $this->app['former.framework']->isnt('TwitterBootstrap3') &&
309
-		    $this->app['former.framework']->isnt('TwitterBootstrap4')
310
-		) {
311
-			throw new BadMethodCallException('This method is only available on the Bootstrap framework');
312
-		}
313
-
314
-		// If no help text, do nothing
315
-		if (!$help) {
316
-			return false;
317
-		}
318
-
319
-		$this->help['block'] = $this->app['former.framework']->createBlockHelp($help, $attributes);
320
-	}
321
-
322
-	////////////////////////////////////////////////////////////////////
323
-	///////////////////////// PREPEND/APPEND METHODS ///////////////////
324
-	////////////////////////////////////////////////////////////////////
325
-
326
-	/**
327
-	 * Prepend elements to the field
328
-	 */
329
-	public function prepend()
330
-	{
331
-		$this->placeAround(func_get_args(), 'prepend');
332
-	}
333
-
334
-	/**
335
-	 * Append elements to the field
336
-	 */
337
-	public function append()
338
-	{
339
-		$this->placeAround(func_get_args(), 'append');
340
-	}
341
-
342
-	/**
343
-	 * Prepends an icon to a field
344
-	 *
345
-	 * @param string $icon       The icon to prepend
346
-	 * @param array  $attributes Its attributes
347
-	 */
348
-	public function prependIcon($icon, $attributes = array(), $iconSettings = array())
349
-	{
350
-		$icon = $this->app['former.framework']->createIcon($icon, $attributes, $iconSettings);
351
-
352
-		$this->prepend($icon);
353
-	}
354
-
355
-	/**
356
-	 * Append an icon to a field
357
-	 *
358
-	 * @param string $icon       The icon to prepend
359
-	 * @param array  $attributes Its attributes
360
-	 */
361
-	public function appendIcon($icon, $attributes = array(), $iconSettings = array())
362
-	{
363
-		$icon = $this->app['former.framework']->createIcon($icon, $attributes, $iconSettings);
364
-
365
-		$this->append($icon);
366
-	}
367
-
368
-	////////////////////////////////////////////////////////////////////
369
-	//////////////////////////////// HELPERS ///////////////////////////
370
-	////////////////////////////////////////////////////////////////////
371
-
372
-	/**
373
-	 * Get the errors for the group
374
-	 *
375
-	 * @return string
376
-	 */
377
-	public function getErrors()
378
-	{
379
-		$errors = '';
380
-
381
-		if (!self::$opened) {
382
-
383
-			// for non-custom groups, normal error handling applies
384
-			$errors = $this->app['former']->getErrors();
385
-		} elseif (!empty($this->validations)) {
386
-
387
-			// error handling only when validations specified for custom groups
388
-			foreach ($this->validations as $validation) {
389
-				$errors .= $this->app['former']->getErrors($validation);
390
-			}
391
-		}
392
-
393
-		return $errors;
394
-	}
395
-
396
-	/**
397
-	 * Wraps content in a group
398
-	 *
399
-	 * @param string $contents The content
400
-	 * @param string $label    The label to add
401
-	 *
402
-	 * @return string A group
403
-	 */
404
-	public function wrap($contents, $label = null)
405
-	{
406
-		$group = $this->open();
407
-		$group .= $label;
408
-		$group .= $this->app['former.framework']->wrapField($contents);
409
-		$group .= $this->close();
410
-
411
-		return $group;
412
-	}
413
-
414
-	/**
415
-	 * Prints out the current label
416
-	 *
417
-	 * @param  string $field The field to create a label for
418
-	 *
419
-	 * @return string        A <label> tag
420
-	 */
421
-	protected function getLabel($field = null)
422
-	{
423
-		// Don't create a label if none exist
424
-		if (!$field or !$this->label) {
425
-			return null;
426
-		}
427
-
428
-		// Wrap label in framework classes
429
-		$this->label->addClass($this->app['former.framework']->getLabelClasses());
430
-		$this->label = $this->app['former.framework']->createLabelOf($field, $this->label);
431
-		$this->label = $this->app['former.framework']->wrapLabel($this->label);
432
-
433
-		return $this->label;
434
-	}
435
-
436
-	/**
437
-	 * Prints out the current help
438
-	 *
439
-	 * @return string A .help-block or .help-inline
440
-	 */
441
-	protected function getHelp()
442
-	{
443
-		$inline = Arr::get($this->help, 'inline');
444
-		$block  = Arr::get($this->help, 'block');
445
-
446
-		// Replace help text with error if any found
447
-		$errors = $this->app['former']->getErrors();
448
-		if ($errors and $this->app['former']->getOption('error_messages')) {
449
-			$inline = $this->app['former.framework']->createValidationError($errors);
450
-		}
451
-
452
-		return join(null, array($inline, $block));
453
-	}
454
-
455
-	/**
456
-	 * Format the field with prepended/appended elements
457
-	 *
458
-	 * @param  Field $field The field to format
459
-	 *
460
-	 * @return string        Field plus supplementary elements
461
-	 */
462
-	protected function prependAppend($field)
463
-	{
464
-		if (!$this->prepend and !$this->append) {
465
-			return $field->render();
466
-		}
467
-
468
-		return $this->app['former.framework']->prependAppend($field, $this->prepend, $this->append);
469
-	}
470
-
471
-	/**
472
-	 * Place elements around the field
473
-	 *
474
-	 * @param  array  $items An array of items to place
475
-	 * @param  string $place Where they should end up (prepend|append)
476
-	 */
477
-	protected function placeAround($items, $place)
478
-	{
479
-		// Iterate over the items and place them where they should
480
-		foreach ((array) $items as $item) {
481
-			$item             = $this->app['former.framework']->placeAround($item, $place);
482
-			$this->{$place}[] = $item;
483
-		}
484
-	}
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());
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() && $this->app['former']->framework() == 'TwitterBootstrap4') {
180
+            $wrapperClass = $field->isInline() ? 'form-check form-check-inline' : 'form-check';
181
+            if ($this->app['former']->getErrors($field->getName())) {
182
+                $hiddenInput = Element::create('input', null, ['type' => 'hidden'])->class('form-check-input is-invalid');
183
+                $help = $hiddenInput.$help;
184
+            }
185
+            $help = Element::create('div', $help)->class($wrapperClass);
186
+        }
187
+        $field = $this->prependAppend($field);
188
+        $field .= $help;
189
+
190
+        return $this->wrap($field, $label);
191
+    }
192
+
193
+    ////////////////////////////////////////////////////////////////////
194
+    //////////////////////////// FIELD METHODS /////////////////////////
195
+    ////////////////////////////////////////////////////////////////////
196
+
197
+    /**
198
+     * Set the state of the group
199
+     *
200
+     * @param  string $state A Bootstrap state class
201
+     */
202
+    public function state($state)
203
+    {
204
+        // Filter state
205
+        $state = $this->app['former.framework']->filterState($state);
206
+
207
+        $this->state = $state;
208
+    }
209
+
210
+    /**
211
+     * Set a class on the Group
212
+     *
213
+     * @param string $class The class to add
214
+     */
215
+    public function addGroupClass($class)
216
+    {
217
+        $this->addClass($class);
218
+    }
219
+
220
+    /**
221
+     * Adds a label to the group
222
+     *
223
+     * @param  string $label A label
224
+     */
225
+    public function setLabel($label)
226
+    {
227
+        if (!$label instanceof Element) {
228
+            $label = Helpers::translate($label);
229
+            $label = Element::create('label', $label)->for($label);
230
+        }
231
+
232
+        $this->label = $label;
233
+    }
234
+
235
+    /**
236
+     * Get the formatted group label
237
+     *
238
+     * @return string|null
239
+     */
240
+    public function getFormattedLabel()
241
+    {
242
+        if (!$this->label) {
243
+            return false;
244
+        }
245
+
246
+        return $this->label->addClass($this->app['former.framework']->getLabelClasses());
247
+    }
248
+
249
+    /**
250
+     * Disables the control group for the current field
251
+     */
252
+    public function raw()
253
+    {
254
+        $this->raw = true;
255
+    }
256
+
257
+    /**
258
+     * Check if the current group is to be displayed or not
259
+     *
260
+     * @return boolean
261
+     */
262
+    public function isRaw()
263
+    {
264
+        return (bool) $this->raw;
265
+    }
266
+
267
+    ////////////////////////////////////////////////////////////////////
268
+    ///////////////////////////// HELP BLOCKS //////////////////////////
269
+    ////////////////////////////////////////////////////////////////////
270
+
271
+    /**
272
+     * Alias for inlineHelp
273
+     *
274
+     * @param  string $help       The help text
275
+     * @param  array  $attributes Facultative attributes
276
+     */
277
+    public function help($help, $attributes = array())
278
+    {
279
+        return $this->inlineHelp($help, $attributes);
280
+    }
281
+
282
+    /**
283
+     * Add an inline help
284
+     *
285
+     * @param  string $help       The help text
286
+     * @param  array  $attributes Facultative attributes
287
+     */
288
+    public function inlineHelp($help, $attributes = array())
289
+    {
290
+        // If no help text, do nothing
291
+        if (!$help) {
292
+            return false;
293
+        }
294
+
295
+        $this->help['inline'] = $this->app['former.framework']->createHelp($help, $attributes);
296
+    }
297
+
298
+    /**
299
+     * Add an block help
300
+     *
301
+     * @param  string $help       The help text
302
+     * @param  array  $attributes Facultative attributes
303
+     */
304
+    public function blockHelp($help, $attributes = array())
305
+    {
306
+        // Reserved method
307
+        if ($this->app['former.framework']->isnt('TwitterBootstrap') &&
308
+            $this->app['former.framework']->isnt('TwitterBootstrap3') &&
309
+            $this->app['former.framework']->isnt('TwitterBootstrap4')
310
+        ) {
311
+            throw new BadMethodCallException('This method is only available on the Bootstrap framework');
312
+        }
313
+
314
+        // If no help text, do nothing
315
+        if (!$help) {
316
+            return false;
317
+        }
318
+
319
+        $this->help['block'] = $this->app['former.framework']->createBlockHelp($help, $attributes);
320
+    }
321
+
322
+    ////////////////////////////////////////////////////////////////////
323
+    ///////////////////////// PREPEND/APPEND METHODS ///////////////////
324
+    ////////////////////////////////////////////////////////////////////
325
+
326
+    /**
327
+     * Prepend elements to the field
328
+     */
329
+    public function prepend()
330
+    {
331
+        $this->placeAround(func_get_args(), 'prepend');
332
+    }
333
+
334
+    /**
335
+     * Append elements to the field
336
+     */
337
+    public function append()
338
+    {
339
+        $this->placeAround(func_get_args(), 'append');
340
+    }
341
+
342
+    /**
343
+     * Prepends an icon to a field
344
+     *
345
+     * @param string $icon       The icon to prepend
346
+     * @param array  $attributes Its attributes
347
+     */
348
+    public function prependIcon($icon, $attributes = array(), $iconSettings = array())
349
+    {
350
+        $icon = $this->app['former.framework']->createIcon($icon, $attributes, $iconSettings);
351
+
352
+        $this->prepend($icon);
353
+    }
354
+
355
+    /**
356
+     * Append an icon to a field
357
+     *
358
+     * @param string $icon       The icon to prepend
359
+     * @param array  $attributes Its attributes
360
+     */
361
+    public function appendIcon($icon, $attributes = array(), $iconSettings = array())
362
+    {
363
+        $icon = $this->app['former.framework']->createIcon($icon, $attributes, $iconSettings);
364
+
365
+        $this->append($icon);
366
+    }
367
+
368
+    ////////////////////////////////////////////////////////////////////
369
+    //////////////////////////////// HELPERS ///////////////////////////
370
+    ////////////////////////////////////////////////////////////////////
371
+
372
+    /**
373
+     * Get the errors for the group
374
+     *
375
+     * @return string
376
+     */
377
+    public function getErrors()
378
+    {
379
+        $errors = '';
380
+
381
+        if (!self::$opened) {
382
+
383
+            // for non-custom groups, normal error handling applies
384
+            $errors = $this->app['former']->getErrors();
385
+        } elseif (!empty($this->validations)) {
386
+
387
+            // error handling only when validations specified for custom groups
388
+            foreach ($this->validations as $validation) {
389
+                $errors .= $this->app['former']->getErrors($validation);
390
+            }
391
+        }
392
+
393
+        return $errors;
394
+    }
395
+
396
+    /**
397
+     * Wraps content in a group
398
+     *
399
+     * @param string $contents The content
400
+     * @param string $label    The label to add
401
+     *
402
+     * @return string A group
403
+     */
404
+    public function wrap($contents, $label = null)
405
+    {
406
+        $group = $this->open();
407
+        $group .= $label;
408
+        $group .= $this->app['former.framework']->wrapField($contents);
409
+        $group .= $this->close();
410
+
411
+        return $group;
412
+    }
413
+
414
+    /**
415
+     * Prints out the current label
416
+     *
417
+     * @param  string $field The field to create a label for
418
+     *
419
+     * @return string        A <label> tag
420
+     */
421
+    protected function getLabel($field = null)
422
+    {
423
+        // Don't create a label if none exist
424
+        if (!$field or !$this->label) {
425
+            return null;
426
+        }
427
+
428
+        // Wrap label in framework classes
429
+        $this->label->addClass($this->app['former.framework']->getLabelClasses());
430
+        $this->label = $this->app['former.framework']->createLabelOf($field, $this->label);
431
+        $this->label = $this->app['former.framework']->wrapLabel($this->label);
432
+
433
+        return $this->label;
434
+    }
435
+
436
+    /**
437
+     * Prints out the current help
438
+     *
439
+     * @return string A .help-block or .help-inline
440
+     */
441
+    protected function getHelp()
442
+    {
443
+        $inline = Arr::get($this->help, 'inline');
444
+        $block  = Arr::get($this->help, 'block');
445
+
446
+        // Replace help text with error if any found
447
+        $errors = $this->app['former']->getErrors();
448
+        if ($errors and $this->app['former']->getOption('error_messages')) {
449
+            $inline = $this->app['former.framework']->createValidationError($errors);
450
+        }
451
+
452
+        return join(null, array($inline, $block));
453
+    }
454
+
455
+    /**
456
+     * Format the field with prepended/appended elements
457
+     *
458
+     * @param  Field $field The field to format
459
+     *
460
+     * @return string        Field plus supplementary elements
461
+     */
462
+    protected function prependAppend($field)
463
+    {
464
+        if (!$this->prepend and !$this->append) {
465
+            return $field->render();
466
+        }
467
+
468
+        return $this->app['former.framework']->prependAppend($field, $this->prepend, $this->append);
469
+    }
470
+
471
+    /**
472
+     * Place elements around the field
473
+     *
474
+     * @param  array  $items An array of items to place
475
+     * @param  string $place Where they should end up (prepend|append)
476
+     */
477
+    protected function placeAround($items, $place)
478
+    {
479
+        // Iterate over the items and place them where they should
480
+        foreach ((array) $items as $item) {
481
+            $item             = $this->app['former.framework']->placeAround($item, $place);
482
+            $this->{$place}[] = $item;
483
+        }
484
+    }
485 485
 }
Please login to merge, or discard this patch.