Completed
Push — master ( d68eda...13bada )
by Pavel
03:23
created

DataGrid::handlePage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
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
use Ublaboo\DataGrid\Toolbar\ToolbarButton;
22
23
/**
24
 * @method onRedraw()
25
 * @method onRender()
26
 * @method onColumnAdd()
27
 */
28
class DataGrid extends Nette\Application\UI\Control
29
{
30
31
	/**
32
	 * @var callable[]
33
	 */
34
	public $onRedraw;
35
36
	/**
37
	 * @var callable[]
38
	 */
39
	public $onRender = [];
40
41
	/**
42
	 * @var callable[]
43
	 */
44
	public $onColumnAdd;
45
46
	/**
47
	 * @var string
48
	 */
49
	public static $icon_prefix = 'fa fa-';
50
51
	/**
52
	 * Default form method
53
	 * @var string
54
	 */
55
	public static $form_method = 'post';
56
57
	/**
58
	 * When set to TRUE, datagrid throws an exception
59
	 * 	when tring to get related entity within join and entity does not exist
60
	 * @var bool
61
	 */
62
	public $strict_entity_property = FALSE;
63
64
	/**
65
	 * @var int
66
	 * @persistent
67
	 */
68
	public $page = 1;
69
70
	/**
71
	 * @var int|string
72
	 * @persistent
73
	 */
74
	public $per_page;
75
76
	/**
77
	 * @var array
78
	 * @persistent
79
	 */
80
	public $sort = [];
81
82
	/**
83
	 * @var array
84
	 */
85
	public $default_sort = [];
86
87
	/**
88
	 * @var array
89
	 */
90
	public $default_filter = [];
91
92
	/**
93
	 * @var bool
94
	 */
95
	public $default_filter_use_on_reset = TRUE;
96
97
	/**
98
	 * @var array
99
	 * @persistent
100
	 */
101
	public $filter = [];
102
103
	/**
104
	 * @var callable|null
105
	 */
106
	protected $sort_callback = NULL;
107
108
	/**
109
	 * @var bool
110
	 */
111
	protected $use_happy_components = TRUE;
112
113
	/**
114
	 * @var callable
115
	 */
116
	protected $rowCallback;
117
118
	/**
119
	 * @var array
120
	 */
121
	protected $items_per_page_list;
122
123
124
	/**
125
	 * @var int
126
	 */
127
	protected $items_per_page_default;
128
129
	/**
130
	 * @var string
131
	 */
132
	protected $template_file;
133
134
	/**
135
	 * @var Column\IColumn[]
136
	 */
137
	protected $columns = [];
138
139
	/**
140
	 * @var Column\Action[]
141
	 */
142
	protected $actions = [];
143
144
	/**
145
	 * @var GroupAction\GroupActionCollection
146
	 */
147
	protected $group_action_collection;
148
149
	/**
150
	 * @var Filter\Filter[]
151
	 */
152
	protected $filters = [];
153
154
	/**
155
	 * @var Export\Export[]
156
	 */
157
	protected $exports = [];
158
159
	/**
160
	 * @var ToolbarButton[]
161
	 */
162
	protected $toolbar_buttons = [];
163
164
	/**
165
	 * @var DataModel
166
	 */
167
	protected $dataModel;
168
169
	/**
170
	 * @var DataFilter
171
	 */
172
	protected $dataFilter;
173
174
	/**
175
	 * @var string
176
	 */
177
	protected $primary_key = 'id';
178
179
	/**
180
	 * @var bool
181
	 */
182
	protected $do_paginate = TRUE;
183
184
	/**
185
	 * @var bool
186
	 */
187
	protected $csv_export = TRUE;
188
189
	/**
190
	 * @var bool
191
	 */
192
	protected $csv_export_filtered = TRUE;
193
194
	/**
195
	 * @var bool
196
	 */
197
	protected $sortable = FALSE;
198
199
	/**
200
	 * @var string
201
	 */
202
	protected $sortable_handler = 'sort!';
203
204
	/**
205
	 * @var string
206
	 */
207
	protected $original_template;
208
209
	/**
210
	 * @var array
211
	 */
212
	protected $redraw_item;
213
214
	/**
215
	 * @var mixed
216
	 */
217
	protected $translator;
218
219
	/**
220
	 * @var bool
221
	 */
222
	protected $force_filter_active;
223
224
	/**
225
	 * @var callable
226
	 */
227
	protected $tree_view_children_callback;
228
229
	/**
230
	 * @var callable
231
	 */
232
	protected $tree_view_has_children_callback;
233
234
	/**
235
	 * @var string
236
	 */
237
	protected $tree_view_has_children_column;
238
239
	/**
240
	 * @var bool
241
	 */
242
	protected $outer_filter_rendering = FALSE;
243
244
	/**
245
	 * @var array
246
	 */
247
	protected $columns_export_order = [];
248
249
	/**
250
	 * @var bool
251
	 */
252
	protected $remember_state = TRUE;
253
254
	/**
255
	 * @var bool
256
	 */
257
	protected $refresh_url = TRUE;
258
259
	/**
260
	 * @var Nette\Http\SessionSection
261
	 */
262
	protected $grid_session;
263
264
	/**
265
	 * @var Column\ItemDetail
266
	 */
267
	protected $items_detail;
268
269
	/**
270
	 * @var array
271
	 */
272
	protected $row_conditions = [
273
		'group_action' => FALSE,
274
		'action' => []
275
	];
276
277
	/**
278
	 * @var array
279
	 */
280
	protected $column_callbacks = [];
281
282
	/**
283
	 * @var bool
284
	 */
285
	protected $can_hide_columns = FALSE;
286
287
	/**
288
	 * @var array
289
	 */
290
	protected $columns_visibility = [];
291
292
	/**
293
	 * @var InlineEdit
294
	 */
295
	protected $inlineEdit;
296
297
	/**
298
	 * @var InlineEdit
299
	 */
300
	protected $inlineAdd;
301
302
	/**
303
	 * @var bool
304
	 */
305
	protected $snippets_set = FALSE;
306
307
	/**
308
	 * @var bool
309
	 */
310
	protected $some_column_default_hide = FALSE;
311
312
	/**
313
	 * @var ColumnsSummary
314
	 */
315
	protected $columnsSummary;
316
317
318
	/**
319
	 * @param Nette\ComponentModel\IContainer|NULL $parent
320
	 * @param string                               $name
321
	 */
322
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
323
	{
324
		parent::__construct($parent, $name);
325
326
		$this->monitor('Nette\Application\UI\Presenter');
327
328
		/**
329
		 * Try to find previous filters, pagination, per_page and other values in session
330
		 */
331
		$this->onRender[] = [$this, 'findSessionValues'];
332
333
		/**
334
		 * Find default filter values
335
		 */
336
		$this->onRender[] = [$this, 'findDefaultFilter'];
337
338
		/**
339
		 * Find default sort
340
		 */
341
		$this->onRender[] = [$this, 'findDefaultSort'];
342
	}
343
344
345
	/**
346
	 * {inheritDoc}
347
	 * @return void
348
	 */
349
	public function attached($presenter)
350
	{
351
		parent::attached($presenter);
352
353
		if ($presenter instanceof Nette\Application\UI\Presenter) {
354
			/**
355
			 * Get session
356
			 */
357
			if ($this->remember_state) {
358
				$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...
359
			}
360
		}
361
	}
362
363
364
	/********************************************************************************
365
	 *                                  RENDERING                                   *
366
	 ********************************************************************************/
367
368
369
	/**
370
	 * Render template
371
	 * @return void
372
	 */
373
	public function render()
374
	{
375
		/**
376
		 * Check whether datagrid has set some columns, initiated data source, etc
377
		 */
378
		if (!($this->dataModel instanceof DataModel)) {
379
			throw new DataGridException('You have to set a data source first.');
380
		}
381
382
		if (empty($this->columns)) {
383
			throw new DataGridException('You have to add at least one column.');
384
		}
385
386
		$this->getTemplate()->setTranslator($this->getTranslator());
387
388
		/**
389
		 * Invoke possible events
390
		 */
391
		$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...
392
393
		/**
394
		 * Prepare data for rendering (datagrid may render just one item)
395
		 */
396
		$rows = [];
397
398
		if (!empty($this->redraw_item)) {
399
			$items = $this->dataModel->filterRow($this->redraw_item);
400
		} else {
401
			$items = Nette\Utils\Callback::invokeArgs(
402
				[$this->dataModel, 'filterData'],
403
				[
404
					$this->getPaginator(),
405
					$this->createSorting($this->sort, $this->sort_callback),
406
					$this->assableFilters()
407
				]
408
			);
409
		}
410
411
		$callback = $this->rowCallback ?: NULL;
412
		$hasGroupActionOnRows = FALSE;
413
414
		foreach ($items as $item) {
415
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
416
417
			if (!$hasGroupActionOnRows && $row->hasGroupAction()){
418
				$hasGroupActionOnRows = TRUE;
419
			}
420
			
421
			if ($callback) {
422
				$callback($item, $row->getControl());
423
			}
424
425
			/**
426
			 * Walkaround for item snippet - snippet is the <tr> element and its class has to be also updated
427
			 */
428
			if (!empty($this->redraw_item)) {
429
				$this->getPresenter()->payload->_datagrid_redraw_item_class = $row->getControlClass();
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...
430
				$this->getPresenter()->payload->_datagrid_redraw_item_id = $row->getId();
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...
431
			}
432
		}
433
434
		if ($hasGroupActionOnRows){
435
			$hasGroupActionOnRows = $this->hasGroupActions();
436
		}
437
438
		if ($this->isTreeView()) {
439
			$this->getTemplate()->add('tree_view_has_children_column', $this->tree_view_has_children_column);
440
		}
441
442
		$this->getTemplate()->add('rows', $rows);
443
444
		$this->getTemplate()->add('columns', $this->getColumns());
445
		$this->getTemplate()->add('actions', $this->actions);
446
		$this->getTemplate()->add('exports', $this->exports);
447
		$this->getTemplate()->add('filters', $this->filters);
448
		$this->getTemplate()->add('toolbar_buttons', $this->toolbar_buttons);
449
450
		$this->getTemplate()->add('filter_active', $this->isFilterActive());
451
		$this->getTemplate()->add('original_template', $this->getOriginalTemplateFile());
452
		//$this->getTemplate()->add('icon_prefix', static::$icon_prefix);
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...
453
		$this->getTemplate()->icon_prefix = static::$icon_prefix;
0 ignored issues
show
Bug introduced by
Accessing icon_prefix on the interface Nette\Application\UI\ITemplate 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...
454
		$this->getTemplate()->add('items_detail', $this->items_detail);
455
		$this->getTemplate()->add('columns_visibility', $this->columns_visibility);
456
		$this->getTemplate()->add('columnsSummary', $this->columnsSummary);
457
458
		$this->getTemplate()->add('inlineEdit', $this->inlineEdit);
459
		$this->getTemplate()->add('inlineAdd', $this->inlineAdd);
460
461
		$this->getTemplate()->add('hasGroupActionOnRows', $hasGroupActionOnRows);
462
463
		/**
464
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
465
		 */
466
		$this->getTemplate()->add('filter', $this['filter']);
467
468
		/**
469
		 * Set template file and render it
470
		 */
471
		$this->getTemplate()->setFile($this->getTemplateFile());
472
		$this->getTemplate()->render();
473
	}
474
475
476
	/********************************************************************************
477
	 *                                 ROW CALLBACK                                 *
478
	 ********************************************************************************/
479
480
481
	/**
482
	 * Each row can be modified with user callback
483
	 * @param  callable  $callback
484
	 * @return static
485
	 */
486
	public function setRowCallback(callable $callback)
487
	{
488
		$this->rowCallback = $callback;
489
490
		return $this;
491
	}
492
493
494
	/********************************************************************************
495
	 *                                 DATA SOURCE                                  *
496
	 ********************************************************************************/
497
498
499
	/**
500
	 * By default ID, you can change that
501
	 * @param string $primary_key
502
	 * @return static
503
	 */
504
	public function setPrimaryKey($primary_key)
505
	{
506
		if ($this->dataModel instanceof DataModel) {
507
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
508
		}
509
510
		$this->primary_key = $primary_key;
511
512
		return $this;
513
	}
514
515
516
	/**
517
	 * Set Grid data source
518
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
519
	 * @return static
520
	 */
521
	public function setDataSource($source)
522
	{
523
		$this->dataModel = new DataModel($source, $this->primary_key);
524
525
		return $this;
526
	}
527
528
529
	/********************************************************************************
530
	 *                                  TEMPLATING                                  *
531
	 ********************************************************************************/
532
533
534
	/**
535
	 * Set custom template file to render
536
	 * @param string $template_file
537
	 * @return static
538
	 */
539
	public function setTemplateFile($template_file)
540
	{
541
		$this->template_file = $template_file;
542
543
		return $this;
544
	}
545
546
547
	/**
548
	 * Get DataGrid template file
549
	 * @return string
550
	 * @return static
551
	 */
552
	public function getTemplateFile()
553
	{
554
		return $this->template_file ?: $this->getOriginalTemplateFile();
555
	}
556
557
558
	/**
559
	 * Get DataGrid original template file
560
	 * @return string
561
	 */
562
	public function getOriginalTemplateFile()
563
	{
564
		return __DIR__.'/templates/datagrid.latte';
565
	}
566
567
568
	/**
569
	 * Tell datagrid wheteher to use or not happy components
570
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
571
	 * @return void|bool
572
	 */
573
	public function useHappyComponents($use = NULL)
574
	{
575
		if (NULL === $use) {
576
			return $this->use_happy_components;
577
		}
578
579
		$this->use_happy_components = (bool) $use;
580
	}
581
582
583
	/********************************************************************************
584
	 *                                   SORTING                                    *
585
	 ********************************************************************************/
586
587
588
	/**
589
	 * Set default sorting
590
	 * @param array $sort
591
	 * @return static
592
	 */
593
	public function setDefaultSort($sort)
594
	{
595
		if (is_string($sort)) {
596
			$sort = [$sort => 'ASC'];
597
		} else {
598
			$sort = (array) $sort;
599
		}
600
601
		$this->default_sort = $sort;
602
603
		return $this;
604
	}
605
606
607
	/**
608
	 * User may set default sorting, apply it
609
	 * @return void
610
	 */
611
	public function findDefaultSort()
612
	{
613
		if (!empty($this->sort)) {
614
			return;
615
		}
616
617
		if (!empty($this->default_sort)) {
618
			$this->sort = $this->default_sort;
619
		}
620
621
		$this->saveSessionData('_grid_sort', $this->sort);
622
	}
623
624
625
	/**
626
	 * Set grido to be sortable
627
	 * @param bool $sortable
628
	 * @return static
629
	 */
630
	public function setSortable($sortable = TRUE)
631
	{
632
		if ($this->getItemsDetail()) {
633
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
634
		}
635
636
		$this->sortable = (bool) $sortable;
637
638
		return $this;
639
	}
640
641
642
	/**
643
	 * Set sortable handle
644
	 * @param string $handler
645
	 * @return static
646
	 */
647
	public function setSortableHandler($handler = 'sort!')
648
	{
649
		$this->sortable_handler = (string) $handler;
650
651
		return $this;
652
	}
653
654
655
	/**
656
	 * Tell whether DataGrid is sortable
657
	 * @return bool
658
	 */
659
	public function isSortable()
660
	{
661
		return $this->sortable;
662
	}
663
664
	/**
665
	 * Return sortable handle name
666
	 * @return string
667
	 */
668
	public function getSortableHandler()
669
	{
670
		return $this->sortable_handler;
671
	}
672
673
674
	protected function createSorting(array $sort, $sort_callback)
675
	{
676
		foreach ($sort as $key => $order) {
677
			$column = $this->columns[$key];
678
			$sort = [$column->getSortingColumn() => $order];
679
		}
680
681
		return new Sorting($sort, $sort_callback);
682
	}
683
684
685
	/********************************************************************************
686
	 *                                  TREE VIEW                                   *
687
	 ********************************************************************************/
688
689
690
	/**
691
	 * Is tree view set?
692
	 * @return boolean
693
	 */
694
	public function isTreeView()
695
	{
696
		return (bool) $this->tree_view_children_callback;
697
	}
698
699
700
	/**
701
	 * Setting tree view
702
	 * @param callable $get_children_callback
703
	 * @param string|callable $tree_view_has_children_column
704
	 * @return static
705
	 */
706
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
707
	{
708
		if (!is_callable($get_children_callback)) {
709
			throw new DataGridException(
710
				'Parameters to method DataGrid::setTreeView must be of type callable'
711
			);
712
		}
713
714
		if (is_callable($tree_view_has_children_column)) {
715
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
716
			$tree_view_has_children_column = NULL;
717
		}
718
719
		$this->tree_view_children_callback = $get_children_callback;
720
		$this->tree_view_has_children_column = $tree_view_has_children_column;
721
722
		/**
723
		 * TUrn off pagination
724
		 */
725
		$this->setPagination(FALSE);
726
727
		/**
728
		 * Set tree view template file
729
		 */
730
		if (!$this->template_file) {
731
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
732
		}
733
734
		return $this;
735
	}
736
737
738
	/**
739
	 * Is tree view children callback set?
740
	 * @return boolean
741
	 */
742
	public function hasTreeViewChildrenCallback()
743
	{
744
		return is_callable($this->tree_view_has_children_callback);
745
	}
746
747
748
	/**
749
	 * @param  mixed $item
750
	 * @return boolean
751
	 */
752
	public function treeViewChildrenCallback($item)
753
	{
754
		return call_user_func($this->tree_view_has_children_callback, $item);
755
	}
756
757
758
	/********************************************************************************
759
	 *                                    COLUMNS                                   *
760
	 ********************************************************************************/
761
762
763
	/**
764
	 * Add text column with no other formating
765
	 * @param  string      $key
766
	 * @param  string      $name
767
	 * @param  string|null $column
768
	 * @return Column\ColumnText
769
	 */
770
	public function addColumnText($key, $name, $column = NULL)
771
	{
772
		$this->addColumnCheck($key);
773
		$column = $column ?: $key;
774
775
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
776
	}
777
778
779
	/**
780
	 * Add column with link
781
	 * @param  string      $key
782
	 * @param  string      $name
783
	 * @param  string|null $column
784
	 * @return Column\ColumnLink
785
	 */
786
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
787
	{
788
		$this->addColumnCheck($key);
789
		$column = $column ?: $key;
790
		$href = $href ?: $key;
791
792
		if (NULL === $params) {
793
			$params = [$this->primary_key];
794
		}
795
796
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
797
	}
798
799
800
	/**
801
	 * Add column with possible number formating
802
	 * @param  string      $key
803
	 * @param  string      $name
804
	 * @param  string|null $column
805
	 * @return Column\ColumnNumber
806
	 */
807
	public function addColumnNumber($key, $name, $column = NULL)
808
	{
809
		$this->addColumnCheck($key);
810
		$column = $column ?: $key;
811
812
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
813
	}
814
815
816
	/**
817
	 * Add column with date formating
818
	 * @param  string      $key
819
	 * @param  string      $name
820
	 * @param  string|null $column
821
	 * @return Column\ColumnDateTime
822
	 */
823
	public function addColumnDateTime($key, $name, $column = NULL)
824
	{
825
		$this->addColumnCheck($key);
826
		$column = $column ?: $key;
827
828
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
829
	}
830
831
832
	/**
833
	 * Add column status
834
	 * @param  string      $key
835
	 * @param  string      $name
836
	 * @param  string|null $column
837
	 * @return Column\ColumnStatus
838
	 */
839
	public function addColumnStatus($key, $name, $column = NULL)
840
	{
841
		$this->addColumnCheck($key);
842
		$column = $column ?: $key;
843
844
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
845
	}
846
847
848
	/**
849
	 * @param string $key
850
	 * @param Column\Column $column
851
	 * @return Column\Column
852
	 */
853
	protected function addColumn($key, Column\Column $column)
854
	{
855
		$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...
856
857
		$this->columns_visibility[$key] = [
858
			'visible' => TRUE,
859
			'name' => $column->getName()
860
		];
861
862
		return $this->columns[$key] = $column;
863
	}
864
865
866
	/**
867
	 * Return existing column
868
	 * @param  string $key
869
	 * @return Column\Column
870
	 * @throws DataGridException
871
	 */
872
	public function getColumn($key)
873
	{
874
		if (!isset($this->columns[$key])) {
875
			throw new DataGridException("There is no column at key [$key] defined.");
876
		}
877
878
		return $this->columns[$key];
879
	}
880
881
882
	/**
883
	 * Remove column
884
	 * @param string $key
885
	 * @return void
886
	 */
887
	public function removeColumn($key)
888
	{
889
		unset($this->columns[$key]);
890
	}
891
892
893
	/**
894
	 * Check whether given key already exists in $this->columns
895
	 * @param  string $key
896
	 * @throws DataGridException
897
	 */
898
	protected function addColumnCheck($key)
899
	{
900
		if (isset($this->columns[$key])) {
901
			throw new DataGridException("There is already column at key [$key] defined.");
902
		}
903
	}
904
905
906
	/********************************************************************************
907
	 *                                    ACTIONS                                   *
908
	 ********************************************************************************/
909
910
911
	/**
912
	 * Create action
913
	 * @param string     $key
914
	 * @param string     $name
915
	 * @param string     $href
916
	 * @param array|null $params
917
	 * @return Column\Action
918
	 */
919
	public function addAction($key, $name, $href = NULL, array $params = NULL)
920
	{
921
		$this->addActionCheck($key);
922
		$href = $href ?: $key;
923
924
		if (NULL === $params) {
925
			$params = [$this->primary_key];
926
		}
927
928
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
929
	}
930
931
932
	/**
933
	 * Create action callback
934
	 * @param string     $key
935
	 * @param string     $name
936
	 * @return Column\Action
937
	 */
938
	public function addActionCallback($key, $name, $callback = NULL)
939
	{
940
		$this->addActionCheck($key);
941
		$params = ['__id' => $this->primary_key];
942
943
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
944
945
		if ($callback) {
946
			if (!is_callable($callback)) {
947
				throw new DataGridException('ActionCallback callback has to be callable.');
948
			}
949
950
			$action->onClick[] = $callback;
951
		}
952
953
		return $action;
954
	}
955
956
957
	/**
958
	 * Get existing action
959
	 * @param  string       $key
960
	 * @return Column\Action
961
	 * @throws DataGridException
962
	 */
963
	public function getAction($key)
964
	{
965
		if (!isset($this->actions[$key])) {
966
			throw new DataGridException("There is no action at key [$key] defined.");
967
		}
968
969
		return $this->actions[$key];
970
	}
971
972
973
	/**
974
	 * Remove action
975
	 * @param string $key
976
	 * @return void
977
	 */
978
	public function removeAction($key)
979
	{
980
		unset($this->actions[$key]);
981
	}
982
983
984
	/**
985
	 * Check whether given key already exists in $this->filters
986
	 * @param  string $key
987
	 * @throws DataGridException
988
	 */
989
	protected function addActionCheck($key)
990
	{
991
		if (isset($this->actions[$key])) {
992
			throw new DataGridException("There is already action at key [$key] defined.");
993
		}
994
	}
995
996
997
	/********************************************************************************
998
	 *                                    FILTERS                                   *
999
	 ********************************************************************************/
1000
1001
1002
	/**
1003
	 * Add filter fot text search
1004
	 * @param string       $key
1005
	 * @param string       $name
1006
	 * @param array|string $columns
1007
	 * @return Filter\FilterText
1008
	 * @throws DataGridException
1009
	 */
1010
	public function addFilterText($key, $name, $columns = NULL)
1011
	{
1012
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
1013
1014
		if (!is_array($columns)) {
1015
			throw new DataGridException("Filter Text can except only array or string.");
1016
		}
1017
1018
		$this->addFilterCheck($key);
1019
1020
		return $this->filters[$key] = new Filter\FilterText($key, $name, $columns);
1021
	}
1022
1023
1024
	/**
1025
	 * Add select box filter
1026
	 * @param string $key
1027
	 * @param string $name
1028
	 * @param array  $options
1029
	 * @param string $column
1030
	 * @return Filter\FilterSelect
1031
	 * @throws DataGridException
1032
	 */
1033 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...
1034
	{
1035
		$column = $column ?: $key;
1036
1037
		if (!is_string($column)) {
1038
			throw new DataGridException("Filter Select can only filter in one column.");
1039
		}
1040
1041
		$this->addFilterCheck($key);
1042
1043
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
1044
	}
1045
1046
1047
	/**
1048
	 * Add multi select box filter
1049
	 * @param string $key
1050
	 * @param string $name
1051
	 * @param array  $options
1052
	 * @param string $column
1053
	 * @return Filter\FilterSelect
1054
	 * @throws DataGridException
1055
	 */
1056
	public function addFilterMultiSelect($key, $name, array $options, $column = NULL)
1057
	{
1058
		$column = $column ?: $key;
1059
1060
		if (!is_string($column)) {
1061
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1062
		}
1063
1064
		$this->addFilterCheck($key);
1065
1066
		return $this->filters[$key] = new Filter\FilterMultiSelect($key, $name, $options, $column);
1067
	}
1068
1069
1070
	/**
1071
	 * Add datepicker filter
1072
	 * @param string $key
1073
	 * @param string $name
1074
	 * @param string $column
1075
	 * @return Filter\FilterDate
1076
	 * @throws DataGridException
1077
	 */
1078
	public function addFilterDate($key, $name, $column = NULL)
1079
	{
1080
		$column = $column ?: $key;
1081
1082
		if (!is_string($column)) {
1083
			throw new DataGridException("FilterDate can only filter in one column.");
1084
		}
1085
1086
		$this->addFilterCheck($key);
1087
1088
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
1089
	}
1090
1091
1092
	/**
1093
	 * Add range filter (from - to)
1094
	 * @param string $key
1095
	 * @param string $name
1096
	 * @param string $column
1097
	 * @return Filter\FilterRange
1098
	 * @throws DataGridException
1099
	 */
1100 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...
1101
	{
1102
		$column = $column ?: $key;
1103
1104
		if (!is_string($column)) {
1105
			throw new DataGridException("FilterRange can only filter in one column.");
1106
		}
1107
1108
		$this->addFilterCheck($key);
1109
1110
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
1111
	}
1112
1113
1114
	/**
1115
	 * Add datepicker filter (from - to)
1116
	 * @param string $key
1117
	 * @param string $name
1118
	 * @param string $column
1119
	 * @return Filter\FilterDateRange
1120
	 * @throws DataGridException
1121
	 */
1122 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...
1123
	{
1124
		$column = $column ?: $key;
1125
1126
		if (!is_string($column)) {
1127
			throw new DataGridException("FilterDateRange can only filter in one column.");
1128
		}
1129
1130
		$this->addFilterCheck($key);
1131
1132
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
1133
	}
1134
1135
1136
	/**
1137
	 * Check whether given key already exists in $this->filters
1138
	 * @param  string $key
1139
	 * @throws DataGridException
1140
	 */
1141
	protected function addFilterCheck($key)
1142
	{
1143
		if (isset($this->filters[$key])) {
1144
			throw new DataGridException("There is already action at key [$key] defined.");
1145
		}
1146
	}
1147
1148
1149
	/**
1150
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1151
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1152
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1153
	 */
1154
	public function assableFilters()
1155
	{
1156
		foreach ($this->filter as $key => $value) {
1157
			if (!isset($this->filters[$key])) {
1158
				$this->deleteSesssionData($key);
1159
1160
				continue;
1161
			}
1162
1163
			if (is_array($value) || $value instanceof \Traversable) {
1164
				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...
1165
					$this->filters[$key]->setValue($value);
1166
				}
1167
			} else {
1168
				if ($value !== '' && $value !== NULL) {
1169
					$this->filters[$key]->setValue($value);
1170
				}
1171
			}
1172
		}
1173
1174
		foreach ($this->columns as $key => $column) {
1175
			if (isset($this->sort[$key])) {
1176
				$column->setSort($this->sort);
1177
			}
1178
		}
1179
1180
		return $this->filters;
1181
	}
