Completed
Pull Request — master (#236)
by
unknown
08:30
created

DataGrid::hasSomeColumnDefaultHide()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
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;
10
11
use Nette;
12
use Nette\Application\UI\Link;
13
use Nette\Application\UI\PresenterComponent;
14
use Ublaboo\DataGrid\Utils\ArraysHelper;
15
use Nette\Application\UI\Form;
16
use Ublaboo\DataGrid\Exception\DataGridException;
17
use Ublaboo\DataGrid\Exception\DataGridHasToBeAttachedToPresenterComponentException;
18
use Ublaboo\DataGrid\Utils\Sorting;
19
use Ublaboo\DataGrid\InlineEdit\InlineEdit;
20
use Ublaboo\DataGrid\ColumnsSummary;
21
22
/**
23
 * @method onRedraw()
24
 * @method onRender()
25
 * @method onColumnAdd()
26
 */
27
class DataGrid extends Nette\Application\UI\Control
28
{
29
30
	/**
31
	 * @var callable[]
32
	 */
33
	public $onRedraw;
34
35
	/**
36
	 * @var callable[]
37
	 */
38
	public $onRender = [];
39
40
	/**
41
	 * @var callable[]
42
	 */
43
	public $onColumnAdd;
44
45
	/**
46
	 * @var string
47
	 */
48
	public static $icon_prefix = 'fa fa-';
49
50
	/**
51
	 * When set to TRUE, datagrid throws an exception
52
	 * 	when tring to get related entity within join and entity does not exist
53
	 * @var bool
54
	 */
55
	public $strict_entity_property = FALSE;
56
57
	/**
58
	 * @var int
59
	 * @persistent
60
	 */
61
	public $page = 1;
62
63
	/**
64
	 * @var int|string
65
	 * @persistent
66
	 */
67
	public $per_page;
68
69
	/**
70
	 * @var array
71
	 * @persistent
72
	 */
73
	public $sort = [];
74
75
	/**
76
	 * @var array
77
	 */
78
	public $default_sort = [];
79
80
	/**
81
	 * @var array
82
	 */
83
	public $default_filter = [];
84
85
	/**
86
	 * @var bool
87
	 */
88
	public $default_filter_use_on_reset = TRUE;
89
90
	/**
91
	 * @var array
92
	 * @persistent
93
	 */
94
	public $filter = [];
95
96
	/**
97
	 * @var callable|null
98
	 */
99
	protected $sort_callback = NULL;
100
101
	/**
102
	 * @var bool
103
	 */
104
	protected $use_happy_components = TRUE;
105
106
	/**
107
	 * @var callable
108
	 */
109
	protected $rowCallback;
110
111
	/**
112
	 * @var array
113
	 */
114
	protected $items_per_page_list;
115
116
	/**
117
	 * @var string
118
	 */
119
	protected $template_file;
120
121
	/**
122
	 * @var Column\IColumn[]
123
	 */
124
	protected $columns = [];
125
126
	/**
127
	 * @var Column\Action[]
128
	 */
129
	protected $actions = [];
130
131
	/**
132
	 * @var GroupAction\GroupActionCollection
133
	 */
134
	protected $group_action_collection;
135
136
	/**
137
	 * @var Filter\Filter[]
138
	 */
139
	protected $filters = [];
140
141
	/**
142
	 * @var Export\Export[]
143
	 */
144
	protected $exports = [];
145
146
	/**
147
	 * @var DataModel
148
	 */
149
	protected $dataModel;
150
151
	/**
152
	 * @var DataFilter
153
	 */
154
	protected $dataFilter;
155
156
	/**
157
	 * @var string
158
	 */
159
	protected $primary_key = 'id';
160
161
	/**
162
	 * @var bool
163
	 */
164
	protected $do_paginate = TRUE;
165
166
	/**
167
	 * @var bool
168
	 */
169
	protected $csv_export = TRUE;
170
171
	/**
172
	 * @var bool
173
	 */
174
	protected $csv_export_filtered = TRUE;
175
176
	/**
177
	 * @var bool
178
	 */
179
	protected $sortable = FALSE;
180
181
	/**
182
	 * @var string
183
	 */
184
	protected $sortable_handler = 'sort!';
185
186
	/**
187
	 * @var string
188
	 */
189
	protected $original_template;
190
191
	/**
192
	 * @var array
193
	 */
194
	protected $redraw_item;
195
196
	/**
197
	 * @var mixed
198
	 */
199
	protected $translator;
200
201
	/**
202
	 * @var bool
203
	 */
204
	protected $force_filter_active;
205
206
	/**
207
	 * @var callable
208
	 */
209
	protected $tree_view_children_callback;
210
211
	/**
212
	 * @var callable
213
	 */
214
	protected $tree_view_has_children_callback;
215
216
	/**
217
	 * @var string
218
	 */
219
	protected $tree_view_has_children_column;
220
221
	/**
222
	 * @var boolean
223
	 */
224
	protected $tree_dynamic;
225
226
	/**
227
	 * @var boolean
228
	 */
229
	protected $tree_nodes_opened = FALSE;
230
231
	/**
232
	 * @var bool
233
	 */
234
	protected $outer_filter_rendering = FALSE;
235
236
	/**
237
	 * @var array
238
	 */
239
	protected $columns_export_order = [];
240
241
	/**
242
	 * @var bool
243
	 */
244
	protected $remember_state = TRUE;
245
246
	/**
247
	 * @var bool
248
	 */
249
	protected $refresh_url = TRUE;
250
251
	/**
252
	 * @var Nette\Http\SessionSection
253
	 */
254
	protected $grid_session;
255
256
	/**
257
	 * @var Column\ItemDetail
258
	 */
259
	protected $items_detail;
260
261
	/**
262
	 * @var array
263
	 */
264
	protected $row_conditions = [
265
		'group_action' => FALSE,
266
		'action' => []
267
	];
268
269
	/**
270
	 * @var array
271
	 */
272
	protected $column_callbacks = [];
273
274
	/**
275
	 * @var bool
276
	 */
277
	protected $can_hide_columns = FALSE;
278
279
	/**
280
	 * @var array
281
	 */
282
	protected $columns_visibility = [];
283
284
	/**
285
	 * @var InlineEdit
286
	 */
287
	protected $inlineEdit;
288
289
	/**
290
	 * @var InlineEdit
291
	 */
292
	protected $inlineAdd;
293
294
	/**
295
	 * @var bool
296
	 */
297
	protected $snippets_set = FALSE;
298
299
	/**
300
	 * @var bool
301
	 */
302
	protected $some_column_default_hide = FALSE;
303
304
	/**
305
	 * @var ColumnsSummary
306
	 */
307
	protected $columnsSummary;
308
309
310
	/**
311
	 * @param Nette\ComponentModel\IContainer|NULL $parent
312
	 * @param string                               $name
313
	 */
314
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
315
	{
316
		parent::__construct($parent, $name);
317
318
		$this->monitor('Nette\Application\UI\Presenter');
319
320
		/**
321
		 * Try to find previous filters, pagination, per_page and other values in session
322
		 */
323
		$this->onRender[] = [$this, 'findSessionValues'];
324
325
		/**
326
		 * Find default filter values
327
		 */
328
		$this->onRender[] = [$this, 'findDefaultFilter'];
329
330
		/**
331
		 * Find default sort
332
		 */
333
		$this->onRender[] = [$this, 'findDefaultSort'];
334
	}
335
336
337
	/**
338
	 * {inheritDoc}
339
	 * @return void
340
	 */
341
	public function attached($presenter)
342
	{
343
		parent::attached($presenter);
344
345
		if ($presenter instanceof Nette\Application\UI\Presenter) {
346
			/**
347
			 * Get session
348
			 */
349
			if ($this->remember_state) {
350
				$this->grid_session = $presenter->getSession($this->getSessionSectionName());
0 ignored issues
show
Documentation Bug introduced by
It seems like $presenter->getSession($...etSessionSectionName()) can also be of type object<Nette\Http\Session>. However, the property $grid_session is declared as type object<Nette\Http\SessionSection>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
351
			}
352
		}
353
	}
354
355
356
	/********************************************************************************
357
	 *                                  RENDERING                                   *
358
	 ********************************************************************************/
359
360
361
	/**
362
	 * Render template
363
	 * @return void
364
	 */
365
	public function render()
366
	{
367
		/**
368
		 * Check whether datagrid has set some columns, initiated data source, etc
369
		 */
370
		if (!($this->dataModel instanceof DataModel)) {
371
			throw new DataGridException('You have to set a data source first.');
372
		}
373
374
		if (empty($this->columns)) {
375
			throw new DataGridException('You have to add at least one column.');
376
		}
377
378
		$this->getTemplate()->setTranslator($this->getTranslator());
379
380
		/**
381
		 * Invoke possible events
382
		 */
383
		$this->onRender($this);
0 ignored issues
show
Unused Code introduced by
The call to DataGrid::onRender() has too many arguments starting with $this.

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...
384
385
		/**
386
		 * Prepare data for rendering (datagrid may render just one item)
387
		 */
388
		$rows = [];
389
390
		if (!empty($this->redraw_item)) {
391
			$items = $this->dataModel->filterRow($this->redraw_item);
392
		} else {
393
			$items = Nette\Utils\Callback::invokeArgs(
394
				[$this->dataModel, 'filterData'],
395
				[
396
					$this->getPaginator(),
397
					new Sorting($this->sort, $this->sort_callback),
398
					$this->assableFilters()
399
				]
400
			);
401
		}
402
403
		$callback = $this->rowCallback ?: NULL;
404
405
		foreach ($items as $item) {
406
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
407
408
			if ($callback) {
409
				$callback($item, $row->getControl());
410
			}
411
		}
412
413
		if ($this->isTreeView()) {
414
			$this->getTemplate()->add('tree_view_has_children_column', $this->tree_view_has_children_column);
415
			$this->getTemplate()->add('tree_dynamic', $this->tree_dynamic);
416
			$this->getTemplate()->add('tree_nodes_opened', $this->tree_nodes_opened);
417
			$this->getTemplate()->add('getTreeChildrenRows', $this->getTreeChildrenRows);
0 ignored issues
show
Documentation introduced by
The property getTreeChildrenRows does not exist on object<Ublaboo\DataGrid\DataGrid>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
418
		}
419
420
		$this->getTemplate()->add('rows', $rows);
421
422
		$this->getTemplate()->add('columns', $this->getColumns());
423
		$this->getTemplate()->add('actions', $this->actions);
424
		$this->getTemplate()->add('exports', $this->exports);
425
		$this->getTemplate()->add('filters', $this->filters);
426
427
		$this->getTemplate()->add('filter_active', $this->isFilterActive());
428
		$this->getTemplate()->add('original_template', $this->getOriginalTemplateFile());
429
		$this->getTemplate()->add('icon_prefix', static::$icon_prefix);
430
		$this->getTemplate()->add('items_detail', $this->items_detail);
431
		$this->getTemplate()->add('columns_visibility', $this->columns_visibility);
432
		$this->getTemplate()->add('columnsSummary', $this->columnsSummary);
433
434
		$this->getTemplate()->add('inlineEdit', $this->inlineEdit);
435
		$this->getTemplate()->add('inlineAdd', $this->inlineAdd);
436
437
		/**
438
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
439
		 */
440
		$this->getTemplate()->add('filter', $this['filter']);
441
442
		/**
443
		 * Set template file and render it
444
		 */
445
		$this->getTemplate()->setFile($this->getTemplateFile());
446
		$this->getTemplate()->render();
447
	}
448
449
450
	/********************************************************************************
451
	 *                                 ROW CALLBACK                                 *
452
	 ********************************************************************************/
453
454
455
	/**
456
	 * Each row can be modified with user callback
457
	 * @param  callable  $callback
458
	 * @return static
459
	 */
460
	public function setRowCallback(callable $callback)
461
	{
462
		$this->rowCallback = $callback;
463
464
		return $this;
465
	}
466
467
468
	/********************************************************************************
469
	 *                                 DATA SOURCE                                  *
470
	 ********************************************************************************/
471
472
473
	/**
474
	 * By default ID, you can change that
475
	 * @param string $primary_key
476
	 * @return static
477
	 */
478
	public function setPrimaryKey($primary_key)
479
	{
480
		if ($this->dataModel instanceof DataModel) {
481
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
482
		}
483
484
		$this->primary_key = $primary_key;
485
486
		return $this;
487
	}
488
489
490
	/**
491
	 * Set Grid data source
492
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
493
	 * @return static
494
	 */
495
	public function setDataSource($source)
496
	{
497
		$this->dataModel = new DataModel($source, $this->primary_key);
498
499
		return $this;
500
	}
501
502
503
	/********************************************************************************
504
	 *                                  TEMPLATING                                  *
505
	 ********************************************************************************/
506
507
508
	/**
509
	 * Set custom template file to render
510
	 * @param string $template_file
511
	 * @return static
512
	 */
513
	public function setTemplateFile($template_file)
514
	{
515
		$this->template_file = $template_file;
516
517
		return $this;
518
	}
519
520
521
	/**
522
	 * Get DataGrid template file
523
	 * @return string
524
	 * @return static
525
	 */
526
	public function getTemplateFile()
527
	{
528
		return $this->template_file ?: $this->getOriginalTemplateFile();
529
	}
530
531
532
	/**
533
	 * Get DataGrid original template file
534
	 * @return string
535
	 */
536
	public function getOriginalTemplateFile()
537
	{
538
		return __DIR__.'/templates/datagrid.latte';
539
	}
540
541
542
	/**
543
	 * Tell datagrid wheteher to use or not happy components
544
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
545
	 * @return void|bool
546
	 */
547
	public function useHappyComponents($use = NULL)
548
	{
549
		if (NULL === $use) {
550
			return $this->use_happy_components;
551
		}
552
553
		$this->use_happy_components = (bool) $use;
554
	}
555
556
557
	/********************************************************************************
558
	 *                                   SORTING                                    *
559
	 ********************************************************************************/
560
561
562
	/**
563
	 * Set default sorting
564
	 * @param array $sort
565
	 * @return static
566
	 */
567
	public function setDefaultSort($sort)
568
	{
569
		if (is_string($sort)) {
570
			$sort = [$sort => 'ASC'];
571
		} else {
572
			$sort = (array) $sort;
573
		}
574
575
		$this->default_sort = $sort;
576
577
		return $this;
578
	}
579
580
581
	/**
582
	 * User may set default sorting, apply it
583
	 * @return void
584
	 */
585
	public function findDefaultSort()
586
	{
587
		if (!empty($this->sort)) {
588
			return;
589
		}
590
591
		if (!empty($this->default_sort)) {
592
			$this->sort = $this->default_sort;
593
		}
594
595
		$this->saveSessionData('_grid_sort', $this->sort);
596
	}
597
598
599
	/**
600
	 * Set grido to be sortable
601
	 * @param bool $sortable
602
	 * @return static
603
	 */
604
	public function setSortable($sortable = TRUE)
605
	{
606
		if ($this->getItemsDetail()) {
607
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
608
		}
609
610
		$this->sortable = (bool) $sortable;
611
612
		return $this;
613
	}
614
615
616
	/**
617
	 * Set sortable handle
618
	 * @param string $handler
619
	 * @return static
620
	 */
621
	public function setSortableHandler($handler = 'sort!')
622
	{
623
		$this->sortable_handler = (string) $handler;
624
625
		return $this;
626
	}
627
628
629
	/**
630
	 * Tell whether DataGrid is sortable
631
	 * @return bool
632
	 */
633
	public function isSortable()
634
	{
635
		return $this->sortable;
636
	}
637
638
	/**
639
	 * Return sortable handle name
640
	 * @return string
641
	 */
642
	public function getSortableHandler()
643
	{
644
		return $this->sortable_handler;
645
	}
646
647
648
	/********************************************************************************
649
	 *                                  TREE VIEW                                   *
650
	 ********************************************************************************/
651
652
653
	/**
654
	 * Set all tree nodes to be opened by default. Works only along with non-dynamic tree
655
	 * @param boolean $tree_nodes_opened
656
	 * @return static
657
	 */
658
	public function setTreeOpenAllNodes($tree_nodes_opened = TRUE)
659
	{
660
		if (!$this->isTreeView()) {
661
			throw new DataGridException('Please call setTreeview before calling setTreeOpenAllNodes');
662
		}
663
		if ($this->isTreeDynamic()) {
664
			throw new DataGridException('Open all nodes can only be used for non-dynamic trees');
665
		}
666
		$this->tree_nodes_opened = $tree_nodes_opened;
667
668
		return $this;
669
	}
670
671
672
	/**
673
	 * Is open all nodes turned on?
674
	 * @return boolean
675
	 */
676
	public function isTreeOpenAllNodes()
677
	{
678
		return (bool) $this->tree_nodes_opened;
679
	}
680
681
682
	/**
683
	 * Is tree view set?
684
	 * @return boolean
685
	 */
686
	public function isTreeView()
687
	{
688
		return (bool) $this->tree_view_children_callback;
689
	}
690
691
692
	/**
693
	 * Is tree view dynamic?
694
	 * @return boolean
695
	 */
696
	public function isTreeDynamic()
697
	{
698
		return (bool) $this->tree_dynamic;
699
	}
700
701
702
	/**
703
	 * Setting tree view
704
	 * @param callable $get_children_callback
705
	 * @param string|callable $tree_view_has_children_column
706
	 * @param  boolean $tree_dynamic should tree be loaded dynamically?
707
	 * @return static
708
	 */
709
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children', $tree_dynamic = TRUE)
710
	{
711
		if (!is_callable($get_children_callback)) {
712
			throw new DataGridException(
713
				'Parameters to method DataGrid::setTreeView must be of type callable'
714
			);
715
		}
716
717
		if (is_callable($tree_view_has_children_column)) {
718
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
719
			$tree_view_has_children_column = NULL;
720
		}
721
722
		$this->tree_dynamic = $tree_dynamic;
723
		$this->tree_view_children_callback = $get_children_callback;
724
		$this->tree_view_has_children_column = $tree_view_has_children_column;
725
726
		/**
727
		 * TUrn off pagination
728
		 */
729
		$this->setPagination(FALSE);
730
731
		/**
732
		 * Set tree view template file
733
		 */
734
		if (!$this->template_file) {
735
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
736
		}
737
738
		return $this;
739
	}
740
741
742
	/**
743
	 * Is tree view children callback set?
744
	 * @return boolean
745
	 */
746
	public function hasTreeViewChildrenCallback()
747
	{
748
		return is_callable($this->tree_view_has_children_callback);
749
	}
750
751
752
	/**
753
	 * @param  mixed $item
754
	 * @return boolean
755
	 */
756
	public function treeViewChildrenCallback($item)
757
	{
758
		return call_user_func($this->tree_view_has_children_callback, $item);
759
	}
760
761
762
	/**
763
	 * Returns array of \Ublaboo\DataGrid\Row for all children of given parent
764
	 * @param mixed $parent
765
	 * @return array
766
	 */
767
	public function getTreeChildrenRows($parent)
768
	{
769
		$rows = [];
770
		foreach (call_user_func($this->tree_view_children_callback, $parent) as $item) {
771
			$rows[] = new Row($this, $item, $this->primary_key);
772
		}
773
		return $rows;
774
	}
775
776
777
	/********************************************************************************
778
	 *                                    COLUMNS                                   *
779
	 ********************************************************************************/
780
781
782
	/**
783
	 * Add text column with no other formating
784
	 * @param  string      $key
785
	 * @param  string      $name
786
	 * @param  string|null $column
787
	 * @return Column\ColumnText
788
	 */
789
	public function addColumnText($key, $name, $column = NULL)
790
	{
791
		$this->addColumnCheck($key);
792
		$column = $column ?: $key;
793
794
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
795
	}
796
797
798
	/**
799
	 * Add column with link
800
	 * @param  string      $key
801
	 * @param  string      $name
802
	 * @param  string|null $column
803
	 * @return Column\ColumnLink
804
	 */
805
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
806
	{
807
		$this->addColumnCheck($key);
808
		$column = $column ?: $key;
809
		$href = $href ?: $key;
810
811
		if (NULL === $params) {
812
			$params = [$this->primary_key];
813
		}
814
815
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
816
	}
817
818
819
	/**
820
	 * Add column with possible number formating
821
	 * @param  string      $key
822
	 * @param  string      $name
823
	 * @param  string|null $column
824
	 * @return Column\ColumnNumber
825
	 */
826
	public function addColumnNumber($key, $name, $column = NULL)
827
	{
828
		$this->addColumnCheck($key);
829
		$column = $column ?: $key;
830
831
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
832
	}
833
834
835
	/**
836
	 * Add column with date formating
837
	 * @param  string      $key
838
	 * @param  string      $name
839
	 * @param  string|null $column
840
	 * @return Column\ColumnDateTime
841
	 */
842
	public function addColumnDateTime($key, $name, $column = NULL)
843
	{
844
		$this->addColumnCheck($key);
845
		$column = $column ?: $key;
846
847
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
848
	}
849
850
851
	/**
852
	 * Add column status
853
	 * @param  string      $key
854
	 * @param  string      $name
855
	 * @param  string|null $column
856
	 * @return Column\ColumnStatus
857
	 */
858
	public function addColumnStatus($key, $name, $column = NULL)
859
	{
860
		$this->addColumnCheck($key);
861
		$column = $column ?: $key;
862
863
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
864
	}
865
866
867
	/**
868
	 * @param string $key
869
	 * @param Column\Column $column
870
	 * @return Column\Column
871
	 */
872
	protected function addColumn($key, Column\Column $column)
873
	{
874
		$this->onColumnAdd($key, $column);
0 ignored issues
show
Unused Code introduced by
The call to DataGrid::onColumnAdd() has too many arguments starting with $key.

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...
875
876
		$this->columns_visibility[$key] = [
877
			'visible' => TRUE,
878
			'name' => $column->getName()
879
		];
880
881
		return $this->columns[$key] = $column;
882
	}
883
884
885
	/**
886
	 * Return existing column
887
	 * @param  string $key
888
	 * @return Column\Column
889
	 * @throws DataGridException
890
	 */
891
	public function getColumn($key)
892
	{
893
		if (!isset($this->columns[$key])) {
894
			throw new DataGridException("There is no column at key [$key] defined.");
895
		}
896
897
		return $this->columns[$key];
898
	}
899
900
901
	/**
902
	 * Remove column
903
	 * @param string $key
904
	 * @return void
905
	 */
906
	public function removeColumn($key)
907
	{
908
		unset($this->columns[$key]);
909
	}
910
911
912
	/**
913
	 * Check whether given key already exists in $this->columns
914
	 * @param  string $key
915
	 * @throws DataGridException
916
	 */
917
	protected function addColumnCheck($key)
918
	{
919
		if (isset($this->columns[$key])) {
920
			throw new DataGridException("There is already column at key [$key] defined.");
921
		}
922
	}
923
924
925
	/********************************************************************************
926
	 *                                    ACTIONS                                   *
927
	 ********************************************************************************/
928
929
930
	/**
931
	 * Create action
932
	 * @param string     $key
933
	 * @param string     $name
934
	 * @param string     $href
935
	 * @param array|null $params
936
	 * @return Column\Action
937
	 */
938
	public function addAction($key, $name, $href = NULL, array $params = NULL)
939
	{
940
		$this->addActionCheck($key);
941
		$href = $href ?: $key;
942
943
		if (NULL === $params) {
944
			$params = [$this->primary_key];
945
		}
946
947
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
948
	}
949
950
951
	/**
952
	 * Create action callback
953
	 * @param string     $key
954
	 * @param string     $name
955
	 * @return Column\Action
956
	 */
957
	public function addActionCallback($key, $name, $callback = NULL)
958
	{
959
		$this->addActionCheck($key);
960
		$params = ['__id' => $this->primary_key];
961
962
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
963
964
		if ($callback) {
965
			if (!is_callable($callback)) {
966
				throw new DataGridException('ActionCallback callback has to be callable.');
967
			}
968
969
			$action->onClick[] = $callback;
970
		}
971
972
		return $action;
973
	}
974
975
976
	/**
977
	 * Get existing action
978
	 * @param  string       $key
979
	 * @return Column\Action
980
	 * @throws DataGridException
981
	 */
982
	public function getAction($key)
983
	{
984
		if (!isset($this->actions[$key])) {
985
			throw new DataGridException("There is no action at key [$key] defined.");
986
		}
987
988
		return $this->actions[$key];
989
	}
990
991
992
	/**
993
	 * Remove action
994
	 * @param string $key
995
	 * @return void
996
	 */
997
	public function removeAction($key)
998
	{
999
		unset($this->actions[$key]);
1000
	}
1001
1002
1003
	/**
1004
	 * Check whether given key already exists in $this->filters
1005
	 * @param  string $key
1006
	 * @throws DataGridException
1007
	 */
1008
	protected function addActionCheck($key)
1009
	{
1010
		if (isset($this->actions[$key])) {
1011
			throw new DataGridException("There is already action at key [$key] defined.");
1012
		}
1013
	}
1014
1015
1016
	/********************************************************************************
1017
	 *                                    FILTERS                                   *
1018
	 ********************************************************************************/
1019
1020
1021
	/**
1022
	 * Add filter fot text search
1023
	 * @param string       $key
1024
	 * @param string       $name
1025
	 * @param array|string $columns
1026
	 * @return Filter\FilterText
1027
	 * @throws DataGridException
1028
	 */
1029
	public function addFilterText($key, $name, $columns = NULL)
1030
	{
1031
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
1032
1033
		if (!is_array($columns)) {
1034
			throw new DataGridException("Filter Text can except only array or string.");
1035
		}
1036
1037
		$this->addFilterCheck($key);
1038
1039
		return $this->filters[$key] = new Filter\FilterText($key, $name, $columns);
1040
	}
1041
1042
1043
	/**
1044
	 * Add select box filter
1045
	 * @param string $key
1046
	 * @param string $name
1047
	 * @param array  $options
1048
	 * @param string $column
1049
	 * @return Filter\FilterSelect
1050
	 * @throws DataGridException
1051
	 */
1052 View Code Duplication
	public function addFilterSelect($key, $name, array $options, $column = NULL)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1053
	{
1054
		$column = $column ?: $key;
1055
1056
		if (!is_string($column)) {
1057
			throw new DataGridException("Filter Select can only filter in one column.");
1058
		}
1059
1060
		$this->addFilterCheck($key);
1061
1062
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
1063
	}
1064
1065
1066
	/**
1067
	 * Add multi select box filter
1068
	 * @param string $key
1069
	 * @param string $name
1070
	 * @param array  $options
1071
	 * @param string $column
1072
	 * @return Filter\FilterSelect
1073
	 * @throws DataGridException
1074
	 */
1075
	public function addFilterMultiSelect($key, $name, array $options, $column = NULL)
1076
	{
1077
		$column = $column ?: $key;
1078
1079
		if (!is_string($column)) {
1080
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1081
		}
1082
1083
		$this->addFilterCheck($key);
1084
1085
		return $this->filters[$key] = new Filter\FilterMultiSelect($key, $name, $options, $column);
1086
	}
1087
1088
1089
	/**
1090
	 * Add datepicker filter
1091
	 * @param string $key
1092
	 * @param string $name
1093
	 * @param string $column
1094
	 * @return Filter\FilterDate
1095
	 * @throws DataGridException
1096
	 */
1097
	public function addFilterDate($key, $name, $column = NULL)
1098
	{
1099
		$column = $column ?: $key;
1100
1101
		if (!is_string($column)) {
1102
			throw new DataGridException("FilterDate can only filter in one column.");
1103
		}
1104
1105
		$this->addFilterCheck($key);
1106
1107
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
1108
	}
1109
1110
1111
	/**
1112
	 * Add range filter (from - to)
1113
	 * @param string $key
1114
	 * @param string $name
1115
	 * @param string $column
1116
	 * @return Filter\FilterRange
1117
	 * @throws DataGridException
1118
	 */
1119 View Code Duplication
	public function addFilterRange($key, $name, $column = NULL, $name_second = '-')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1120
	{
1121
		$column = $column ?: $key;
1122
1123
		if (!is_string($column)) {
1124
			throw new DataGridException("FilterRange can only filter in one column.");
1125
		}
1126
1127
		$this->addFilterCheck($key);
1128
1129
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
1130
	}
1131
1132
1133
	/**
1134
	 * Add datepicker filter (from - to)
1135
	 * @param string $key
1136
	 * @param string $name
1137
	 * @param string $column
1138
	 * @return Filter\FilterDateRange
1139
	 * @throws DataGridException
1140
	 */
1141 View Code Duplication
	public function addFilterDateRange($key, $name, $column = NULL, $name_second = '-')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1142
	{
1143
		$column = $column ?: $key;
1144
1145
		if (!is_string($column)) {
1146
			throw new DataGridException("FilterDateRange can only filter in one column.");
1147
		}
1148
1149
		$this->addFilterCheck($key);
1150
1151
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
1152
	}
1153
1154
1155
	/**
1156
	 * Check whether given key already exists in $this->filters
1157
	 * @param  string $key
1158
	 * @throws DataGridException
1159
	 */
1160
	protected function addFilterCheck($key)
1161
	{
1162
		if (isset($this->filters[$key])) {
1163
			throw new DataGridException("There is already action at key [$key] defined.");
1164
		}
1165
	}
1166
1167
1168
	/**
1169
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1170
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1171
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1172
	 */
1173
	public function assableFilters()
1174
	{
1175
		foreach ($this->filter as $key => $value) {
1176
			if (!isset($this->filters[$key])) {
1177
				$this->deleteSesssionData($key);
1178
1179
				continue;
1180
			}
1181
1182
			if (is_array($value) || $value instanceof \Traversable) {
1183
				if (!ArraysHelper::testEmpty($value)) {
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type object<Traversable>; however, Ublaboo\DataGrid\Utils\ArraysHelper::testEmpty() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1184
					$this->filters[$key]->setValue($value);
1185
				}
1186
			} else {
1187
				if ($value !== '' && $value !== NULL) {
1188
					$this->filters[$key]->setValue($value);
1189
				}
1190
			}
1191
		}
1192
1193
		foreach ($this->columns as $column) {
1194
			if (isset($this->sort[$column->getSortingColumn()])) {
1195
				$column->setSort($this->sort);
1196
			}
1197
		}
1198
1199
		return $this->filters;
1200
	}
1201
1202
1203
	/**
1204
	 * Remove filter
1205
	 * @param string $key
1206
	 * @return void
1207
	 */
1208
	public function removeFilter($key)
1209
	{
1210
		unset($this->filters[$key]);
1211
	}
1212
1213
1214
	/**
1215
	 * Get defined filter
1216
	 * @param  string $key
1217
	 * @return Filter\Filter
1218
	 */
1219
	public function getFilter($key)
1220
	{
1221
		if (!isset($this->filters[$key])) {
1222
			throw new DataGridException("Filter [{$key}] is not defined");
1223
		}
1224
1225
		return $this->filters[$key];
1226
	}
1227
1228
1229
	/********************************************************************************
1230
	 *                                  FILTERING                                   *
1231
	 ********************************************************************************/
1232
1233
1234
	/**
1235
	 * Is filter active?
1236
	 * @return boolean
1237
	 */
1238
	public function isFilterActive()
1239
	{
1240
		$is_filter = ArraysHelper::testTruthy($this->filter);
1241
1242
		return ($is_filter) || $this->force_filter_active;
1243
	}
1244
1245
1246
	/**
1247
	 * Tell that filter is active from whatever reasons
1248
	 * return static
1249
	 */
1250
	public function setFilterActive()
1251
	{
1252
		$this->force_filter_active = TRUE;
1253
1254
		return $this;
1255
	}
1256
1257
1258
	/**
1259
	 * Set filter values (force - overwrite user data)
1260
	 * @param array $filter
1261
	 * @return static
1262
	 */
1263
	public function setFilter(array $filter)
1264
	{
1265
		$this->filter = $filter;
1266
1267
		$this->saveSessionData('_grid_has_filtered', 1);
1268
1269
		return $this;
1270
	}
1271
1272
1273
	/**
1274
	 * If we want to sent some initial filter
1275
	 * @param array $filter
0 ignored issues
show
Documentation introduced by
There is no parameter named $filter. Did you maybe mean $default_filter?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1276
	 * @param bool  $use_on_reset
1277
	 * @return static
1278
	 */
1279
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1280
	{
1281
		foreach ($default_filter as $key => $value) {
1282
			$filter = $this->getFilter($key);
1283
1284
			if (!$filter) {
1285
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1286
			}
1287
1288
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1289
				throw new DataGridException(
1290
					"Default value of filter [$key] - MultiSelect has to be an array"
1291
				);
1292
			}
1293
1294
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1295
				if (!is_array($value)) {
1296
					throw new DataGridException(
1297
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1298
					);
1299
				}
1300
1301
				$temp = $value;
1302
				unset($temp['from'], $temp['to']);
1303
1304
				if (!empty($temp)) {
1305
					throw new DataGridException(
1306
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1307
					);
1308
				}
1309
			}
1310
		}
1311
1312
		$this->default_filter = $default_filter;
1313
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1314
1315
		return $this;
1316
	}
