Completed
Pull Request — master (#375)
by Dalibor
04:36
created

Column::setAggregationFunction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 2
1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid\Column;
10
11
use Ublaboo;
12
use Ublaboo\DataGrid\DataGrid;
13
use Ublaboo\DataGrid\Row;
14
use Ublaboo\DataGrid\Exception\DataGridException;
15
use Ublaboo\DataGrid\Exception\DataGridColumnRendererException;
16
use Nette\Utils\Html;
17
use Ublaboo\DataGrid\Traits;
18
19
abstract class Column extends FilterableColumn
20
{
21
22
	use Traits\TLink;
23
24
	/**
25
	 * @var array
26
	 */
27
	protected $replacements = [];
28
29
	/**
30
	 * @var Renderer|NULL
31
	 */
32
	protected $renderer;
33
34
	/**
35
	 * @var string
36
	 */
37
	protected $template;
38
39
	/**
40
	 * @var bool|string
41
	 */
42
	protected $sortable = FALSE;
43
44
	/**
45
	 * @var bool
46
	 */
47
	protected $sortable_reset_pagination = FALSE;
48
49
	/**
50
	 * @var null|callable
51
	 */
52
	protected $sortable_callback = NULL;
53
54
	/**
55
	 * @var array
56
	 */
57
	protected $sort;
58
59
	/**
60
	 * @var bool
61
	 */
62
	protected $template_escaping = TRUE;
63
64
	/**
65
	 * @var bool
66
	 */
67
	protected $header_escaping = FALSE;
68
69
	
70
	/**
71
	 * @var string
72
	 */
73
	protected $align;
74
75
76
	/**
77
	 * @var array
78
	 */
79
	protected $template_variables = [];
80
81
	/**
82
	 * @var callable
83
	 */
84
	protected $editable_callback;
85
86
	/**
87
	 * @var array
88
	 */
89
	protected $editable_element = ['textarea', ['class' => 'form-control']];
90
91
	/**
92
	 * Cached html elements
93
	 * @var array
94
	 */
95
	protected $el_cache = [];
96
97
	/**
98
	 * @var bool
99
	 */
100
	protected $default_hide = FALSE;
101
102
	/**
103
	 * @var ColumnAggregationFunction
104
	 */
105
	protected $aggregation_function = NULL;
106
107
108
	/**
109
	 * Render row item into template
110
	 * @param  Row   $row
111
	 * @return mixed
112
	 */
113
	public function render(Row $row)
114
	{
115
		/**
116
		 * Renderer function may be used
117
		 */
118
		try {
119
			return $this->useRenderer($row);
120
		} catch (DataGridColumnRendererException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
121
			/**
122
			 * Do not use renderer
123
			 */
124
		}
125
126
		/**
127
		 * Or replacements may be applied
128
		 */
129
		list($do_replace, $replaced) = $this->applyReplacements($row);
130
		if ($do_replace) {
131
			return $replaced;
132
		}
133
134
		return $this->getColumnValue($row);
135
	}
136
137
138
	/**
139
	 * Try to render item with custom renderer
140
	 * @param  Row   $row
141
	 * @return mixed
142
	 */
143
	public function useRenderer(Row $row)
144
	{
145
		$renderer = $this->getRenderer();
146
147
		if (!$renderer) {
148
			throw new DataGridColumnRendererException;
149
		}
150
151
		if ($renderer->getConditionCallback()) {
152
			if (!call_user_func_array($renderer->getConditionCallback(), [$row->getItem()])) {
153
				throw new DataGridColumnRendererException;
154
			}
155
156
			return call_user_func_array($renderer->getCallback(), [$row->getItem()]);
157
		}
158
159
		return call_user_func_array($renderer->getCallback(), [$row->getItem()]);
160
	}
161
162
163
	/**
164
	 * Should be column values escaped in latte?
165
	 * @param bool $template_escaping
166
	 */
167
	public function setTemplateEscaping($template_escaping = TRUE)
168
	{
169
		$this->template_escaping = (bool) $template_escaping;
170
171
		return $this;
172
	}
173
174
175
	public function isTemplateEscaped()
176
	{
177
		return $this->template_escaping;
178
	}
179
180
	/**
181
	 * Should be column header escaped in latte?
182
	 * @param bool $header_escaping
183
	 */
184
	public function setHeaderEscaping($header_escaping = FALSE)
185
	{
186
		$this->header_escaping = (bool) $header_escaping;
187
188
		return $this;
189
	}
190
	
191
	public function isHeaderEscaped()
192
	{
193
		return $this->header_escaping;
194
	}
195
	
196
	
197
	/**
198
	 * Set column sortable or not
199
	 * @param bool|string $sortable
200
	 */
201
	public function setSortable($sortable = TRUE)
202
	{
203
		$this->sortable = is_string($sortable) ? $sortable : (bool) $sortable;
204
205
		return $this;
206
	}
207
208
209
	/**
210
	 * Tell whether column is sortable
211
	 * @return bool
212
	 */
213
	public function isSortable()
214
	{
215
		return (bool) $this->sortable;
216
	}
217
218
219
	/**
220
	 * Shoud be the pagination reseted after sorting?
221
	 * @param bool $sortable_reset_pagination
222
	 * @return static
223
	 */
224
	public function setSortableResetPagination($sortable_reset_pagination = TRUE)
225
	{
226
		$this->sortable_reset_pagination = (bool) $sortable_reset_pagination;
227
228
		return $this;
229
	}
230
231
232
	/**
233
	 * Do reset pagination after sorting?
234
	 * @return bool
235
	 */
236
	public function sortableResetPagination()
237
	{
238
		return $this->sortable_reset_pagination;
239
	}
240
241
242
	/**
243
	 * Set custom ORDER BY clause
244
	 * @param callable $sortable_callback
245
	 * @return static
246
	 */
247
	public function setSortableCallback(callable $sortable_callback)
248
	{
249
		$this->sortable_callback = $sortable_callback;
250
251
		return $this;
252
	}
253
254
255
	/**
256
	 * Get custom ORDER BY clause
257
	 * @return callable|null
258
	 */
259
	public function getSortableCallback()
260
	{
261
		return $this->sortable_callback;
262
	}
263
264
265
	/**
266
	 * Get column to sort by
267
	 * @return string
268
	 */
269
	public function getSortingColumn()
270
	{
271
		return is_string($this->sortable) ? $this->sortable : $this->column;
272
	}
273
274
275
	/**
276
	 * Get column name
277
	 * @return string
278
	 */
279
	public function getColumnName()
280
	{
281
		return $this->column;
282
	}
283
284
285
	/**
286
	 * Get column value of row item
287
	 * @param  Row   $row
288
	 * @return mixed
289
	 */
290
	public function getColumnValue(Row $row)
291
	{
292
		return $row->getValue($this->column);
293
	}
294
295
296
	/**
297
	 * @return string
298
	 */
299
	public function getName()
300
	{
301
		return $this->name;
302
	}
303
304
305
	/**
306
	 * Set column replacements
307
	 * @param  array $replacements
308
	 * @return Column
309
	 */
310
	public function setReplacement(array $replacements)
311
	{
312
		$this->replacements = $replacements;
313
314
		return $this;
315
	}
316
317
318
	/**
319
	 * Tell whether columns has replacements
320
	 * @return bool
321
	 */
322
	public function hasReplacements()
323
	{
324
		return (bool) $this->replacements;
325
	}
326
327
328
	/**
329
	 * Apply replacements
330
	 * @param  Row   $row
331
	 * @return array
332
	 */
333
	public function applyReplacements(Row $row)
334
	{
335
		$value = $row->getValue($this->column);
336
337
		if ((is_scalar($value) || is_null($value)) && isset($this->replacements[$value])) {
338
			return [TRUE, $this->replacements[$value]];
339
		}
340
341
		return [FALSE, NULL];
342
	}
343
344
345
	/**
346
	 * Set renderer callback and (it may be optional - the condition callback will decide)
347
	 * @param callable $renderer
348
	 */
349
	public function setRenderer($renderer, $condition_callback = NULL)
350
	{
351
		if ($this->hasReplacements()) {
352
			throw new DataGridException(
353
				"Use either Column::setReplacement() or Column::setRenderer, not both."
354
			);
355
		}
356
357
		if (!is_callable($renderer)) {
358
			throw new DataGridException(
359
				"Renderer (method Column::setRenderer()) must be callable."
360
			);
361
		}
362
363
		if (NULL != $condition_callback && !is_callable($condition_callback)) {
364
			throw new DataGridException(
365
				"Renderer (method Column::setRenderer()) must be callable."
366
			);
367
		}
368
369
		$this->renderer = new Renderer($renderer, $condition_callback);
370
371
		return $this;
372
	}
373
374
375
	/**
376
	 * Set renderer callback just if condition is truthy
377
	 * @param callable $renderer
378
	 */
379
	public function setRendererOnCondition($renderer, $condition_callback)
380
	{
381
		return $this->setRenderer($renderer, $condition_callback);
382
	}
383
384
385
	/**
386
	 * Return custom renderer callback
387
	 * @return Renderer|null
388
	 */
389
	public function getRenderer()
390
	{
391
		return $this->renderer;
392
	}
393
394
395
	/**
396
	 * Column may have its own template
397
	 * @param string $template
398
	 */
399
	public function setTemplate($template, array $template_variables = [])
400
	{
401
		$this->template = $template;
402
		$this->template_variables = $template_variables;
403
404
		return $this;
405
	}
406
407
408
	/**
409
	 * Column can have variables that will be passed to custom template scope
410
	 * @return array
411
	 */
412
	public function getTemplateVariables()
413
	{
414
		return $this->template_variables;
415
	}
416
417
418
	/**
419
	 * Tell whether column has its owntemplate
420
	 * @return bool
421
	 */
422
	public function hasTemplate()
423
	{
424
		return (bool) $this->template;
425
	}
426
427
428
	/**
429
	 * Get column template path
430
	 * @return string
431
	 */
432
	public function getTemplate()
433
	{
434
		return $this->template;
435
	}
436
437
438
	/**
439
	 * Tell whether data source is sorted by this collumn
440
	 * @return bool
441
	 */
442
	public function isSortedBy()
443
	{
444
		return (bool) $this->sort;
445
	}
446
447
448
	/**
449
	 * Tell column his sorting options
450
	 * @param array $sort
451
	 */
452
	public function setSort(array $sort)
453
	{
454
		$this->sort = $sort[$this->key];
455
456
		return $this;
457
	}
458
459
460
	/**
461
	 * What sorting will be applied after next click?
462
	 * @return array
463
	 */
464
	public function getSortNext()
465
	{
466
		if ($this->sort == 'ASC') {
467
			return [$this->key => 'DESC'];
468
		} else if ($this->sort == 'DESC') {
469
			return [$this->key => FALSE];
470
		}
471
472
		return [$this->key => 'ASC'];
473
	}
474
475
476
	/**
477
	 * @return bool
478
	 */
479
	public function hasSortNext()
480
	{
481
		foreach ($this->getSortNext() as $key => $order) {
482
			return $order !== FALSE;
483
		}
484
	}
485
486
487
	/**
488
	 * Is sorting ascending?
489
	 * @return bool
490
	 */
491
	public function isSortAsc()
492
	{
493
		return $this->sort == 'ASC';
494
	}
495
496
497
	/**
498
	 * Set column alignment
499
	 * @param string $align
500
	 */
501
	public function setAlign($align)
502
	{
503
		$this->align = (string) $align;
504
505
		return $this;
506
	}
507
508
509
	/**
510
	 * Has column some alignment?
511
	 * @return bool [description]
512
	 */
513
	public function hasAlign()
514
	{
515
		return (bool) $this->align;
516
	}
517
518
519
	/**
520
	 * Get column alignment
521
	 * @return string
522
	 */
523
	public function getAlign()
524
	{
525
		return $this->align ?: 'left';
526
	}
527
528
529
	/**
530
	 * Set column content fit
531
	 * @param bool $fit_content
532
	 * @return $this
533
	 */
534
	public function setFitContent($fit_content = TRUE)
535
	{
536
		($fit_content) ? $this->addAttributes(['class' => 'datagrid-fit-content']) : NULL;
537
538
		return $this;
539
	}
540
541
542
	/**
543
	 * Set callback that will be called after inline editing
544
	 * @param callable $editable_callback
545
	 */
546
	public function setEditableCallback(callable $editable_callback)
547
	{
548
		$this->editable_callback = $editable_callback;
549
550
		return $this;
551
	}
552
553
554
	/**
555
	 * Return callback that is used after inline editing
556
	 * @return callable
557
	 */
558
	public function getEditableCallback()
559
	{
560
		return $this->editable_callback;
561
	}
562
563
564
	/**
565
	 * Is column editable?
566
	 * @return bool
567
	 */
568
	public function isEditable()
569
	{
570
		return (bool) $this->getEditableCallback();
571
	}
572
573
574
	/**
575
	 * Element is by default textarea, user can change that
576
	 * @param string $el_type
577
	 * @param array  $attrs
578
	 * @return static
579
	 */
580
	public function setEditableInputType($el_type, array $attrs = [])
581
	{
582
		$this->editable_element = [$el_type, $attrs];
583
584
		return $this;
585
	}
586
587
588
	/**
589
	 * Change small inline edit input type to select
590
	 * @param array  $options
591
	 * @return static
592
	 */
593
	public function setEditableInputTypeSelect(array $options = [])
594
	{
595
		$select = Html::el('select');
596
597
		foreach ($options as $value => $text) {
598
			$select->create('option')
599
				->value($value)
600
				->setText($text);
601
		}
602
603
		$this->addAttributes(['data-datagrid-editable-element' => (string) $select]);
604
605
		return $this->setEditableInputType('select');
606
	}
607
608
609
	/**
610
	 * [getEditableInputType description]
611
	 * @return array
612
	 */
613
	public function getEditableInputType()
614
	{
615
		return $this->editable_element;
616
	}
617
618
619
	/**
620
	 * Set attributes for both th and td element
621
	 * @param array $attrs
622
	 * @return static
623
	 */
624
	public function addAttributes(array $attrs)
625
	{
626
		$this->getElementPrototype('td')->addAttributes($attrs);
627
		$this->getElementPrototype('th')->addAttributes($attrs);
628
629
		return $this;
630
	}
631
632
633
	/**
634
	 * Get th/td column element
635
	 * @param  string   $tag th|td
636
	 * @param  string   $key
637
	 * @param  Row|NULL $row
638
	 * @return Html
639
	 */
640
	public function getElementPrototype($tag, $key = NULL, Row $row = NULL)
641
	{
642
		/**
643
		 * Get cached element
644
		 */
645
		if (empty($this->el_cache[$tag])) {
646
			$this->el_cache[$tag] = $el = $el = Html::el($tag);
647
		} else {
648
			$el = $this->el_cache[$tag];
649
		}
650
651
		/**
652
		 * If class was set by user via $el->class = '', fix it
653
		 */
654
		if (!empty($el->class) && is_string($el->class)) {
655
			$class = $el->class;
656
			unset($el->class);
657
658
			$el->class[] = $class;
659
		}
660
661
		$el->class[] = "text-{$this->getAlign()}";
662
663
		/**
664
		 * Method called from datagrid template, set appropriate classes and another attributes
665
		 */
666
		if ($key !== NULL && $row !== NULL) {
667
			$el->class[] = "col-{$key}";
668
669
			if ($tag == 'td') {
670
				if ($this->isEditable()) {
671
					$link = $this->grid->link('edit!', ['key' => $key, 'id' => $row->getId()]);
672
673
					$el->data('datagrid-editable-url', $link);
674
675
					$el->data('datagrid-editable-type', $this->editable_element[0]);
676
					$el->data('datagrid-editable-attrs', json_encode($this->editable_element[1]));
677
				}
678
			}
679
		}
680
681
		return $el;
682
	}
683
684
685
	/**
686
	 * @param bool $default_hide
687
	 * @return static
688
	 */
689
	public function setDefaultHide($default_hide = TRUE)
690
	{
691
		$this->default_hide = (bool) $default_hide;
692
693
		if ($default_hide) {
694
			$this->grid->setSomeColumnDefaultHide($default_hide);
695
		}
696
697
		return $this;
698
	}
699
700
701
	public function getDefaultHide()
702
	{
703
		return $this->default_hide;
704
	}
705
706
707
	/**
708
	 * Get row item params (E.g. action may be called id => $item->id, name => $item->name, ...)
709
	 * @param  Row   $row
710
	 * @param  array $params_list
711
	 * @return array
712
	 */
713
	protected function getItemParams(Row $row, array $params_list)
714
	{
715
		$return = [];
716
717
		foreach ($params_list as $param_name => $param) {
718
			$return[is_string($param_name) ? $param_name : $param] = $row->getValue($param);
719
		}
720
721
		return $return;
722
	}
723
724
725
	/**
726
	 * @return string
727
	 */
728
	public function getColumn()
729
	{
730
		return $this->column;
731
	}
732
733
734
	/**
735
	 * @return ColumnAggregationFunction
736
	 */
737
	public function getAggregationFunction()
738
	{
739
		return $this->aggregation_function;
740
	}
741
742
743
	/**
744
	 * @return bool
745
	 */
746
	public function hasAggregationFunction()
747
	{
748
		return $this->aggregation_function instanceof ColumnAggregationFunction;
749
	}
750
751
752
	/**
753
	 * @param $aggregation_type use ColumnAggregationFunction::$aggregation_type...
754
	 * @param string $column
755
	 * @return ColumnAggregationFunction
756
	 */
757
	public function setAggregationFunction($aggregation_type, $column = NULL)
758
	{
759
		if ($column === NULL) {
760
			$column = $this->column;
761
		}
762
		$this->aggregation_function = new ColumnAggregationFunction($aggregation_type, $column);
0 ignored issues
show
Unused Code introduced by
The call to ColumnAggregationFunction::__construct() has too many arguments starting with $column.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
763
		return $this->aggregation_function;
764
	}
765
}
766