1182
1183
1184
	/**
1185
	 * Remove filter
1186
	 * @param string $key
1187
	 * @return void
1188
	 */
1189
	public function removeFilter($key)
1190
	{
1191
		unset($this->filters[$key]);
1192
	}
1193
1194
1195
	/**
1196
	 * Get defined filter
1197
	 * @param  string $key
1198
	 * @return Filter\Filter
1199
	 */
1200
	public function getFilter($key)
1201
	{
1202
		if (!isset($this->filters[$key])) {
1203
			throw new DataGridException("Filter [{$key}] is not defined");
1204
		}
1205
1206
		return $this->filters[$key];
1207
	}
1208
1209
1210
	/********************************************************************************
1211
	 *                                  FILTERING                                   *
1212
	 ********************************************************************************/
1213
1214
1215
	/**
1216
	 * Is filter active?
1217
	 * @return boolean
1218
	 */
1219
	public function isFilterActive()
1220
	{
1221
		$is_filter = ArraysHelper::testTruthy($this->filter);
1222
1223
		return ($is_filter) || $this->force_filter_active;
1224
	}
1225
1226
1227
	/**
1228
	 * Tell that filter is active from whatever reasons
1229
	 * return static
1230
	 */
1231
	public function setFilterActive()
1232
	{
1233
		$this->force_filter_active = TRUE;
1234
1235
		return $this;
1236
	}