1317
1318
1319
	/**
1320
	 * User may set default filter, find it
1321
	 * @return void
1322
	 */
1323
	public function findDefaultFilter()
1324
	{
1325
		if (!empty($this->filter)) {
1326
			return;
1327
		}
1328
1329
		if ($this->getSessionData('_grid_has_filtered')) {
1330
			return;
1331
		}
1332
1333
		if (!empty($this->default_filter)) {
1334
			$this->filter = $this->default_filter;
1335
		}
1336
1337
		foreach ($this->filter as $key => $value) {
1338
			$this->saveSessionData($key, $value);
1339
		}
1340
	}
1341
1342
1343
	/**
1344
	 * FilterAndGroupAction form factory
1345
	 * @return Form
1346
	 */
1347
	public function createComponentFilter()
1348
	{
1349
		$form = new Form($this, 'filter');
1350
1351
		$form->setMethod('get');
1352
1353
		$form->setTranslator($this->getTranslator());
1354
1355
		/**
1356
		 * InlineEdit part
1357
		 */
1358
		$inline_edit_container = $form->addContainer('inline_edit');
1359
1360 View Code Duplication
		if ($this->inlineEdit instanceof InlineEdit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1361
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1362
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1363
				->setValidationScope(FALSE);
1364
1365
			$this->inlineEdit->onControlAdd($inline_edit_container);
1366
		}
1367
1368
		/**
1369
		 * InlineAdd part
1370
		 */
1371
		$inline_add_container = $form->addContainer('inline_add');
1372
1373 View Code Duplication
		if ($this->inlineAdd instanceof InlineEdit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1374
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1375
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1376
				->setValidationScope(FALSE)
1377
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1378
1379
			$this->inlineAdd->onControlAdd($inline_add_container);
1380
		}
1381
1382
		/**
1383
		 * ItemDetail form part
1384
		 */
1385
		$items_detail_form = $this->getItemDetailForm();
1386
1387
		if ($items_detail_form instanceof Nette\Forms\Container) {
1388
			$form['items_detail_form'] = $items_detail_form;
1389
		}
1390
1391
		/**
1392
		 * Filter part
1393
		 */
1394
		$filter_container = $form->addContainer('filter');
1395
1396
		foreach ($this->filters as $filter) {
1397
			$filter->addToFormContainer($filter_container);
1398
		}
1399
1400
		/**
1401
		 * Group action part
1402
		 */
1403
		$group_action_container = $form->addContainer('group_action');
1404
1405
		if ($this->hasGroupActions()) {
1406
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1407
		}
1408
1409
		$form->setDefaults(['filter' => $this->filter]);
1410
1411
		/**
1412
		 * Per page part
1413
		 */
1414
		$form->addSelect('per_page', '', $this->getItemsPerPageList());
1415
1416
		if (!$form->isSubmitted()) {
1417
			$form['per_page']->setValue($this->getPerPage());
1418
		}
1419
1420
		$form->addSubmit('per_page_submit', '');
1421
		
1422
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1423
1424
		return $form;
1425
	}