1237
1238
1239
	/**
1240
	 * Set filter values (force - overwrite user data)
1241
	 * @param array $filter
1242
	 * @return static
1243
	 */
1244
	public function setFilter(array $filter)
1245
	{
1246
		$this->filter = $filter;
1247
1248
		$this->saveSessionData('_grid_has_filtered', 1);
1249
1250
		return $this;
1251
	}
1252
1253
1254
	/**
1255
	 * If we want to sent some initial filter
1256
	 * @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...
1257
	 * @param bool  $use_on_reset
1258
	 * @return static
1259
	 */
1260
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1261
	{
1262
		foreach ($default_filter as $key => $value) {
1263
			$filter = $this->getFilter($key);
1264
1265
			if (!$filter) {
1266
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1267
			}
1268
1269
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1270
				throw new DataGridException(
1271
					"Default value of filter [$key] - MultiSelect has to be an array"
1272
				);
1273
			}
1274
1275
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1276
				if (!is_array($value)) {
1277
					throw new DataGridException(
1278
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1279
					);
1280
				}
1281
1282
				$temp = $value;
1283
				unset($temp['from'], $temp['to']);
1284
1285
				if (!empty($temp)) {
1286
					throw new DataGridException(
1287
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1288
					);
1289
				}
1290
			}
1291
		}
1292
1293
		$this->default_filter = $default_filter;
1294
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1295
1296
		return $this;
1297
	}
1298
1299
1300
	/**
1301
	 * User may set default filter, find it
1302
	 * @return void
1303
	 */
1304
	public function findDefaultFilter()
1305
	{
1306
		if (!empty($this->filter)) {
1307
			return;
1308
		}
1309
1310
		if ($this->getSessionData('_grid_has_filtered')) {
1311
			return;
1312
		}
1313
1314
		if (!empty($this->default_filter)) {
1315
			$this->filter = $this->default_filter;
1316
		}
1317
1318
		foreach ($this->filter as $key => $value) {
1319
			$this->saveSessionData($key, $value);
1320
		}
1321
	}
1322
1323
1324
	/**
1325
	 * FilterAndGroupAction form factory
1326
	 * @return Form
1327
	 */
1328
	public function createComponentFilter()
1329
	{
1330
		$form = new Form($this, 'filter');
1331
1332
		$form->setMethod(static::$form_method);
1333
1334
		$form->setTranslator($this->getTranslator());
1335
1336
		/**
1337
		 * InlineEdit part
1338
		 */
1339
		$inline_edit_container = $form->addContainer('inline_edit');
1340
1341 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...
1342
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1343
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1344
				->setValidationScope(FALSE);
1345
1346
			$this->inlineEdit->onControlAdd($inline_edit_container);
1347
		}
1348
1349
		/**
1350
		 * InlineAdd part
1351
		 */
1352
		$inline_add_container = $form->addContainer('inline_add');
1353
1354 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...
1355
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1356
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1357
				->setValidationScope(FALSE)
1358
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1359
1360
			$this->inlineAdd->onControlAdd($inline_add_container);
1361
		}
1362
1363
		/**
1364
		 * ItemDetail form part
1365
		 */
1366
		$items_detail_form = $this->getItemDetailForm();
1367
1368
		if ($items_detail_form instanceof Nette\Forms\Container) {
1369
			$form['items_detail_form'] = $items_detail_form;
1370
		}
1371
1372
		/**
1373
		 * Filter part
1374
		 */
1375
		$filter_container = $form->addContainer('filter');
1376
1377
		foreach ($this->filters as $filter) {
1378
			$filter->addToFormContainer($filter_container);
1379
		}
1380
1381
		/**
1382
		 * Group action part
1383
		 */
1384
		$group_action_container = $form->addContainer('group_action');
1385
1386
		if ($this->hasGroupActions()) {
1387
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1388
		}
1389
1390
		$form->setDefaults(['filter' => $this->filter]);
1391
1392
		/**
1393
		 * Per page part
1394
		 */
1395
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1396
			->setTranslator(NULL);
1397
1398
		if (!$form->isSubmitted()) {
1399
			$form['per_page']->setValue($this->getPerPage());
1400
		}
1401
1402
		$form->addSubmit('per_page_submit', '');
1403
		
1404
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1405
1406
		return $form;
1407
	}