1426
1427
1428
	/**
1429
	 * Set $this->filter values after filter form submitted
1430
	 * @param  Form $form
1431
	 * @return void
1432
	 */
1433
	public function filterSucceeded(Form $form)
1434
	{
1435
		if ($this->snippets_set) {
1436
			return;
1437
		}
1438
1439
		$values = $form->getValues();
1440
1441
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1442
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1443
				return;
1444
			}
1445
		}
1446
1447
		/**
1448
		 * Per page
1449
		 */
1450
		if (isset($form['per_page_submit']) && $form['per_page_submit']->isSubmittedBy()) {
1451
			/**
1452
			 * Session stuff
1453
			 */
1454
			$this->saveSessionData('_grid_per_page', $values->per_page);
1455
1456
			/**
1457
			 * Other stuff
1458
			 */
1459
			$this->per_page = $values->per_page;
1460
			$this->reload();
1461
1462
			return;
1463
		}
1464
1465
		/**
1466
		 * Inline edit
1467
		 */
1468
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1469
			$edit = $form['inline_edit'];
1470
1471
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1472
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1473
				$primary_where_column = $form->getHttpData(
1474
					Form::DATA_LINE,
1475
					'inline_edit[_primary_where_column]'
1476
				);
1477
1478 View Code Duplication
				if ($edit['submit']->isSubmittedBy()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1479
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1480
1481
					if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1482
						$this->getPresenter()->payload->_datagrid_inline_edited = $id;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1483
					}