1408
1409
1410
	/**
1411
	 * Set $this->filter values after filter form submitted
1412
	 * @param  Form $form
1413
	 * @return void
1414
	 */
1415
	public function filterSucceeded(Form $form)
1416
	{
1417
		if ($this->snippets_set) {
1418
			return;
1419
		}
1420
1421
		$values = $form->getValues();
1422
1423
		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...
1424
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1425
				return;
1426
			}
1427
		}
1428
1429
		/**
1430
		 * Per page
1431
		 */
1432
		$this->saveSessionData('_grid_per_page', $values->per_page);
1433
		$this->per_page = $values->per_page;
1434
1435
		/**
1436
		 * Inline edit
1437
		 */
1438
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1439
			$edit = $form['inline_edit'];
1440
1441
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1442
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1443
				$primary_where_column = $form->getHttpData(
1444
					Form::DATA_LINE,
1445
					'inline_edit[_primary_where_column]'
1446
				);
1447
1448
				if ($edit['submit']->isSubmittedBy()) {
1449
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1450
					$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...
1451
				} else {
1452
					$this->getPresenter()->payload->_datagrid_inline_edit_cancel = $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...
1453
				}
1454
1455
				if ($edit['submit']->isSubmittedBy() && !empty($this->inlineEdit->onCustomRedraw)) {
1456
					$this->inlineEdit->onCustomRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onCustomRedraw does not exist on object<Ublaboo\DataGrid\InlineEdit\InlineEdit>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1457
				} else {
1458
					$this->redrawItem($id, $primary_where_column);
1459
				}
1460
1461
				return;
1462
			}
1463
		}
1464
1465
		/**
1466
		 * Inline add
1467
		 */
1468
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1469
			$add = $form['inline_add'];
1470
1471
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1472
				if ($add['submit']->isSubmittedBy()) {
1473
					$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...
1474
1475
					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...
1476
						$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...
1477
					}
1478
				}
1479
1480
				return;
1481
			}
1482
		}
1483
1484
		/**
1485
		 * Filter itself
1486
		 */
1487
		$values = $values['filter'];
1488
1489
		foreach ($values as $key => $value) {
1490
			/**
1491
			 * Session stuff
1492
			 */
1493
			$this->saveSessionData($key, $value);
1494
1495
			/**
1496
			 * Other stuff
1497
			 */
1498
			$this->filter[$key] = $value;
1499
		}
1500
1501
		if (!empty($values)) {
1502
			$this->saveSessionData('_grid_has_filtered', 1);
1503
		}
1504
1505
		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...
1506
			$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...
1507
1508
			foreach ($this->columns as $key => $column) {
1509
				if ($column->isSortable()) {
1510
					$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...
1511
						'sort' => $column->getSortNext()
1512
					]);
1513
				}
1514
			}
1515
		}
1516
1517
		$this->reload();
1518
	}
1519
1520
1521
	/**
1522
	 * Should be datagrid filters rendered separately?
1523
	 * @param boolean $out
1524
	 * @return static
1525
	 */
1526
	public function setOuterFilterRendering($out = TRUE)
1527
	{
1528
		$this->outer_filter_rendering = (bool) $out;
1529
1530
		return $this;
1531
	}
1532
1533
1534
	/**
1535
	 * Are datagrid filters rendered separately?
1536
	 * @return boolean
1537
	 */
1538
	public function hasOuterFilterRendering()
1539
	{
1540
		return $this->outer_filter_rendering;
1541
	}
1542
1543
1544
	/**
1545
	 * Try to restore session stuff
1546
	 * @return void
1547
	 */
1548
	public function findSessionValues()
1549
	{
1550
		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...
1551
			return;
1552
		}
1553
1554
		if (!$this->remember_state) {
1555
			return;
1556
		}
1557
1558
		if ($page = $this->getSessionData('_grid_page')) {
1559
			$this->page = $page;
1560
		}
1561
1562
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1563
			$this->per_page = $per_page;
1564
		}
1565
1566
		if ($sort = $this->getSessionData('_grid_sort')) {
1567
			$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...
1568
		}
1569
1570
		foreach ($this->getSessionData() as $key => $value) {
1571
			$other_session_keys = [
1572
				'_grid_per_page',
1573
				'_grid_sort',
1574
				'_grid_page',
1575
				'_grid_has_filtered',
1576
				'_grid_hidden_columns',
1577
				'_grid_hidden_columns_manipulated'
1578
			];
1579
1580
			if (!in_array($key, $other_session_keys)) {
1581
				$this->filter[$key] = $value;
1582
			}
1583
		}
1584
1585
		/**
1586
		 * When column is sorted via custom callback, apply it
1587
		 */
1588
		if (empty($this->sort_callback) && !empty($this->sort)) {
1589
			foreach ($this->sort as $key => $order) {
1590
				$column = $this->getColumn($key);
1591
1592
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1593
					$this->sort_callback = $column->getSortableCallback();
1594
				}
1595
			}
1596
		}
1597
	}
1598
1599
1600
	/********************************************************************************
1601
	 *                                    EXPORTS                                   *
1602
	 ********************************************************************************/
1603
1604
1605
	/**
1606
	 * Add export of type callback
1607
	 * @param string $text
1608
	 * @param callable $callback
1609
	 * @param boolean $filtered
1610
	 * @return Export\Export
1611
	 */
1612
	public function addExportCallback($text, $callback, $filtered = FALSE)
1613
	{
1614
		if (!is_callable($callback)) {
1615
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1616
		}
1617
1618
		return $this->addToExports(new Export\Export($this, $text, $callback, $filtered));
1619
	}
1620
1621
1622
	/**
1623
	 * Add already implemented csv export
1624
	 * @param string $text
1625
	 * @param string $csv_file_name
1626
	 * @param string|null $output_encoding
1627
	 * @param string|null $delimiter
1628
	 * @param bool $include_bom
1629
	 * @return Export\Export
1630
	 */
1631 View Code Duplication
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL, $include_bom = FALSE)
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...
1632
	{
1633
		return $this->addToExports(new Export\ExportCsv(
1634
			$this,
1635
			$text,
1636
			$csv_file_name,
1637
			FALSE,
1638
			$output_encoding,
1639
			$delimiter,
1640
			$include_bom
1641
		));
1642
	}
1643
1644
1645
	/**
1646
	 * Add already implemented csv export, but for filtered data
1647
	 * @param string $text
1648
	 * @param string $csv_file_name
1649
	 * @param string|null $output_encoding
1650
	 * @param string|null $delimiter
1651
	 * @param bool $include_bom
1652
	 * @return Export\Export
1653
	 */
1654 View Code Duplication
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL, $include_bom = FALSE)
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...
1655
	{
1656
		return $this->addToExports(new Export\ExportCsv(
1657
			$this,
1658
			$text,
1659
			$csv_file_name,
1660
			TRUE,
1661
			$output_encoding,
1662
			$delimiter,
1663
			$include_bom
1664
		));
1665
	}
1666
1667
1668
	/**
1669
	 * Add export to array
1670
	 * @param Export\Export $export
1671
	 * @return Export\Export
1672
	 */
1673
	protected function addToExports(Export\Export $export)
1674
	{
1675
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1676
1677
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1678
1679
		return $this->exports[$id] = $export;
1680
	}
1681
1682
1683
	public function resetExportsLinks()
1684
	{
1685
		foreach ($this->exports as $id => $export) {
1686
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1687
		}
1688
	}
1689
1690
1691
	/********************************************************************************
1692
	 *                                TOOLBAR BUTTONS                               *
1693
	 ********************************************************************************/
1694
1695
1696
	/**
1697
	 * Add toolbar button
1698
	 * @param string $href
1699
	 * @param string $text
1700
	 * @param array  $params
1701
	 * @return ToolbarButton
1702
	 */
1703
	public function addToolbarButton($href, $text = '', $params = [])
1704
	{
1705
		$button = new ToolbarButton($this, $href, $text, $params);
1706
1707
		return $this->toolbar_buttons[] = $button;
1708
	}
1709
1710
1711
	/********************************************************************************
1712
	 *                                 GROUP ACTIONS                                *
1713
	 ********************************************************************************/
1714
1715
1716
	/**
1717
	 * Alias for add group select action
1718
	 * @param string $title
1719
	 * @param array  $options
1720
	 * @return GroupAction\GroupAction
1721
	 */
1722
	public function addGroupAction($title, $options = [])
1723
	{
1724
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1725
	}
1726
1727
	/**
1728
	 * Add group action (select box)
1729
	 * @param string $title
1730
	 * @param array  $options
1731
	 * @return GroupAction\GroupAction
1732
	 */