1484
				}
1485
1486
				$this->redrawItem($id, $primary_where_column);
1487
1488
				return;
1489
			}
1490
		}
1491
1492
		/**
1493
		 * Inline add
1494
		 */
1495
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1496
			$add = $form['inline_add'];
1497
1498
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1499 View Code Duplication
				if ($add['submit']->isSubmittedBy()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1500
					$this->inlineAdd->onSubmit($values->inline_add);
0 ignored issues
show
Bug introduced by
The call to onSubmit() misses a required argument $values.

This check looks for function calls that miss required arguments.

Loading history...
1501
1502
					if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1503
						$this->getPresenter()->payload->_datagrid_inline_added = TRUE;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1504
					}
1505
				}
1506
1507
				return;
1508
			}
1509
		}
1510
1511
		/**
1512
		 * Filter itself
1513
		 */
1514
		$values = $values['filter'];
1515
1516
		foreach ($values as $key => $value) {
1517
			/**
1518
			 * Session stuff
1519
			 */
1520
			$this->saveSessionData($key, $value);
1521
1522
			/**
1523
			 * Other stuff
1524
			 */
1525
			$this->filter[$key] = $value;
1526
		}
1527
1528
		if (!empty($values)) {
1529
			$this->saveSessionData('_grid_has_filtered', 1);
1530
		}
1531
1532
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1533
			$this->getPresenter()->payload->_datagrid_sort = [];
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1534
1535
			foreach ($this->columns as $key => $column) {
1536
				if ($column->isSortable()) {
1537
					$this->getPresenter()->payload->_datagrid_sort[$key] = $this->link('sort!', [
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1538
						'sort' => $column->getSortNext()
1539
					]);
1540
				}
1541
			}
1542
		}
1543
1544
		$this->reload();
1545
	}
1546
1547
1548
	/**
1549
	 * Should be datagrid filters rendered separately?
1550
	 * @param boolean $out
1551
	 * @return static
1552
	 */
1553
	public function setOuterFilterRendering($out = TRUE)
1554
	{
1555
		$this->outer_filter_rendering = (bool) $out;
1556
1557
		return $this;
1558
	}
1559
1560
1561
	/**
1562
	 * Are datagrid filters rendered separately?
1563
	 * @return boolean
1564
	 */
1565
	public function hasOuterFilterRendering()
1566
	{
1567
		return $this->outer_filter_rendering;
1568
	}
1569
1570
1571
	/**
1572
	 * Try to restore session stuff
1573
	 * @return void
1574
	 */
1575
	public function findSessionValues()
1576
	{
1577
		if ($this->filter || ($this->page != 1) || !empty($this->sort) || $this->per_page) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->filter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1578
			return;
1579
		}
1580
1581
		if (!$this->remember_state) {
1582
			return;
1583
		}
1584
1585
		if ($page = $this->getSessionData('_grid_page')) {
1586
			$this->page = $page;
1587
		}
1588
1589
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1590
			$this->per_page = $per_page;
1591
		}
1592
1593
		if ($sort = $this->getSessionData('_grid_sort')) {
1594
			$this->sort = $sort;
0 ignored issues
show
Documentation Bug introduced by
It seems like $sort of type * is incompatible with the declared type array of property $sort.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1595
		}
1596
1597
		foreach ($this->getSessionData() as $key => $value) {
1598
			$other_session_keys = [
1599
				'_grid_per_page',
1600
				'_grid_sort',
1601
				'_grid_page',
1602
				'_grid_has_filtered',
1603
				'_grid_hidden_columns',
1604
				'_grid_hidden_columns_manipulated'
1605
			];
1606
1607
			if (!in_array($key, $other_session_keys)) {
1608
				$this->filter[$key] = $value;
1609
			}
1610
		}
1611
1612
		/**
1613
		 * When column is sorted via custom callback, apply it
1614
		 */
1615
		if (empty($this->sort_callback) && !empty($this->sort)) {
1616
			foreach ($this->sort as $key => $order) {
1617
				$column = $this->getColumn($key);
1618
1619
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1620
					$this->sort_callback = $column->getSortableCallback();
1621
				}
1622
			}
1623
		}
1624
	}
1625
1626
1627
	/********************************************************************************
1628
	 *                                    EXPORTS                                   *
1629
	 ********************************************************************************/
1630
1631
1632
	/**
1633
	 * Add export of type callback
1634
	 * @param string $text
1635
	 * @param callable $callback
1636
	 * @param boolean $filtered
1637
	 * @return Export\Export
1638
	 */
1639
	public function addExportCallback($text, $callback, $filtered = FALSE)
1640
	{
1641
		if (!is_callable($callback)) {
1642
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1643
		}
1644
1645
		return $this->addToExports(new Export\Export($text, $callback, $filtered));
1646
	}
1647
1648
1649
	/**
1650
	 * Add already implemented csv export
1651
	 * @param string      $text
1652
	 * @param string      $csv_file_name
1653
	 * @param string|null $output_encoding
1654
	 * @param string|null $delimiter
1655
	 * @return Export\Export
1656
	 */
1657
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1658
	{
1659
		return $this->addToExports(new Export\ExportCsv(
1660
			$text,
1661
			$csv_file_name,
1662
			FALSE,
1663
			$output_encoding,
1664
			$delimiter
1665
		));
1666
	}
1667
1668
1669
	/**
1670
	 * Add already implemented csv export, but for filtered data
1671
	 * @param string      $text
1672
	 * @param string      $csv_file_name
1673
	 * @param string|null $output_encoding
1674
	 * @param string|null $delimiter
1675
	 * @return Export\Export
1676
	 */
1677
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1678
	{
1679
		return $this->addToExports(new Export\ExportCsv(
1680
			$text,
1681
			$csv_file_name,
1682
			TRUE,
1683
			$output_encoding,
1684
			$delimiter
1685
		));
1686
	}
1687
1688
1689
	/**
1690
	 * Add export to array
1691
	 * @param Export\Export $export
1692
	 * @return Export\Export
1693
	 */
1694
	protected function addToExports(Export\Export $export)
1695
	{
1696
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1697
1698
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1699
1700
		return $this->exports[$id] = $export;
1701
	}
1702
1703
1704
	public function resetExportsLinks()
1705
	{
1706
		foreach ($this->exports as $id => $export) {
1707
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1708
		}
1709
	}
1710
1711
1712
	/********************************************************************************
1713
	 *                                 GROUP ACTIONS                                *
1714
	 ********************************************************************************/
1715
1716
1717
	/**
1718
	 * Alias for add group select action
1719
	 * @param string $title
1720
	 * @param array  $options
1721
	 * @return GroupAction\GroupAction
1722
	 */
1723
	public function addGroupAction($title, $options = [])
1724
	{
1725
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1726
	}
1727
1728
	/**
1729
	 * Add group action (select box)
1730
	 * @param string $title
1731
	 * @param array  $options
1732
	 * @return GroupAction\GroupAction
1733
	 */
1734
	public function addGroupSelectAction($title, $options = [])
1735
	{
1736
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1737
	}
1738
1739
	/**
1740
	 * Add group action (text input)
1741
	 * @param string $title
1742
	 * @return GroupAction\GroupAction
1743
	 */
1744
	public function addGroupTextAction($title)
1745
	{
1746
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1747
	}
1748
1749
	/**
1750
	 * Get collection of all group actions
1751
	 * @return GroupAction\GroupActionCollection
1752
	 */
1753
	public function getGroupActionCollection()
1754
	{
1755
		if (!$this->group_action_collection) {
1756
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
1757
		}
1758
1759
		return $this->group_action_collection;
1760
	}
1761
1762
1763
	/**
1764
	 * Has datagrid some group actions?
1765
	 * @return boolean
1766
	 */
1767
	public function hasGroupActions()
1768
	{
1769
		return (bool) $this->group_action_collection;
1770
	}
1771
1772
1773
	/********************************************************************************
1774
	 *                                   HANDLERS                                   *
1775
	 ********************************************************************************/
1776
1777
1778
	/**
1779
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1780
	 * @param  int  $page
1781
	 * @return void
1782
	 */