1733
	public function addGroupSelectAction($title, $options = [])
1734
	{
1735
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1736
	}
1737
1738
	/**
1739
	 * Add group action (text input)
1740
	 * @param string $title
1741
	 * @return GroupAction\GroupAction
1742
	 */
1743
	public function addGroupTextAction($title)
1744
	{
1745
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1746
	}
1747
1748
	/**
1749
	 * Get collection of all group actions
1750
	 * @return GroupAction\GroupActionCollection
1751
	 */
1752
	public function getGroupActionCollection()
1753
	{
1754
		if (!$this->group_action_collection) {
1755
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
1756
		}
1757
1758
		return $this->group_action_collection;
1759
	}
1760
1761
1762
	/**
1763
	 * Has datagrid some group actions?
1764
	 * @return boolean
1765
	 */
1766
	public function hasGroupActions()
1767
	{
1768
		return (bool) $this->group_action_collection;
1769
	}
1770
1771
1772
	/********************************************************************************
1773
	 *                                   HANDLERS                                   *
1774
	 ********************************************************************************/
1775
1776
1777
	/**
1778
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1779
	 * @param  int  $page
1780
	 * @return void
1781
	 */
1782
	public function handlePage($page)
1783
	{
1784
		/**
1785
		 * Session stuff
1786
		 */
1787
		$this->page = $page;
1788
		$this->saveSessionData('_grid_page', $page);
1789
1790
		$this->reload(['table']);
1791
	}
1792
1793
1794
	/**
1795
	 * Handler for sorting
1796
	 * @param array $sort
1797
	 * @return void
1798
	 */
1799
	public function handleSort(array $sort)
1800
	{
1801
		$new_sort = [];
1802
1803
		/**
1804
		 * Find apropirate column
1805
		 */
1806
		foreach ($sort as $key => $value) {
1807
			if (empty($this->columns[$key])) {
1808
				throw new DataGridException("Column <$key> not found");
1809
			}
1810
1811
			$column = $this->columns[$key];
1812
			$new_sort = [$key => $value];
1813
1814
			/**
1815
			 * Pagination may be reseted after sorting
1816
			 */
1817
			if ($column->sortableResetPagination()) {
1818
				$this->page = 1;
1819
				$this->saveSessionData('_grid_page', 1);
1820
			}
1821
1822
			/**
1823
			 * Custom sorting callback may be applied
1824
			 */
1825
			if ($column->getSortableCallback()) {
1826
				$this->sort_callback = $column->getSortableCallback();
1827
			}
1828
		}
1829
1830
		/**
1831
		 * Session stuff
1832
		 */
1833
		$this->sort = $new_sort;
1834
		$this->saveSessionData('_grid_sort', $this->sort);
1835
1836
		$this->reload(['table']);
1837
	}
1838
1839
1840
	/**
1841
	 * handler for reseting the filter
1842
	 * @return void
1843
	 */
1844
	public function handleResetFilter()
1845
	{
1846
		/**
1847
		 * Session stuff
1848
		 */
1849
		$this->deleteSesssionData('_grid_page');
1850
1851
		if ($this->default_filter_use_on_reset) {
1852
			$this->deleteSesssionData('_grid_has_filtered');
1853
		}
1854
1855
		foreach ($this->getSessionData() as $key => $value) {
1856
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page', '_grid_has_filtered'])) {
1857
				$this->deleteSesssionData($key);
1858
			}
1859
		}
1860
1861
		$this->filter = [];
1862
1863
		$this->reload(['grid']);
1864
	}
1865
1866
1867
	/**
1868
	 * Handler for export
1869
	 * @param  int $id Key for particular export class in array $this->exports
1870
	 * @return void
1871
	 */
1872
	public function handleExport($id)
1873
	{
1874
		if (!isset($this->exports[$id])) {
1875
			throw new Nette\Application\ForbiddenRequestException;
1876
		}
1877
1878
		if (!empty($this->columns_export_order)) {
1879
			$this->setColumnsOrder($this->columns_export_order);
1880
		}
1881
1882
		$export = $this->exports[$id];
1883
1884
		if ($export->isFiltered()) {
1885
			$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...
1886
			$filter    = $this->assableFilters();
1887
		} else {
1888
			$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...
1889
			$filter    = [];
1890
		}
1891
1892
		if (NULL === $this->dataModel) {
1893
			throw new DataGridException('You have to set a data source first.');
1894
		}
1895
1896
		$rows = [];
1897
1898
		$items = Nette\Utils\Callback::invokeArgs(
1899
			[$this->dataModel, 'filterData'], [
1900
				NULL,
1901
				$this->createSorting($this->sort, $this->sort_callback),
1902
				$filter
1903
			]
1904
		);
1905
1906
		foreach ($items as $item) {
1907
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1908
		}
1909
1910
		if ($export instanceof Export\ExportCsv) {
1911
			$export->invoke($rows);
1912
		} else {
1913
			$export->invoke($items);
1914
		}
1915
1916
		if ($export->isAjax()) {
1917
			$this->reload();
1918
		}
1919
	}
1920
1921
1922
	/**
1923
	 * Handler for getting children of parent item (e.g. category)
1924
	 * @param  int $parent
1925
	 * @return void
1926
	 */
1927
	public function handleGetChildren($parent)
1928
	{
1929
		$this->setDataSource(
1930
			call_user_func($this->tree_view_children_callback, $parent)
1931
		);
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('items');
1938
1939
			$this->onRedraw();
1940
		} else {
1941
			$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...
1942
		}
1943
	}
1944
1945
1946
	/**
1947
	 * Handler for getting item detail
1948
	 * @param  mixed $id
1949
	 * @return void
1950
	 */
1951
	public function handleGetItemDetail($id)
1952
	{
1953
		$this->getTemplate()->add('toggle_detail', $id);
1954
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1955
1956
		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...
1957
			$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...
1958
			$this->redrawControl('items');
1959
1960
			$this->onRedraw();
1961
		} else {
1962
			$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...
1963
		}
1964
	}
1965
1966
1967
	/**
1968
	 * Handler for inline editing
1969
	 * @param  mixed $id
1970
	 * @param  mixed $key
1971
	 * @return void
1972
	 */
1973
	public function handleEdit($id, $key)
1974
	{
1975
		$column = $this->getColumn($key);
1976
		$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...
1977
1978
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1979
	}
1980
1981
1982
	/**
1983
	 * Redraw $this
1984
	 * @return void
1985
	 */
1986
	public function reload($snippets = [])
1987
	{
1988
		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...
1989
			$this->redrawControl('tbody');
1990
			$this->redrawControl('pagination');
1991
1992
			/**
1993
			 * manualy reset exports links...
1994
			 */
1995
			$this->resetExportsLinks();
1996
			$this->redrawControl('exports');
1997
1998
			foreach ($snippets as $snippet) {
1999
				$this->redrawControl($snippet);
2000
			}
2001
2002
			$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...
2003
2004
			$this->onRedraw();
2005
		} else {
2006
			$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...
2007
		}
2008
	}
2009
2010
2011
	/**
2012
	 * Handler for column status
2013
	 * @param  string $id
2014
	 * @param  string $key
2015
	 * @param  string $value
2016
	 * @return void
2017
	 */
2018
	public function handleChangeStatus($id, $key, $value)
2019
	{
2020
		if (empty($this->columns[$key])) {
2021
			throw new DataGridException("ColumnStatus[$key] does not exist");
2022
		}
2023
2024
		$this->columns[$key]->onChange($id, $value);
2025
	}
2026
2027
2028
	/**
2029
	 * Redraw just one row via ajax
2030
	 * @param  int   $id
2031
	 * @param  mixed $primary_where_column
2032
	 * @return void
2033
	 */
2034
	public function redrawItem($id, $primary_where_column = NULL)