1783
	public function handlePage($page)
1784
	{
1785
		/**
1786
		 * Session stuff
1787
		 */
1788
		$this->page = $page;
1789
		$this->saveSessionData('_grid_page', $page);
1790
1791
		$this->reload(['table']);
1792
	}
1793
1794
1795
	/**
1796
	 * Handler for sorting
1797
	 * @param array $sort
1798
	 * @return void
1799
	 */
1800
	public function handleSort(array $sort)
1801
	{
1802
		$new_sort = [];
1803
1804
		/**
1805
		 * Find apropirate column
1806
		 */
1807
		foreach ($sort as $key => $value) {
1808
			if (empty($this->columns[$key])) {
1809
				throw new DataGridException("Column <$key> not found");
1810
			}
1811
1812
			$column = $this->columns[$key];
1813
			$new_sort = [$column->getSortingColumn() => $value];
1814
1815
			/**
1816
			 * Pagination may be reseted after sorting
1817
			 */
1818
			if ($column->sortableResetPagination()) {
1819
				$this->page = 1;
1820
				$this->saveSessionData('_grid_page', 1);
1821
			}
1822
1823
			/**
1824
			 * Custom sorting callback may be applied
1825
			 */
1826
			if ($column->getSortableCallback()) {
1827
				$this->sort_callback = $column->getSortableCallback();
1828
			}
1829
		}
1830
1831
		/**
1832
		 * Session stuff
1833
		 */
1834
		$this->sort = $new_sort;
1835
		$this->saveSessionData('_grid_sort', $this->sort);
1836
1837
		$this->reload(['table']);
1838
	}
1839
1840
1841
	/**
1842
	 * handler for reseting the filter
1843
	 * @return void
1844
	 */
1845
	public function handleResetFilter()
1846
	{
1847
		/**
1848
		 * Session stuff
1849
		 */
1850
		$this->deleteSesssionData('_grid_page');
1851
1852
		if ($this->default_filter_use_on_reset) {
1853
			$this->deleteSesssionData('_grid_has_filtered');
1854
		}
1855
1856
		foreach ($this->getSessionData() as $key => $value) {
1857
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page', '_grid_has_filtered'])) {
1858
				$this->deleteSesssionData($key);
1859
			}
1860
		}
1861
1862
		$this->filter = [];
1863
1864
		$this->reload(['grid']);
1865
	}
1866
1867
1868
	/**
1869
	 * Handler for export
1870
	 * @param  int $id Key for particular export class in array $this->exports
1871
	 * @return void
1872
	 */
1873
	public function handleExport($id)
1874
	{
1875
		if (!isset($this->exports[$id])) {
1876
			throw new Nette\Application\ForbiddenRequestException;
1877
		}
1878
1879
		if (!empty($this->columns_export_order)) {
1880
			$this->setColumnsOrder($this->columns_export_order);
1881
		}
1882
1883
		$export = $this->exports[$id];
1884
1885
		if ($export->isFiltered()) {
1886
			$sort      = $this->sort;
0 ignored issues
show
Unused Code introduced by
$sort is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1887
			$filter    = $this->assableFilters();
1888
		} else {
1889
			$sort      = [$this->primary_key => 'ASC'];
0 ignored issues
show
Unused Code introduced by
$sort is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1890
			$filter    = [];
1891
		}
1892
1893
		if (NULL === $this->dataModel) {
1894
			throw new DataGridException('You have to set a data source first.');
1895
		}
1896
1897
		$rows = [];
1898
1899
		$items = Nette\Utils\Callback::invokeArgs(
1900
			[$this->dataModel, 'filterData'], [
1901
				NULL,
1902
				new Sorting($this->sort, $this->sort_callback),
1903
				$filter
1904
			]
1905
		);
1906
1907
		foreach ($items as $item) {
1908
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1909
		}
1910
1911
		if ($export instanceof Export\ExportCsv) {
1912
			$export->invoke($rows, $this);
1913
		} else {
1914
			$export->invoke($items, $this);
1915
		}
1916
1917
		if ($export->isAjax()) {
1918
			$this->reload();
1919
		}
1920
	}
1921
1922
1923
	/**
1924
	 * Handler for getting children of parent item (e.g. category)
1925
	 * @param  int $parent
1926
	 * @return void
1927
	 */
1928
	public function handlegetChildren($parent)
1929
	{
1930
		$this->setDataSource(
1931
			call_user_func($this->tree_view_children_callback, $parent)
1932
		);
1933
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1934
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1935
			$this->getPresenter()->payload->_datagrid_tree = $parent;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1936
1937
			$this->redrawControl('includedTreeRows');
1938
			$this->redrawControl('items');
1939
1940
			$this->onRedraw();
1941
		} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
1942
			//$this->getPresenter()->redirect('this');
0 ignored issues
show
Unused Code Comprehensibility introduced by
82% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1943
		}
1944
	}
1945
1946
1947
	/**
1948
	 * Handler for getting item detail
1949
	 * @param  mixed $id
1950
	 * @return void
1951
	 */
1952
	public function handleGetItemDetail($id)
1953
	{
1954
		$this->getTemplate()->add('toggle_detail', $id);
1955
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1956
1957
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1958
			$this->getPresenter()->payload->_datagrid_toggle_detail = $id;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1959
			$this->redrawControl('items');
1960
1961
			$this->onRedraw();
1962
		} else {
1963
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1964
		}
1965
	}
1966
1967
1968
	/**
1969
	 * Handler for inline editing
1970
	 * @param  mixed $id
1971
	 * @param  mixed $key
1972
	 * @return void
1973
	 */
1974
	public function handleEdit($id, $key)
1975
	{
1976
		$column = $this->getColumn($key);
1977
		$value = $this->getPresenter()->getRequest()->getPost('value');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method getRequest() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1978
1979
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1980
	}
1981
1982
1983
	/**
1984
	 * Redraw $this
1985
	 * @return void
1986
	 */
1987
	public function reload($snippets = [])
1988
	{
1989
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1990
			$this->redrawControl('tbody');
1991
			$this->redrawControl('pagination');
1992
1993
			/**
1994
			 * manualy reset exports links...
1995
			 */
1996
			$this->resetExportsLinks();
1997
			$this->redrawControl('exports');
1998
1999
			foreach ($snippets as $snippet) {
2000
				$this->redrawControl($snippet);
2001
			}
2002
2003
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2004
2005
			$this->onRedraw();
2006
		} else {
2007
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2008
		}
2009
	}
2010
2011
2012
	/**
2013
	 * Handler for column status
2014
	 * @param  string $id
2015
	 * @param  string $key
2016
	 * @param  string $value
2017
	 * @return void
2018
	 */
2019
	public function handleChangeStatus($id, $key, $value)
2020
	{
2021
		if (empty($this->columns[$key])) {
2022
			throw new DataGridException("ColumnStatus[$key] does not exist");
2023
		}
2024
2025
		$this->columns[$key]->onChange($id, $value);
2026
	}
2027
2028
2029
	/**
2030
	 * Redraw just one row via ajax
2031
	 * @param  int   $id
2032
	 * @param  mixed $primary_where_column
2033
	 * @return void
2034
	 */
2035
	public function redrawItem($id, $primary_where_column = NULL)
2036
	{
2037
		$this->snippets_set = TRUE;
2038
2039
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
2040
2041
		$this->redrawControl('items');
2042
		$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2043
2044
		$this->onRedraw();
2045
	}
2046
2047
2048
	/**
2049
	 * Tell datagrid to display all columns
2050
	 * @return void
2051
	 */
2052
	public function handleShowAllColumns()
2053
	{
2054
		$this->deleteSesssionData('_grid_hidden_columns');
2055
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2056
2057
		$this->redrawControl();
2058
2059
		$this->onRedraw();
2060
	}
2061
2062
2063
	/**
2064
	 * Tell datagrid to display default columns
2065
	 * @return void
2066
	 */
2067
	public function handleShowDefaultColumns()
2068
	{
2069
		$this->deleteSesssionData('_grid_hidden_columns');
2070
		$this->saveSessionData('_grid_hidden_columns_manipulated', FALSE);
2071
2072
		$this->redrawControl();
2073
2074
		$this->onRedraw();
2075
	}
2076
2077
2078
	/**
2079
	 * Reveal particular column
2080
	 * @param  string $column
2081
	 * @return void
2082
	 */
2083 View Code Duplication
	public function handleShowColumn($column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2084
	{
2085
		$columns = $this->getSessionData('_grid_hidden_columns');
2086
2087
		if (!empty($columns)) {
2088
			$pos = array_search($column, $columns);
2089
2090
			if ($pos !== FALSE) {
2091
				unset($columns[$pos]);
2092
			}
2093
		}
2094
2095
		$this->saveSessionData('_grid_hidden_columns', $columns);
2096
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2097
2098
		$this->redrawControl();
2099
2100
		$this->onRedraw();
2101
	}
2102
2103
2104
	/**
2105
	 * Notice datagrid to not display particular columns
2106
	 * @param  string $column
2107
	 * @return void
2108
	 */
2109 View Code Duplication
	public function handleHideColumn($column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2110
	{
2111
		/**
2112
		 * Store info about hiding a column to session
2113
		 */
2114
		$columns = $this->getSessionData('_grid_hidden_columns');
2115
2116
		if (empty($columns)) {
2117
			$columns = [$column];
2118
		} else if (!in_array($column, $columns)) {
2119
			array_push($columns, $column);
2120
		}
2121
2122
		$this->saveSessionData('_grid_hidden_columns', $columns);
2123
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2124
2125
		$this->redrawControl();
2126
2127
		$this->onRedraw();
2128
	}
2129
2130
2131
	public function handleActionCallback($__key, $__id)
2132
	{
2133
		$action = $this->getAction($__key);
2134
2135
		if (!($action instanceof Column\ActionCallback)) {
2136
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2137
		}
2138
2139
		$action->onClick($__id);
2140
	}
2141
2142
2143
	/********************************************************************************
2144
	 *                                  PAGINATION                                  *
2145
	 ********************************************************************************/
2146
2147
2148
	/**
2149
	 * Set options of select "items_per_page"
2150
	 * @param array $items_per_page_list
2151
	 * @return static
2152
	 */
2153
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
2154
	{
2155
		$this->items_per_page_list = $items_per_page_list;
2156
2157
		if ($include_all) {
2158
			$this->items_per_page_list[] = 'all';
2159
		}
2160
2161
		return $this;
2162
	}
2163
2164
2165
	/**
2166
	 * Paginator factory
2167
	 * @return Components\DataGridPaginator\DataGridPaginator
2168
	 */
2169
	public function createComponentPaginator()
2170
	{
2171
		/**
2172
		 * Init paginator
2173
		 */
2174
		$component = new Components\DataGridPaginator\DataGridPaginator(
2175
			$this->getTranslator(),
2176
			static::$icon_prefix
2177
		);
2178
		$paginator = $component->getPaginator();
2179
2180
		$paginator->setPage($this->page);
2181
		$paginator->setItemsPerPage($this->getPerPage());
2182
2183
		return $component;
2184
	}
2185
2186
2187
	/**
2188
	 * Get parameter per_page
2189
	 * @return int
2190
	 */
2191
	public function getPerPage()
2192
	{
2193
		$items_per_page_list = $this->getItemsPerPageList();
2194
2195
		$per_page = $this->per_page ?: reset($items_per_page_list);
2196
2197
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2198
			$per_page = reset($items_per_page_list);
2199
		}
2200
2201
		return $per_page;
2202
	}
2203
2204
2205
	/**
2206
	 * Get associative array of items_per_page_list
2207
	 * @return array
2208
	 */
2209
	public function getItemsPerPageList()
2210
	{
2211
		if (empty($this->items_per_page_list)) {
2212
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2213
		}
2214
2215
		$list = array_flip($this->items_per_page_list);
2216
2217
		foreach ($list as $key => $value) {
2218
			$list[$key] = $key;
2219
		}
2220
2221
		if (array_key_exists('all', $list)) {
2222
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2223
		}
2224
2225
		return $list;
2226
	}
2227
2228
2229
	/**
2230
	 * Order Grid to "be paginated"
2231
	 * @param bool $do
2232
	 * @return static
2233
	 */
2234
	public function setPagination($do)
2235
	{
2236
		$this->do_paginate = (bool) $do;
2237
2238
		return $this;
2239
	}
2240
2241
2242
	/**
2243
	 * Tell whether Grid is paginated
2244
	 * @return bool
2245
	 */
2246
	public function isPaginated()
2247
	{
2248
		return $this->do_paginate;
2249
	}
2250
2251
2252
	/**
2253
	 * Return current paginator class
2254
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2255
	 */
2256
	public function getPaginator()
2257
	{
2258
		if ($this->isPaginated() && $this->per_page !== 'all') {
2259
			return $this['paginator'];
2260
		}
2261
2262
		return NULL;
2263
	}
2264
2265
2266
	/********************************************************************************
2267
	 *                                     I18N                                     *
2268
	 ********************************************************************************/
2269
2270
2271
	/**
2272
	 * Set datagrid translator
2273
	 * @param Nette\Localization\ITranslator $translator
2274
	 * @return static
2275
	 */
2276
	public function setTranslator(Nette\Localization\ITranslator $translator)
2277
	{
2278
		$this->translator = $translator;
2279
2280
		return $this;
2281
	}
2282
2283
2284
	/**
2285
	 * Get translator for datagrid
2286
	 * @return Nette\Localization\ITranslator
2287
	 */
2288
	public function getTranslator()
2289
	{
2290
		if (!$this->translator) {
2291
			$this->translator = new Localization\SimpleTranslator;
2292
		}
2293
2294
		return $this->translator;
2295
	}
2296
2297
2298
	/********************************************************************************
2299
	 *                                 COLUMNS ORDER                                *
2300
	 ********************************************************************************/
2301
2302
2303
	/**
2304
	 * Set order of datagrid columns
2305
	 * @param array $order
2306
	 * @return static
2307
	 */
2308
	public function setColumnsOrder($order)
2309
	{
2310
		$new_order = [];
2311
2312
		foreach ($order as $key) {
2313
			if (isset($this->columns[$key])) {
2314
				$new_order[$key] = $this->columns[$key];
2315
			}
2316
		}
2317
2318
		if (sizeof($new_order) === sizeof($this->columns)) {
2319
			$this->columns = $new_order;
2320
		} else {
2321
			throw new DataGridException('When changing columns order, you have to specify all columns');
2322
		}
2323
2324
		return $this;
2325
	}
2326
2327
2328
	/**
2329
	 * Columns order may be different for export and normal grid
2330
	 * @param array $order
2331
	 */
2332
	public function setColumnsExportOrder($order)
2333
	{
2334
		$this->columns_export_order = (array) $order;
2335
	}
2336
2337
2338
	/********************************************************************************
2339
	 *                                SESSION & URL                                 *
2340
	 ********************************************************************************/
2341
2342
2343
	/**
2344
	 * Find some unique session key name
2345
	 * @return string
2346
	 */
2347
	public function getSessionSectionName()
2348
	{
2349
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2350
	}
2351
2352
2353
	/**
2354
	 * Should datagrid remember its filters/pagination/etc using session?
2355
	 * @param bool $remember
2356
	 * @return static
2357
	 */
2358
	public function setRememberState($remember = TRUE)
2359
	{
2360
		$this->remember_state = (bool) $remember;
2361
2362
		return $this;
2363
	}
2364
2365
2366
	/**
2367
	 * Should datagrid refresh url using history API?
2368
	 * @param bool $refresh
2369
	 * @return static
2370
	 */
2371
	public function setRefreshUrl($refresh = TRUE)
2372
	{
2373
		$this->refresh_url = (bool) $refresh;
2374
2375
2376
		return $this;
2377
	}
2378
2379
2380
	/**
2381
	 * Get session data if functionality is enabled
2382
	 * @param  string $key
2383
	 * @return mixed
2384
	 */
2385
	public function getSessionData($key = NULL, $default_value = NULL)
2386
	{
2387
		if (!$this->remember_state) {
2388
			return $key ? $default_value : [];
2389
		}
2390
2391
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2392
	}
2393
2394
2395
	/**
2396
	 * Save session data - just if it is enabled
2397
	 * @param  string $key
2398
	 * @param  mixed  $value
2399
	 * @return void
2400
	 */
2401
	public function saveSessionData($key, $value)
2402
	{
2403
		if ($this->remember_state) {
2404
			$this->grid_session->{$key} = $value;
2405
		}
2406
	}
2407
2408
2409
	/**
2410
	 * Delete session data
2411
	 * @return void
2412
	 */
2413
	public function deleteSesssionData($key)
2414
	{
2415
		unset($this->grid_session->{$key});
2416
	}
2417
2418
2419
	/********************************************************************************
2420
	 *                                  ITEM DETAIL                                 *
2421
	 ********************************************************************************/
2422
2423
2424
	/**
2425
	 * Get items detail parameters
2426
	 * @return array
2427
	 */
2428
	public function getItemsDetail()
2429
	{
2430
		return $this->items_detail;
2431
	}
2432
2433
2434
	/**
2435
	 * Items can have thair detail - toggled
2436
	 * @param mixed $detail callable|string|bool
2437
	 * @param bool|NULL $primary_where_column
2438
	 * @return Column\ItemDetail
2439
	 */
2440
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2441
	{
2442
		if ($this->isSortable()) {
2443
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2444
		}
2445
2446
		$this->items_detail = new Column\ItemDetail(
2447
			$this,
2448
			$primary_where_column ?: $this->primary_key
0 ignored issues
show
Bug introduced by
It seems like $primary_where_column ?: $this->primary_key can also be of type boolean; however, Ublaboo\DataGrid\Column\ItemDetail::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2449
		);
2450
2451
		if (is_string($detail)) {
2452
			/**
2453
			 * Item detail will be in separate template
2454
			 */
2455
			$this->items_detail->setType('template');
2456
			$this->items_detail->setTemplate($detail);
2457
2458
		} else if (is_callable($detail)) {
2459
			/**
2460
			 * Item detail will be rendered via custom callback renderer
2461
			 */
2462
			$this->items_detail->setType('renderer');
2463
			$this->items_detail->setRenderer($detail);
2464
2465
		} else if (TRUE === $detail) {
2466
			/**
2467
			 * Item detail will be rendered probably via block #detail
2468
			 */
2469
			$this->items_detail->setType('block');
2470
2471
		} else {
2472
			throw new DataGridException(
2473
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2474
			);
2475
		}
2476
2477
		return $this->items_detail;
2478
	}
2479
2480
2481
	/**
2482
	 * @param callable $callable_set_container 
2483
	 * @return static
2484
	 */
2485
	public function setItemsDetailForm(callable $callable_set_container)
2486
	{
2487
		if ($this->items_detail instanceof Column\ItemDetail) {
2488
			$this->items_detail->setForm(
2489
				new Utils\ItemDetailForm($callable_set_container)
2490
			);
2491
2492
			return $this;
2493
		}
2494
2495
		throw new DataGridException('Please set the ItemDetail first.');
2496
	}
2497
2498
2499
	/**
2500
	 * @return Nette\Forms\Container|NULL
2501
	 */
2502
	public function getItemDetailForm()
2503
	{
2504
		if ($this->items_detail instanceof Column\ItemDetail) {
2505
			return $this->items_detail->getForm();
2506
		}
2507
2508
		return NULL;
2509
	}
2510
2511
2512
	/********************************************************************************
2513
	 *                                ROW PRIVILEGES                                *
2514
	 ********************************************************************************/
2515
2516
2517
	/**
2518
	 * @param  callable $condition
2519
	 * @return void
2520
	 */
2521
	public function allowRowsGroupAction(callable $condition)
2522
	{
2523
		$this->row_conditions['group_action'] = $condition;
2524
	}
2525
2526
2527
	/**
2528
	 * @param  string   $key
2529
	 * @param  callable $condition
2530
	 * @return void
2531
	 */
2532
	public function allowRowsAction($key, callable $condition)
2533
	{
2534
		$this->row_conditions['action'][$key] = $condition;
2535
	}
2536
2537
2538
	/**
2539
	 * @param  string      $name
2540
	 * @param  string|null $key
2541
	 * @return bool|callable
2542
	 */
2543
	public function getRowCondition($name, $key = NULL)
2544
	{
2545
		if (!isset($this->row_conditions[$name])) {
2546
			return FALSE;
2547
		}
2548
2549
		$condition = $this->row_conditions[$name];
2550
2551
		if (!$key) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $key of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2552
			return $condition;
2553
		}
2554
2555
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2556
	}