2035
	{
2036
		$this->snippets_set = TRUE;
2037
2038
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
2039
2040
		$this->redrawControl('items');
2041
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
	 * @param $count
2166
	 */
2167
	public function setDefaultPerPage($count){
2168
		$this->items_per_page_default = $count;
2169
	}
2170
2171
2172
	/**
2173
	 * Paginator factory
2174
	 * @return Components\DataGridPaginator\DataGridPaginator
2175
	 */
2176
	public function createComponentPaginator()
2177
	{
2178
		/**
2179
		 * Init paginator
2180
		 */
2181
		$component = new Components\DataGridPaginator\DataGridPaginator(
2182
			$this->getTranslator(),
2183
			static::$icon_prefix
2184
		);
2185
		$paginator = $component->getPaginator();
2186
2187
		$paginator->setPage($this->page);
2188
		$paginator->setItemsPerPage($this->getPerPage());
2189
2190
		return $component;
2191
	}
2192
2193
2194
	/**
2195
	 * Get parameter per_page
2196
	 * @return int
2197
	 */
2198
	public function getPerPage()
2199
	{
2200
		$items_per_page_list = $this->getItemsPerPageList();
2201
2202
		$per_page = $this->per_page ?: $this->items_per_page_default ?: reset($items_per_page_list);
2203
2204
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2205
			$per_page = $this->items_per_page_default ?: reset($items_per_page_list) ?: reset($items_per_page_list);
2206
		}
2207
2208
		return $per_page;
2209
	}
2210
2211
2212
	/**
2213
	 * Get associative array of items_per_page_list
2214
	 * @return array
2215
	 */
2216
	public function getItemsPerPageList()
2217
	{
2218
		if (empty($this->items_per_page_list)) {
2219
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2220
		}
2221
2222
		$list = array_flip($this->items_per_page_list);
2223
2224
		foreach ($list as $key => $value) {
2225
			$list[$key] = $key;
2226
		}
2227
2228
		if (array_key_exists('all', $list)) {
2229
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2230
		}
2231
2232
		return $list;
2233
	}
2234
2235
2236
	/**
2237
	 * Order Grid to "be paginated"
2238
	 * @param bool $do
2239
	 * @return static
2240
	 */
2241
	public function setPagination($do)
2242
	{
2243
		$this->do_paginate = (bool) $do;
2244
2245
		return $this;
2246
	}
2247
2248
2249
	/**
2250
	 * Tell whether Grid is paginated
2251
	 * @return bool
2252
	 */
2253
	public function isPaginated()
2254
	{
2255
		return $this->do_paginate;
2256
	}
2257
2258
2259
	/**
2260
	 * Return current paginator class
2261
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2262
	 */
2263
	public function getPaginator()
2264
	{
2265
		if ($this->isPaginated() && $this->getPerPage() !== 'all') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $this->getPerPage() (integer) and 'all' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
2266
			return $this['paginator'];
2267
		}
2268
2269
		return NULL;
2270
	}
2271
2272
2273
	/********************************************************************************
2274
	 *                                     I18N                                     *
2275
	 ********************************************************************************/
2276
2277
2278
	/**
2279
	 * Set datagrid translator
2280
	 * @param Nette\Localization\ITranslator $translator
2281
	 * @return static
2282
	 */
2283
	public function setTranslator(Nette\Localization\ITranslator $translator)
2284
	{
2285
		$this->translator = $translator;
2286
2287
		return $this;
2288
	}
2289
2290
2291
	/**
2292
	 * Get translator for datagrid
2293
	 * @return Nette\Localization\ITranslator
2294
	 */
2295
	public function getTranslator()
2296
	{
2297
		if (!$this->translator) {
2298
			$this->translator = new Localization\SimpleTranslator;
2299
		}
2300
2301
		return $this->translator;
2302
	}
2303
2304
2305
	/********************************************************************************
2306
	 *                                 COLUMNS ORDER                                *
2307
	 ********************************************************************************/
2308
2309
2310
	/**
2311
	 * Set order of datagrid columns
2312
	 * @param array $order
2313
	 * @return static
2314
	 */
2315
	public function setColumnsOrder($order)
2316
	{
2317
		$new_order = [];
2318
2319
		foreach ($order as $key) {
2320
			if (isset($this->columns[$key])) {
2321
				$new_order[$key] = $this->columns[$key];
2322
			}
2323
		}
2324
2325
		if (sizeof($new_order) === sizeof($this->columns)) {
2326
			$this->columns = $new_order;
2327
		} else {
2328
			throw new DataGridException('When changing columns order, you have to specify all columns');
2329
		}
2330
2331
		return $this;
2332
	}
2333
2334
2335
	/**
2336
	 * Columns order may be different for export and normal grid
2337
	 * @param array $order
2338
	 */
2339
	public function setColumnsExportOrder($order)
2340
	{
2341
		$this->columns_export_order = (array) $order;
2342
	}
2343
2344
2345
	/********************************************************************************
2346
	 *                                SESSION & URL                                 *
2347
	 ********************************************************************************/
2348
2349
2350
	/**
2351
	 * Find some unique session key name
2352
	 * @return string
2353
	 */
2354
	public function getSessionSectionName()
2355
	{
2356
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2357
	}
2358
2359
2360
	/**
2361
	 * Should datagrid remember its filters/pagination/etc using session?
2362
	 * @param bool $remember
2363
	 * @return static
2364
	 */
2365
	public function setRememberState($remember = TRUE)
2366
	{
2367
		$this->remember_state = (bool) $remember;
2368
2369
		return $this;
2370
	}
2371
2372
2373
	/**
2374
	 * Should datagrid refresh url using history API?
2375
	 * @param bool $refresh
2376
	 * @return static
2377
	 */
2378
	public function setRefreshUrl($refresh = TRUE)
2379
	{
2380
		$this->refresh_url = (bool) $refresh;
2381
2382
2383
		return $this;
2384
	}
2385
2386
2387
	/**
2388
	 * Get session data if functionality is enabled
2389
	 * @param  string $key
2390
	 * @return mixed
2391
	 */
2392
	public function getSessionData($key = NULL, $default_value = NULL)
2393
	{
2394
		if (!$this->remember_state) {
2395
			return $key ? $default_value : [];
2396
		}
2397
2398
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2399
	}
2400
2401
2402
	/**
2403
	 * Save session data - just if it is enabled
2404
	 * @param  string $key
2405
	 * @param  mixed  $value
2406
	 * @return void
2407
	 */
2408
	public function saveSessionData($key, $value)
2409
	{
2410
		if ($this->remember_state) {
2411
			$this->grid_session->{$key} = $value;
2412
		}
2413
	}
2414
2415
2416
	/**
2417
	 * Delete session data
2418
	 * @return void
2419
	 */
2420
	public function deleteSesssionData($key)
2421
	{
2422
		unset($this->grid_session->{$key});
2423
	}
2424
2425
2426
	/********************************************************************************
2427
	 *                                  ITEM DETAIL                                 *
2428
	 ********************************************************************************/
2429
2430
2431
	/**
2432
	 * Get items detail parameters
2433
	 * @return array
2434
	 */
2435
	public function getItemsDetail()
2436
	{
2437
		return $this->items_detail;
2438
	}
2439
2440
2441
	/**
2442
	 * Items can have thair detail - toggled
2443
	 * @param mixed $detail callable|string|bool
2444
	 * @param bool|NULL $primary_where_column
2445
	 * @return Column\ItemDetail
2446
	 */
2447
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2448
	{
2449
		if ($this->isSortable()) {
2450
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2451
		}
2452
2453
		$this->items_detail = new Column\ItemDetail(
2454
			$this,
2455
			$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...
2456
		);
2457
2458
		if (is_string($detail)) {
2459
			/**
2460
			 * Item detail will be in separate template
2461
			 */
2462
			$this->items_detail->setType('template');
2463
			$this->items_detail->setTemplate($detail);
2464
2465
		} else if (is_callable($detail)) {
2466
			/**
2467
			 * Item detail will be rendered via custom callback renderer
2468
			 */
2469
			$this->items_detail->setType('renderer');
2470
			$this->items_detail->setRenderer($detail);
2471
2472
		} else if (TRUE === $detail) {
2473
			/**
2474
			 * Item detail will be rendered probably via block #detail
2475
			 */
2476
			$this->items_detail->setType('block');
2477
2478
		} else {
2479
			throw new DataGridException(
2480
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2481
			);
2482
		}
2483
2484
		return $this->items_detail;
2485
	}
2486
2487
2488
	/**
2489
	 * @param callable $callable_set_container 
2490
	 * @return static
2491
	 */
2492
	public function setItemsDetailForm(callable $callable_set_container)
2493
	{
2494
		if ($this->items_detail instanceof Column\ItemDetail) {
2495
			$this->items_detail->setForm(
2496
				new Utils\ItemDetailForm($callable_set_container)
2497
			);
2498
2499
			return $this;
2500
		}
2501
2502
		throw new DataGridException('Please set the ItemDetail first.');
2503
	}
2504
2505
2506
	/**
2507
	 * @return Nette\Forms\Container|NULL
2508
	 */
2509
	public function getItemDetailForm()
2510
	{
2511
		if ($this->items_detail instanceof Column\ItemDetail) {
2512
			return $this->items_detail->getForm();
2513
		}
2514
2515
		return NULL;
2516
	}
2517
2518
2519
	/********************************************************************************
2520
	 *                                ROW PRIVILEGES                                *
2521
	 ********************************************************************************/
2522
2523
2524
	/**
2525
	 * @param  callable $condition
2526
	 * @return void
2527
	 */
2528
	public function allowRowsGroupAction(callable $condition)
2529
	{
2530
		$this->row_conditions['group_action'] = $condition;
2531
	}
2532
2533
2534
	/**
2535
	 * @param  callable $condition
2536
	 * @return void
2537
	 */
2538
	public function allowRowsInlineEdit(callable $condition)
2539
	{
2540
		$this->row_conditions['inline_edit'] = $condition;
2541
	}
2542
2543
2544
	/**
2545
	 * @param  string   $key
2546
	 * @param  callable $condition
2547
	 * @return void
2548
	 */
2549
	public function allowRowsAction($key, callable $condition)
2550
	{
2551
		$this->row_conditions['action'][$key] = $condition;
2552
	}
2553
2554
2555
	/**
2556
	 * @param  string      $name
2557
	 * @param  string|null $key
2558
	 * @return bool|callable
2559
	 */
2560
	public function getRowCondition($name, $key = NULL)
2561
	{
2562
		if (!isset($this->row_conditions[$name])) {
2563
			return FALSE;
2564
		}
2565
2566
		$condition = $this->row_conditions[$name];
2567
2568
		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...
2569
			return $condition;
2570
		}
2571
2572
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2573
	}
2574
2575
2576
	/********************************************************************************
2577
	 *                               COLUMN CALLBACK                                *
2578
	 ********************************************************************************/
2579
2580
2581
	/**
2582
	 * @param  string   $key
2583
	 * @param  callable $callback
2584
	 * @return void
2585
	 */
2586
	public function addColumnCallback($key, callable $callback)
2587
	{
2588
		$this->column_callbacks[$key] = $callback;
2589
	}
2590
2591
2592
	/**
2593
	 * @param  string $key
2594
	 * @return callable|null
2595
	 */
2596
	public function getColumnCallback($key)
2597
	{
2598
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2599
	}
2600
2601
2602
	/********************************************************************************
2603
	 *                                 INLINE EDIT                                  *
2604
	 ********************************************************************************/
2605
2606
2607
	/**
2608
	 * @return InlineEdit
2609
	 */
2610
	public function addInlineEdit($primary_where_column = NULL)
2611
	{
2612
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2613
2614
		return $this->inlineEdit;
2615
	}
2616
2617
2618
	/**
2619
	 * @return InlineEdit|null
2620
	 */
2621
	public function getInlineEdit()
2622
	{
2623
		return $this->inlineEdit;
2624
	}
2625
2626
2627
	/**
2628
	 * @param  mixed $id
2629
	 * @return void
2630
	 */
2631
	public function handleInlineEdit($id)
2632
	{
2633
		if ($this->inlineEdit) {
2634
			$this->inlineEdit->setItemId($id);
2635
2636
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2637
2638
			$this['filter']['inline_edit']->addHidden('_id', $id);
2639
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2640
2641
			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...
2642
				$this->getPresenter()->payload->_datagrid_inline_editing = 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...
2643
			}
2644
2645
			$this->redrawItem($id, $primary_where_column);
2646
		}
2647
	}
2648
2649
2650
	/********************************************************************************
2651
	 *                                  INLINE ADD                                  *
2652
	 ********************************************************************************/
2653
2654
2655
	/**
2656
	 * @return InlineEdit
2657
	 */
2658
	public function addInlineAdd()
2659
	{
2660
		$this->inlineAdd = new InlineEdit($this);
2661
2662
		$this->inlineAdd
2663
			->setIcon('plus')
2664
			->setClass('btn btn-xs btn-default');
2665
2666
		return $this->inlineAdd;
2667
	}
2668
2669
2670
	/**
2671
	 * @return InlineEdit|null
2672
	 */
2673
	public function getInlineAdd()
2674
	{
2675
		return $this->inlineAdd;
2676
	}
2677
2678
2679
	/********************************************************************************
2680
	 *                               HIDEABLE COLUMNS                               *
2681
	 ********************************************************************************/
2682
2683
2684
	/**
2685
	 * Can datagrid hide colums?
2686
	 * @return boolean
2687
	 */
2688
	public function canHideColumns()
2689
	{
2690
		return (bool) $this->can_hide_columns;
2691
	}
2692
2693
2694
	/**
2695
	 * Order Grid to set columns hideable.
2696
	 * @return static
2697
	 */
2698
	public function setColumnsHideable()
2699
	{
2700
		$this->can_hide_columns = TRUE;
2701
2702
		return $this;
2703
	}
2704
2705
2706
	/********************************************************************************
2707
	 *                                COLUMNS SUMMARY                               *
2708
	 ********************************************************************************/
2709
2710
2711
	/**
2712
	 * Will datagrid show summary in the end?
2713
	 * @return bool
2714
	 */
2715
	public function hasColumnsSummary()
2716
	{
2717
		return $this->columnsSummary instanceof ColumnsSummary;
2718
	}
2719
2720
2721
	/**
2722
	 * Set columns to be summarized in the end.
2723
	 * @param  array  $columns
2724
	 * @return ColumnsSummary
2725
	 */
2726
	public function setColumnsSummary(array $columns)
2727
	{
2728
		$this->columnsSummary = new ColumnsSummary($this, $columns);
2729
2730
		return $this->columnsSummary;
2731
	}
2732
2733
2734
	/**
2735
	 * @return ColumnsSummary|NULL
2736
	 */
2737
	public function getColumnsSummary()
2738
	{
2739
		return $this->columnsSummary;
2740
	}
2741
2742
2743
	/********************************************************************************
2744
	 *                                   INTERNAL                                   *
2745
	 ********************************************************************************/
2746
2747
2748
	/**
2749
	 * Get count of columns
2750
	 * @return int
2751
	 */
2752
	public function getColumnsCount()
2753
	{
2754
		$count = sizeof($this->getColumns());
2755
2756
		if (!empty($this->actions)
2757
			|| $this->isSortable()
2758
			|| $this->getItemsDetail()
2759
			|| $this->getInlineEdit()
2760
			|| $this->getInlineAdd()) {
2761
			$count++;
2762
		}
2763
2764
		if ($this->hasGroupActions()) {
2765
			$count++;
2766
		}
2767
2768
		return $count;
2769
	}
2770
2771
2772
	/**
2773
	 * Get primary key of datagrid data source
2774
	 * @return string
2775
	 */
2776
	public function getPrimaryKey()
2777
	{
2778
		return $this->primary_key;
2779
	}
2780
2781
2782
	/**
2783
	 * Get set of set columns
2784
	 * @return Column\IColumn[]
2785
	 */
2786
	public function getColumns()
2787
	{
2788
		if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
2789
			$columns_to_hide = [];
2790
2791
			foreach ($this->columns as $key => $column) {
2792
				if ($column->getDefaultHide()) {
2793
					$columns_to_hide[] = $key;
2794
				}
2795
			}
2796
2797
			if (!empty($columns_to_hide)) {
2798
				$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
2799
				$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2800
			}
2801
		}
2802
2803
		$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
2804
		
2805
		foreach ($hidden_columns as $column) {
2806
			if (!empty($this->columns[$column])) {
2807
				$this->columns_visibility[$column] = [
2808
					'visible' => FALSE,
2809
					'name' => $this->columns[$column]->getName()
2810
				];
2811
2812
				$this->removeColumn($column);
2813
			}
2814
		}
2815
2816
		return $this->columns;
2817
	}
2818
2819
2820
	/**
2821
	 * @return PresenterComponent
2822
	 */
2823
	public function getParent()
2824
	{
2825
		$parent = parent::getParent();
2826
2827
		if (!($parent instanceof PresenterComponent)) {
2828
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2829
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2830
			);
2831
		}
2832
2833
		return $parent;
2834
	}
2835
2836
2837
	/**
2838
	 * Some of datagrid columns is hidden by default
2839
	 * @param bool $default_hide
2840
	 */
2841
	public function setSomeColumnDefaultHide($default_hide)
2842
	{
2843
		$this->some_column_default_hide = $default_hide;
2844
	}
2845
2846
2847
	/**
2848
	 * Are some of columns hidden bydefault?
2849
	 */
2850
	public function hasSomeColumnDefaultHide()
2851
	{
2852
		return $this->some_column_default_hide;
2853
	}
2854
2855
}
2856