2557
2558
2559
	/********************************************************************************
2560
	 *                               COLUMN CALLBACK                                *
2561
	 ********************************************************************************/
2562
2563
2564
	/**
2565
	 * @param  string   $key
2566
	 * @param  callable $callback
2567
	 * @return void
2568
	 */
2569
	public function addColumnCallback($key, callable $callback)
2570
	{
2571
		$this->column_callbacks[$key] = $callback;
2572
	}
2573
2574
2575
	/**
2576
	 * @param  string $key
2577
	 * @return callable|null
2578
	 */
2579
	public function getColumnCallback($key)
2580
	{
2581
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2582
	}
2583
2584
2585
	/********************************************************************************
2586
	 *                                 INLINE EDIT                                  *
2587
	 ********************************************************************************/
2588
2589
2590
	/**
2591
	 * @return InlineEdit
2592
	 */
2593
	public function addInlineEdit($primary_where_column = NULL)
2594
	{
2595
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2596
2597
		return $this->inlineEdit;
2598
	}
2599
2600
2601
	/**
2602
	 * @return InlineEdit|null
2603
	 */
2604
	public function getInlineEdit()
2605
	{
2606
		return $this->inlineEdit;
2607
	}
2608
2609
2610
	/**
2611
	 * @param  mixed $id
2612
	 * @return void
2613
	 */
2614
	public function handleInlineEdit($id)
2615
	{
2616
		if ($this->inlineEdit) {
2617
			$this->inlineEdit->setItemId($id);
2618
2619
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2620
2621
			$this['filter']['inline_edit']->addHidden('_id', $id);
2622
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2623
2624
			$this->redrawItem($id, $primary_where_column);
2625
		}
2626
	}
2627
2628
2629
	/********************************************************************************
2630
	 *                                  INLINE ADD                                  *
2631
	 ********************************************************************************/
2632
2633
2634
	/**
2635
	 * @return InlineEdit
2636
	 */
2637
	public function addInlineAdd()
2638
	{
2639
		$this->inlineAdd = new InlineEdit($this);
2640
2641
		$this->inlineAdd
2642
			->setIcon('plus')
2643
			->setClass('btn btn-xs btn-default');
2644
2645
		return $this->inlineAdd;
2646
	}
2647
2648
2649
	/**
2650
	 * @return InlineEdit|null
2651
	 */
2652
	public function getInlineAdd()
2653
	{
2654
		return $this->inlineAdd;
2655
	}
2656
2657
2658
	/********************************************************************************
2659
	 *                               HIDEABLE COLUMNS                               *
2660
	 ********************************************************************************/
2661
2662
2663
	/**
2664
	 * Can datagrid hide colums?
2665
	 * @return boolean
2666
	 */
2667
	public function canHideColumns()
2668
	{
2669
		return (bool) $this->can_hide_columns;
2670
	}
2671
2672
2673
	/**
2674
	 * Order Grid to set columns hideable.
2675
	 * @return static
2676
	 */
2677
	public function setColumnsHideable()
2678
	{
2679
		$this->can_hide_columns = TRUE;
2680
2681
		return $this;
2682
	}
2683
2684
2685
	/********************************************************************************
2686
	 *                                COLUMNS SUMMARY                               *
2687
	 ********************************************************************************/
2688
2689
2690
	/**
2691
	 * Will datagrid show summary in the end?
2692
	 * @return bool
2693
	 */
2694
	public function hasColumnsSummary()
2695
	{
2696
		return $this->columnsSummary instanceof ColumnsSummary;
2697
	}
2698
2699
2700
	/**
2701
	 * Set columns to be summarized in the end.
2702
	 * @param  array  $columns
2703
	 * @return ColumnsSummary
2704
	 */
2705
	public function setColumnsSummary(array $columns)
2706
	{
2707
		$this->columnsSummary = new ColumnsSummary($this, $columns);
2708
2709
		return $this->columnsSummary;
2710
	}
2711
2712
2713
	/**
2714
	 * @return ColumnsSummary|NULL
2715
	 */
2716
	public function getColumnsSummary()
2717
	{
2718
		return $this->columnsSummary;
2719
	}
2720
2721
2722
	/********************************************************************************
2723
	 *                                   INTERNAL                                   *
2724
	 ********************************************************************************/
2725
2726
2727
	/**
2728
	 * Get count of columns
2729
	 * @return int
2730
	 */
2731
	public function getColumnsCount()
2732
	{
2733
		$count = sizeof($this->getColumns());
2734
2735
		if (!empty($this->actions)
2736
			|| $this->isSortable()
2737
			|| $this->getItemsDetail()
2738
			|| $this->getInlineEdit()
2739
			|| $this->getInlineAdd()) {
2740
			$count++;
2741
		}
2742
2743
		if ($this->hasGroupActions()) {
2744
			$count++;
2745
		}
2746
2747
		return $count;
2748
	}
2749
2750
2751
	/**
2752
	 * Get primary key of datagrid data source
2753
	 * @return string
2754
	 */
2755
	public function getPrimaryKey()
2756
	{
2757
		return $this->primary_key;
2758
	}
2759
2760
2761
	/**
2762
	 * Get set of set columns
2763
	 * @return Column\IColumn[]
2764
	 */
2765
	public function getColumns()
2766
	{
2767
		if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
2768
			$columns_to_hide = [];
2769
2770
			foreach ($this->columns as $key => $column) {
2771
				if ($column->getDefaultHide()) {
2772
					$columns_to_hide[] = $key;
2773
				}
2774
			}
2775
2776
			if (!empty($columns_to_hide)) {
2777
				$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
2778
				$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2779
			}
2780
		}
2781
2782
		$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
2783
		
2784
		foreach ($hidden_columns as $column) {
2785
			if (!empty($this->columns[$column])) {
2786
				$this->columns_visibility[$column] = [
2787
					'visible' => FALSE,
2788
					'name' => $this->columns[$column]->getName()
2789
				];
2790
2791
				$this->removeColumn($column);
2792
			}
2793
		}
2794
2795
		return $this->columns;
2796
	}
2797
2798
2799
	/**
2800
	 * @return PresenterComponent
2801
	 */
2802
	public function getParent()
2803
	{
2804
		$parent = parent::getParent();
2805
2806
		if (!($parent instanceof PresenterComponent)) {
2807
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2808
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2809
			);
2810
		}
2811
2812
		return $parent;
2813
	}
2814
2815
2816
	/**
2817
	 * Some of datagrid columns is hidden by default
2818
	 * @param bool $default_hide
2819
	 */
2820
	public function setSomeColumnDefaultHide($default_hide)
2821
	{
2822
		$this->some_column_default_hide = $default_hide;
2823
	}
2824
2825
2826
	/**
2827
	 * Are some of columns hidden bydefault?
2828
	 */
2829
	public function hasSomeColumnDefaultHide()
2830
	{
2831
		return $this->some_column_default_hide;
2832
	}
2833
2834
}
2835